From 930c6bab45621e93f74b1e8b4c9b0eb8313cc294 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Mon, 29 Jun 2026 09:15:50 +0200 Subject: [PATCH] Build compliance portal home page sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add the frameworks (Compliance), security commitments, trusted-by, and recent updates sections to the home page, plus a site-wide "Powered by Probo" footer. Frameworks, references, and updates are relay-driven; the security commitments section uses placeholder POJO data until a backend exists. Keep generic primitives in the v2 kit (ProboLogo, a faint Text tone, and a soft Card variant with padding="none") and place the product-specific cards under the app's components folder, composing the base Card. Resolve each framework's themed logo in the graph via Framework.themedLogoUrl, mirroring the trust center logo resolver. Signed-off-by: Émile Ré --- .../compliance-portal/src/_locales/en-US.json | 14 ++- .../compliance-portal/src/_locales/fr-FR.json | 14 ++- .../CommitmentCard/CommitmentCard.tsx | 53 +++++++++++ .../CommitmentCard/CommitmentCardSkeleton.tsx | 30 +++++++ .../src/components/CommitmentCard/variants.ts | 28 ++++++ .../ComplianceArticleItem.tsx | 56 ++++++++++++ .../ComplianceArticleItemSkeleton.tsx | 34 ++++++++ .../ComplianceArticleItem/variants.ts | 29 +++++++ .../ComplianceFrameworkListItem.tsx | 55 ++++++++++++ .../ComplianceFrameworksSection.tsx | 60 +++++++++++++ .../ComplianceFrameworksSectionSkeleton.tsx | 35 ++++++++ .../src/components/HeaderBand/variants.ts | 2 +- .../src/components/Hero/variants.ts | 2 +- .../components/HomeSection/HomeSection.tsx | 43 +++++++++ .../src/components/HomeSection/variants.ts | 24 +++++ .../src/components/MediaTile/MediaTile.tsx | 50 +++++++++++ .../MediaTile/MediaTileSkeleton.tsx | 33 +++++++ .../src/components/MediaTile/variants.ts | 50 +++++++++++ .../src/components/PoweredBy/PoweredBy.tsx | 50 +++++++++++ .../src/components/PoweredBy/variants.ts | 27 ++++++ .../MailingListUpdateListItem.tsx | 48 ++++++++++ .../RecentUpdates/RecentUpdatesSection.tsx | 73 ++++++++++++++++ .../RecentUpdatesSectionSkeleton.tsx | 41 +++++++++ .../SecurityCommitmentsSection.tsx | 66 ++++++++++++++ .../SecurityCommitmentsSectionSkeleton.tsx | 40 +++++++++ .../securityCommitments.ts | 87 +++++++++++++++++++ .../SecurityCommitments/variants.ts | 26 ++++++ .../TrustCenterReferenceListItem.tsx | 60 +++++++++++++ .../components/TrustedBy/TrustedBySection.tsx | 59 +++++++++++++ .../TrustedBy/TrustedBySectionSkeleton.tsx | 35 ++++++++ .../src/lib/datetime/relativeTime.ts | 41 +++++++++ .../relay/resolvers/FrameworkLogoResolver.ts | 63 ++++++++++++++ apps/compliance-portal/src/pages/HomePage.tsx | 33 +++++-- .../src/pages/HomePageSkeleton.tsx | 18 +++- .../src/pages/MainLayout.tsx | 10 ++- packages/ui/src/v2/Card/Card.tsx | 4 +- packages/ui/src/v2/Card/variants.ts | 26 ++++-- .../ui/src/v2/ProboLogo/ProboLogo.stories.tsx | 37 ++++++++ packages/ui/src/v2/ProboLogo/ProboLogo.tsx | 53 +++++++++++ packages/ui/src/v2/typography/variants.ts | 5 ++ 40 files changed, 1490 insertions(+), 24 deletions(-) create mode 100644 apps/compliance-portal/src/components/CommitmentCard/CommitmentCard.tsx create mode 100644 apps/compliance-portal/src/components/CommitmentCard/CommitmentCardSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/CommitmentCard/variants.ts create mode 100644 apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItem.tsx create mode 100644 apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItemSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/ComplianceArticleItem/variants.ts create mode 100644 apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworkListItem.tsx create mode 100644 apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx create mode 100644 apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/HomeSection/HomeSection.tsx create mode 100644 apps/compliance-portal/src/components/HomeSection/variants.ts create mode 100644 apps/compliance-portal/src/components/MediaTile/MediaTile.tsx create mode 100644 apps/compliance-portal/src/components/MediaTile/MediaTileSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/MediaTile/variants.ts create mode 100644 apps/compliance-portal/src/components/PoweredBy/PoweredBy.tsx create mode 100644 apps/compliance-portal/src/components/PoweredBy/variants.ts create mode 100644 apps/compliance-portal/src/components/RecentUpdates/MailingListUpdateListItem.tsx create mode 100644 apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx create mode 100644 apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSectionSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSection.tsx create mode 100644 apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton.tsx create mode 100644 apps/compliance-portal/src/components/SecurityCommitments/securityCommitments.ts create mode 100644 apps/compliance-portal/src/components/SecurityCommitments/variants.ts create mode 100644 apps/compliance-portal/src/components/TrustedBy/TrustCenterReferenceListItem.tsx create mode 100644 apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx create mode 100644 apps/compliance-portal/src/components/TrustedBy/TrustedBySectionSkeleton.tsx create mode 100644 apps/compliance-portal/src/lib/datetime/relativeTime.ts create mode 100644 apps/compliance-portal/src/lib/relay/resolvers/FrameworkLogoResolver.ts create mode 100644 packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx create mode 100644 packages/ui/src/v2/ProboLogo/ProboLogo.tsx diff --git a/apps/compliance-portal/src/_locales/en-US.json b/apps/compliance-portal/src/_locales/en-US.json index 3c63de137..094711e33 100644 --- a/apps/compliance-portal/src/_locales/en-US.json +++ b/apps/compliance-portal/src/_locales/en-US.json @@ -13,7 +13,16 @@ "signOut": "Sign out" }, "home": { - "heroTitle": "Trust at {{name}}." + "heroTitle": "Trust at {{name}}.", + "heroDescription": "Welcome to our Compliance Portal. Find our security documentation and certifications here.", + "sections": { + "compliance": "Compliance", + "trustedBy": "Trusted by", + "recentUpdates": "Recent updates" + }, + "recentUpdates": { + "viewAll": "View all" + } }, "documents": { "title": "Documents" @@ -33,5 +42,8 @@ "title": "Page not found", "description": "The page you are looking for does not exist or has moved.", "backHome": "Back to home" + }, + "footer": { + "poweredBy": "Powered by" } } diff --git a/apps/compliance-portal/src/_locales/fr-FR.json b/apps/compliance-portal/src/_locales/fr-FR.json index 88c6c2ced..d6c2f868c 100644 --- a/apps/compliance-portal/src/_locales/fr-FR.json +++ b/apps/compliance-portal/src/_locales/fr-FR.json @@ -13,7 +13,16 @@ "signOut": "Se déconnecter" }, "home": { - "heroTitle": "La confiance chez {{name}}." + "heroTitle": "La confiance chez {{name}}.", + "heroDescription": "Bienvenue dans notre Compliance Portal. Retrouvez ici notre documentation et nos certifications de sécurité.", + "sections": { + "compliance": "Conformité", + "trustedBy": "Ils nous font confiance", + "recentUpdates": "Mises à jour récentes" + }, + "recentUpdates": { + "viewAll": "Voir tout" + } }, "documents": { "title": "Documents" @@ -33,5 +42,8 @@ "title": "Page introuvable", "description": "La page que vous recherchez n'existe pas ou a été déplacée.", "backHome": "Retour à l'accueil" + }, + "footer": { + "poweredBy": "Propulsé par" } } diff --git a/apps/compliance-portal/src/components/CommitmentCard/CommitmentCard.tsx b/apps/compliance-portal/src/components/CommitmentCard/CommitmentCard.tsx new file mode 100644 index 000000000..3db3bff6b --- /dev/null +++ b/apps/compliance-portal/src/components/CommitmentCard/CommitmentCard.tsx @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Card } from "@probo/ui/src/v2/Card/Card"; +import type { ReactNode } from "react"; + +import { dotPatternStyle } from "#/components/MediaTile/variants"; + +import { commitmentCard } from "./variants"; + +export interface CommitmentCardProps { + // Leading icon shown at 32px over the dotted backdrop. + icon: ReactNode; + // Small accent eyebrow above the title. + eyebrow: ReactNode; + // Card heading. + title: ReactNode; + // Supporting body copy. + description: ReactNode; +} + +// Commitment card, composing the soft Card surface. Region props are placed into +// the layout slots; the consumer supplies typography (Text). Purely +// presentational, so it doubles as the layout for its skeleton. +export function CommitmentCard({ icon, eyebrow, title, description }: CommitmentCardProps) { + const slots = commitmentCard(); + + return ( + +
+
+
+
{icon}
+
+
+ {eyebrow} + {title} + {description} +
+ + ); +} diff --git a/apps/compliance-portal/src/components/CommitmentCard/CommitmentCardSkeleton.tsx b/apps/compliance-portal/src/components/CommitmentCard/CommitmentCardSkeleton.tsx new file mode 100644 index 000000000..90e983cf5 --- /dev/null +++ b/apps/compliance-portal/src/components/CommitmentCard/CommitmentCardSkeleton.tsx @@ -0,0 +1,30 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { CommitmentCard } from "./CommitmentCard"; + +// Loading placeholder paired with CommitmentCard: renders the same layout with a +// pulse icon and skeleton text lines. +export function CommitmentCardSkeleton() { + return ( + } + eyebrow={} + title={} + description={} + /> + ); +} diff --git a/apps/compliance-portal/src/components/CommitmentCard/variants.ts b/apps/compliance-portal/src/components/CommitmentCard/variants.ts new file mode 100644 index 000000000..42b46f724 --- /dev/null +++ b/apps/compliance-portal/src/components/CommitmentCard/variants.ts @@ -0,0 +1,28 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Commitment card (Figma "Commitment Card"): an icon container with the dotted +// + gradient backdrop above an eyebrow / title / description body, rendered +// inside a soft Card. Slots are shared by the shell and its skeleton. +export const commitmentCard = tv({ + slots: { + icon: "relative flex w-full items-center overflow-hidden p-8", + backdrop: "pointer-events-none absolute inset-0", + backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-1/0 to-sand-1", + iconContent: "relative z-10 flex size-8 items-center justify-center text-gold-9", + body: "flex flex-col gap-2 px-8 pb-8", + }, +}); diff --git a/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItem.tsx b/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItem.tsx new file mode 100644 index 000000000..bec6afaee --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItem.tsx @@ -0,0 +1,56 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Text } from "@probo/ui/src/v2/typography/Text"; +import type { ReactNode } from "react"; + +import { complianceArticleItem } from "./variants"; + +export interface ComplianceArticleItemProps { + // Leading category icon (pass a phosphor icon). + icon: ReactNode; + // Primary line. + title: ReactNode; + // Optional accent sub-label below the title. + eyebrow?: ReactNode; + // Right-aligned metadata (e.g. relative time). + meta?: ReactNode; +} + +// A single row of a compliance article list. Encodes the row typography so +// callers pass plain content; wrap rows in a bordered list container. +export function ComplianceArticleItem({ icon, title, eyebrow, meta }: ComplianceArticleItemProps) { + const slots = complianceArticleItem(); + + return ( +
+ {icon} +
+ + {title} + + {eyebrow != null && ( + + {eyebrow} + + )} +
+ {meta != null && ( + + {meta} + + )} +
+ ); +} diff --git a/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItemSkeleton.tsx b/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItemSkeleton.tsx new file mode 100644 index 000000000..b13e37146 --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceArticleItem/ComplianceArticleItemSkeleton.tsx @@ -0,0 +1,34 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { complianceArticleItem } from "./variants"; + +// Loading placeholder paired with ComplianceArticleItem: same row layout with +// a pulse icon and skeleton text. +export function ComplianceArticleItemSkeleton() { + const slots = complianceArticleItem(); + + return ( +
+
+
+ + +
+ +
+ ); +} diff --git a/apps/compliance-portal/src/components/ComplianceArticleItem/variants.ts b/apps/compliance-portal/src/components/ComplianceArticleItem/variants.ts new file mode 100644 index 000000000..6ec1befba --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceArticleItem/variants.ts @@ -0,0 +1,29 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Compliance article list row (Figma "Compliance Article Item"): a leading +// icon, a title (+ optional eyebrow), and right-aligned meta. Designed to sit +// inside a bordered list container; the last row drops its divider. Slots are +// shared by the row and its skeleton. +export const complianceArticleItem = tv({ + slots: { + root: "flex w-full items-center gap-4 border-b border-sand-a2 px-8 py-4 last:border-b-0", + icon: "flex size-6 shrink-0 items-center justify-center text-gold-9 [&_svg]:size-full", + content: "flex min-w-0 flex-1 flex-col gap-1", + meta: "shrink-0", + iconPlaceholder: "size-6 shrink-0 animate-pulse rounded-2 bg-sand-3", + }, +}); diff --git a/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworkListItem.tsx b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworkListItem.tsx new file mode 100644 index 000000000..c412eb4b9 --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworkListItem.tsx @@ -0,0 +1,55 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { CertificateIcon } from "@phosphor-icons/react"; +import { Text } from "@probo/ui/src/v2/typography/Text"; +import { graphql, useFragment } from "react-relay"; + +import { MediaTile } from "#/components/MediaTile/MediaTile"; + +import type { ComplianceFrameworkListItem_complianceFramework$key } from "./__generated__/ComplianceFrameworkListItem_complianceFramework.graphql"; + +const complianceFrameworkListItemFragment = graphql` + fragment ComplianceFrameworkListItem_complianceFramework on ComplianceFramework { + framework { + name + themedLogoUrl + } + } +`; + +interface ComplianceFrameworkListItemProps { + complianceFrameworkKey: ComplianceFrameworkListItem_complianceFramework$key; +} + +// A single framework tile in the Compliance grid. +export function ComplianceFrameworkListItem({ complianceFrameworkKey }: ComplianceFrameworkListItemProps) { + const { framework } = useFragment(complianceFrameworkListItemFragment, complianceFrameworkKey); + const logoUrl = framework.themedLogoUrl; + + return ( + + : + } + label={( + + {framework.name} + + )} + /> + ); +} diff --git a/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx new file mode 100644 index 000000000..91537f50b --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSection.tsx @@ -0,0 +1,60 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { useTranslation } from "react-i18next"; +import { graphql, useFragment } from "react-relay"; + +import { HomeSection } from "#/components/HomeSection/HomeSection"; + +import type { ComplianceFrameworksSection_trustCenter$key } from "./__generated__/ComplianceFrameworksSection_trustCenter.graphql"; +import { ComplianceFrameworkListItem } from "./ComplianceFrameworkListItem"; + +const complianceFrameworksSectionFragment = graphql` + fragment ComplianceFrameworksSection_trustCenter on TrustCenter { + complianceFrameworks(first: 8) { + edges { + node { + id + ...ComplianceFrameworkListItem_complianceFramework + } + } + } + } +`; + +interface ComplianceFrameworksSectionProps { + trustCenterKey: ComplianceFrameworksSection_trustCenter$key; +} + +// "Compliance" section: the grid of certification frameworks the trust center +// covers. +export function ComplianceFrameworksSection({ trustCenterKey }: ComplianceFrameworksSectionProps) { + const { t } = useTranslation(); + const data = useFragment(complianceFrameworksSectionFragment, trustCenterKey); + const frameworks = data.complianceFrameworks.edges.map(edge => edge.node); + + if (frameworks.length === 0) { + return null; + } + + return ( + +
+ {frameworks.map(framework => ( + + ))} +
+
+ ); +} diff --git a/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton.tsx b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton.tsx new file mode 100644 index 000000000..b073b9086 --- /dev/null +++ b/apps/compliance-portal/src/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton.tsx @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { homeSection } from "#/components/HomeSection/variants"; +import { MediaTileSkeleton } from "#/components/MediaTile/MediaTileSkeleton"; + +export function ComplianceFrameworksSectionSkeleton() { + const slots = homeSection(); + + return ( +
+
+ +
+
+ {Array.from({ length: 4 }, (_, index) => ( + + ))} +
+
+ ); +} diff --git a/apps/compliance-portal/src/components/HeaderBand/variants.ts b/apps/compliance-portal/src/components/HeaderBand/variants.ts index b57ff80d1..c3c27abaa 100644 --- a/apps/compliance-portal/src/components/HeaderBand/variants.ts +++ b/apps/compliance-portal/src/components/HeaderBand/variants.ts @@ -19,7 +19,7 @@ import { tv } from "tailwind-variants/lite"; // Hero and the page headers so the band is defined once. export const headerBand = tv({ slots: { - band: "flex w-full flex-col items-center border-b border-sand-6 bg-sand-1 px-8 py-8", + band: "flex w-full flex-col items-center border-b border-sand-a3 bg-sand-1 px-8 py-8", inner: "w-full max-w-5xl", }, }); diff --git a/apps/compliance-portal/src/components/Hero/variants.ts b/apps/compliance-portal/src/components/Hero/variants.ts index 8a9a6ed16..1fa99ab55 100644 --- a/apps/compliance-portal/src/components/Hero/variants.ts +++ b/apps/compliance-portal/src/components/Hero/variants.ts @@ -30,7 +30,7 @@ export const hero = tv({ export const organizationContactInfo = tv({ slots: { // Only a top divider gap; the band's py-8 provides the bottom spacing. - root: "flex w-full items-center gap-6 border-t border-sand-6 pt-4", + root: "flex w-full items-center gap-6 border-t border-sand-a2 pt-4", item: "flex items-center gap-2 text-sand-11 [&_svg]:size-5", link: "flex items-center gap-2 text-sand-11 hover:underline [&_svg]:size-5", }, diff --git a/apps/compliance-portal/src/components/HomeSection/HomeSection.tsx b/apps/compliance-portal/src/components/HomeSection/HomeSection.tsx new file mode 100644 index 000000000..ab9f02d66 --- /dev/null +++ b/apps/compliance-portal/src/components/HomeSection/HomeSection.tsx @@ -0,0 +1,43 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Text } from "@probo/ui/src/v2/typography/Text"; +import type { ReactNode } from "react"; + +import { homeSection } from "./variants"; + +interface HomeSectionProps { + title: ReactNode; + // Optional trailing element in the header row (e.g. a "View all" link). + action?: ReactNode; + children: ReactNode; +} + +// Shared frame for the home page body sections: a labelled header above the +// section content. +export function HomeSection({ title, action, children }: HomeSectionProps) { + const slots = homeSection(); + + return ( +
+
+ + {title} + + {action} +
+ {children} +
+ ); +} diff --git a/apps/compliance-portal/src/components/HomeSection/variants.ts b/apps/compliance-portal/src/components/HomeSection/variants.ts new file mode 100644 index 000000000..582f9eacc --- /dev/null +++ b/apps/compliance-portal/src/components/HomeSection/variants.ts @@ -0,0 +1,24 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Home page section frame: a labelled header row (with an optional trailing +// action) above the section body. Shared by the live sections and skeletons. +export const homeSection = tv({ + slots: { + root: "flex w-full flex-col gap-4 py-8", + header: "flex w-full items-center justify-between", + }, +}); diff --git a/apps/compliance-portal/src/components/MediaTile/MediaTile.tsx b/apps/compliance-portal/src/components/MediaTile/MediaTile.tsx new file mode 100644 index 000000000..f08d10985 --- /dev/null +++ b/apps/compliance-portal/src/components/MediaTile/MediaTile.tsx @@ -0,0 +1,50 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Card } from "@probo/ui/src/v2/Card/Card"; +import type { ReactNode } from "react"; +import type { VariantProps } from "tailwind-variants/lite"; + +import { dotPatternStyle, mediaTile } from "./variants"; + +export type MediaTileProps = VariantProps & { + // Centered media (logo/icon image) shown over the backdrop. + media: ReactNode; + // Caption below the media (pass a Text primitive). + label: ReactNode; + // For the "logo" variant: the image URL rendered as a blurred, magnified + // backdrop behind the media. When omitted, the dotted texture is shown. + backdropSrc?: string; +}; + +// Captioned media tile, composing the soft Card surface. The "icon" variant +// shows a dotted backdrop; the "logo" variant shows a blurred, magnified copy of +// the logo (via backdropSrc). Purely presentational, so it doubles as the layout +// for its skeleton. +export function MediaTile({ media, label, variant = "icon", backdropSrc }: MediaTileProps) { + const slots = mediaTile({ variant }); + + return ( + +
+ {backdropSrc != null + ? + :
} +
+
{media}
+
+
{label}
+ + ); +} diff --git a/apps/compliance-portal/src/components/MediaTile/MediaTileSkeleton.tsx b/apps/compliance-portal/src/components/MediaTile/MediaTileSkeleton.tsx new file mode 100644 index 000000000..b87937e0b --- /dev/null +++ b/apps/compliance-portal/src/components/MediaTile/MediaTileSkeleton.tsx @@ -0,0 +1,33 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; +import type { VariantProps } from "tailwind-variants/lite"; + +import { MediaTile } from "./MediaTile"; +import type { mediaTile } from "./variants"; + +export type MediaTileSkeletonProps = VariantProps; + +// Loading placeholder paired with MediaTile: renders the same layout with a +// pulse block for the media and a TextSkeleton for the caption. +export function MediaTileSkeleton({ variant = "icon" }: MediaTileSkeletonProps) { + return ( + } + label={} + /> + ); +} diff --git a/apps/compliance-portal/src/components/MediaTile/variants.ts b/apps/compliance-portal/src/components/MediaTile/variants.ts new file mode 100644 index 000000000..c85646e1c --- /dev/null +++ b/apps/compliance-portal/src/components/MediaTile/variants.ts @@ -0,0 +1,50 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { CSSProperties } from "react"; +import { tv } from "tailwind-variants/lite"; + +// Captioned media tile (Figma "Framework Card" / "Logo Card"): a media container +// with a decorative backdrop above a centered caption, rendered inside a soft +// Card. The "icon" variant (default) uses a padded square with a dotted texture; +// the "logo" variant fills a square container and uses a blurred, magnified copy +// of the logo as the backdrop. Slots are shared by the shell and its skeleton. +export const mediaTile = tv({ + slots: { + media: "relative flex w-full items-center justify-center overflow-hidden border-b border-sand-a2", + backdrop: "pointer-events-none absolute inset-0", + blurBackdrop: "pointer-events-none absolute inset-0 size-full scale-150 object-cover opacity-10 blur-lg", + backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-1/0 to-sand-1", + mediaContent: "relative z-10 flex size-16 items-center justify-center [&_img]:size-full [&_img]:object-contain", + caption: "flex w-full items-center justify-center px-4 py-3", + }, + variants: { + variant: { + icon: { media: "p-4" }, + logo: { media: "aspect-square" }, + }, + }, + defaultVariants: { + variant: "icon", + }, +}); + +// Repeating dot texture as a radial-gradient using a Radix sand alpha token +// (theme-aware, no binary asset), faded toward the card surface by the +// backdropFade gradient slot. Maps Figma's neutral-alpha dots to sand-a5. Shared +// by the media/commitment cards, the updates list, and the footer. +export const dotPatternStyle: CSSProperties = { + backgroundImage: "radial-gradient(var(--sand-a5) 0.75px, transparent 1.25px)", + backgroundSize: "9px 9px", +}; diff --git a/apps/compliance-portal/src/components/PoweredBy/PoweredBy.tsx b/apps/compliance-portal/src/components/PoweredBy/PoweredBy.tsx new file mode 100644 index 000000000..b6235cb57 --- /dev/null +++ b/apps/compliance-portal/src/components/PoweredBy/PoweredBy.tsx @@ -0,0 +1,50 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { ProboLogo } from "@probo/ui/src/v2/ProboLogo/ProboLogo"; +import { Text } from "@probo/ui/src/v2/typography/Text"; +import type { ReactNode } from "react"; + +import { dotPatternStyle } from "#/components/MediaTile/variants"; + +import { poweredBy } from "./variants"; + +export interface PoweredByProps { + // Localized "Powered by" label. Defaults to the English string. + label?: ReactNode; + // Destination for the Probo logo link. + href?: string; +} + +// "Powered by Probo" footer: a borderless attribution on the body surface, with +// a dotted texture fading in from the bottom and the full Probo logo (picto + +// wordmark). +export function PoweredBy({ label = "Powered by", href = "https://www.probo.com/" }: PoweredByProps) { + const slots = poweredBy(); + + return ( +
+
+
+
+ + {label} + + + + +
+
+ ); +} diff --git a/apps/compliance-portal/src/components/PoweredBy/variants.ts b/apps/compliance-portal/src/components/PoweredBy/variants.ts new file mode 100644 index 000000000..8ac98597b --- /dev/null +++ b/apps/compliance-portal/src/components/PoweredBy/variants.ts @@ -0,0 +1,27 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// "Powered by Probo" footer (Figma "Powered by"): a borderless, full-width row +// on the body surface, with a dotted texture that fades in from the bottom. +export const poweredBy = tv({ + slots: { + root: "relative flex w-full items-center justify-center overflow-hidden bg-sand-2 py-6 text-sand-11", + backdrop: "pointer-events-none absolute inset-0", + backdropFade: "pointer-events-none absolute inset-0 bg-linear-to-b from-sand-2 to-sand-2/0", + content: "relative z-10 flex items-center justify-center gap-2", + logo: "h-6 w-auto text-sand-9", + }, +}); diff --git a/apps/compliance-portal/src/components/RecentUpdates/MailingListUpdateListItem.tsx b/apps/compliance-portal/src/components/RecentUpdates/MailingListUpdateListItem.tsx new file mode 100644 index 000000000..306fa8bb9 --- /dev/null +++ b/apps/compliance-portal/src/components/RecentUpdates/MailingListUpdateListItem.tsx @@ -0,0 +1,48 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { NewspaperIcon } from "@phosphor-icons/react"; +import { useTranslation } from "react-i18next"; +import { graphql, useFragment } from "react-relay"; + +import { ComplianceArticleItem } from "#/components/ComplianceArticleItem/ComplianceArticleItem"; +import { formatRelativeTime } from "#/lib/datetime/relativeTime"; + +import type { MailingListUpdateListItem_update$key } from "./__generated__/MailingListUpdateListItem_update.graphql"; + +const mailingListUpdateListItemFragment = graphql` + fragment MailingListUpdateListItem_update on MailingListUpdate { + title + updatedAt + } +`; + +interface MailingListUpdateListItemProps { + updateKey: MailingListUpdateListItem_update$key; +} + +// A single "Recent updates" row. The schema has no category, so we show the +// title and relative date with a single generic icon. +export function MailingListUpdateListItem({ updateKey }: MailingListUpdateListItemProps) { + const { i18n } = useTranslation(); + const update = useFragment(mailingListUpdateListItemFragment, updateKey); + + return ( + } + title={update.title} + meta={formatRelativeTime(update.updatedAt, i18n.language)} + /> + ); +} diff --git a/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx new file mode 100644 index 000000000..0c014e431 --- /dev/null +++ b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSection.tsx @@ -0,0 +1,73 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Link } from "@probo/ui/src/v2/Button/Link"; +import { useTranslation } from "react-i18next"; +import { graphql, useFragment } from "react-relay"; + +import { HomeSection } from "#/components/HomeSection/HomeSection"; +import { dotPatternStyle } from "#/components/MediaTile/variants"; + +import type { RecentUpdatesSection_trustCenter$key } from "./__generated__/RecentUpdatesSection_trustCenter.graphql"; +import { MailingListUpdateListItem } from "./MailingListUpdateListItem"; + +const recentUpdatesSectionFragment = graphql` + fragment RecentUpdatesSection_trustCenter on TrustCenter { + updates(first: 5) { + edges { + node { + id + ...MailingListUpdateListItem_update + } + } + } + } +`; + +interface RecentUpdatesSectionProps { + trustCenterKey: RecentUpdatesSection_trustCenter$key; +} + +// "Recent updates" section: the latest mailing-list updates as a list, with a +// link to the full updates page. +export function RecentUpdatesSection({ trustCenterKey }: RecentUpdatesSectionProps) { + const { t } = useTranslation(); + const data = useFragment(recentUpdatesSectionFragment, trustCenterKey); + const updates = data.updates.edges.map(edge => edge.node); + + if (updates.length === 0) { + return null; + } + + return ( + + {t("home.recentUpdates.viewAll")} + + )} + > +
+
+
+
+ {updates.map(update => ( + + ))} +
+
+ + ); +} diff --git a/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSectionSkeleton.tsx b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSectionSkeleton.tsx new file mode 100644 index 000000000..f1bcdb5f4 --- /dev/null +++ b/apps/compliance-portal/src/components/RecentUpdates/RecentUpdatesSectionSkeleton.tsx @@ -0,0 +1,41 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { ComplianceArticleItemSkeleton } from "#/components/ComplianceArticleItem/ComplianceArticleItemSkeleton"; +import { homeSection } from "#/components/HomeSection/variants"; +import { dotPatternStyle } from "#/components/MediaTile/variants"; + +export function RecentUpdatesSectionSkeleton() { + const slots = homeSection(); + + return ( +
+
+ + +
+
+
+
+
+ {Array.from({ length: 5 }, (_, index) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSection.tsx b/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSection.tsx new file mode 100644 index 000000000..e2b8585b7 --- /dev/null +++ b/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSection.tsx @@ -0,0 +1,66 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Text } from "@probo/ui/src/v2/typography/Text"; + +import { CommitmentCard } from "#/components/CommitmentCard/CommitmentCard"; + +import { SECURITY_COMMITMENT_GROUPS } from "./securityCommitments"; +import { securityCommitments } from "./variants"; + +// "Security Commitments" section. +// +// TODO: This section renders placeholder data from a local POJO +// (./securityCommitments.ts) because there is no backend / DB structure for it +// yet. Replace it with a relay-driven fragment (and i18n copy) once available. +export function SecurityCommitmentsSection() { + const slots = securityCommitments(); + + return ( +
+ {SECURITY_COMMITMENT_GROUPS.map(group => ( +
+
+ {group.eyebrow != null && ( + + {group.eyebrow} + + )} + + {group.title} + + + {group.description} + +
+
+ {group.items.map(item => ( + } + eyebrow={{item.eyebrow}} + title={( + + {item.title} + + )} + description={{item.description}} + /> + ))} +
+
+ ))} +
+ ); +} diff --git a/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton.tsx b/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton.tsx new file mode 100644 index 000000000..f808220b2 --- /dev/null +++ b/apps/compliance-portal/src/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton.tsx @@ -0,0 +1,40 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { CommitmentCardSkeleton } from "#/components/CommitmentCard/CommitmentCardSkeleton"; + +import { securityCommitments } from "./variants"; + +export function SecurityCommitmentsSectionSkeleton() { + const slots = securityCommitments(); + + return ( +
+
+
+ + + +
+
+ {Array.from({ length: 3 }, (_, index) => ( + + ))} +
+
+
+ ); +} diff --git a/apps/compliance-portal/src/components/SecurityCommitments/securityCommitments.ts b/apps/compliance-portal/src/components/SecurityCommitments/securityCommitments.ts new file mode 100644 index 000000000..eef5e1b21 --- /dev/null +++ b/apps/compliance-portal/src/components/SecurityCommitments/securityCommitments.ts @@ -0,0 +1,87 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { Icon } from "@phosphor-icons/react"; +import { EyeSlashIcon, FingerprintIcon, LockKeyIcon, ShieldWarningIcon, SirenIcon } from "@phosphor-icons/react"; + +export interface SecurityCommitmentItem { + eyebrow: string; + title: string; + description: string; + Icon: Icon; +} + +export interface SecurityCommitmentGroup { + // Only the first group carries the section eyebrow in the design. + eyebrow?: string; + title: string; + description: string; + items: SecurityCommitmentItem[]; +} + +// TODO: This content is placeholder data. There is no backend / DB structure for +// security commitments yet — replace this POJO with relay-sourced data (and move +// the copy into i18n) once the schema exists. +export const SECURITY_COMMITMENT_GROUPS: SecurityCommitmentGroup[] = [ + { + eyebrow: "Security Commitments", + title: "Data Protection", + description: + "Your data is protected with industry-leading encryption and strict privacy controls, from storage to transit to processing.", + items: [ + { + eyebrow: "Data Protection", + title: "Encrypted at rest. Encrypted in transit. Always.", + description: + "Customer data is protected with AES-256 at rest and TLS 1.3 in transit. Keys rotated on a fixed schedule.", + Icon: LockKeyIcon, + }, + { + eyebrow: "Privacy", + title: "Customer data stays customer data.", + description: + "PII is masked in non-production environments. Access requires SSO with hardware-backed keys.", + Icon: EyeSlashIcon, + }, + { + eyebrow: "Identity", + title: "Phishing-resistant authentication, by default.", + description: + "Every employee authenticates via WebAuthn. Production access is just-in-time and recorded.", + Icon: FingerprintIcon, + }, + ], + }, + { + title: "Operational Security", + description: + "We maintain continuous uptime and rapid incident response so you can rely on our platform around the clock.", + items: [ + { + eyebrow: "Threat Detection", + title: "Anomalies do not wait. Neither do we.", + description: + "24/7 SIEM monitoring with automated triage. Critical incidents page on-call within 90 seconds.", + Icon: ShieldWarningIcon, + }, + { + eyebrow: "Incident Response", + title: "Issues are caught fast. And handled.", + description: + "Security incidents are investigated within hours. Customers notified within 24 hours of a confirmed breach.", + Icon: SirenIcon, + }, + ], + }, +]; diff --git a/apps/compliance-portal/src/components/SecurityCommitments/variants.ts b/apps/compliance-portal/src/components/SecurityCommitments/variants.ts new file mode 100644 index 000000000..45c1d3ad4 --- /dev/null +++ b/apps/compliance-portal/src/components/SecurityCommitments/variants.ts @@ -0,0 +1,26 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { tv } from "tailwind-variants/lite"; + +// Security Commitments section: stacked groups, each a header above a grid of +// commitment cards. Slots shared by the section and its skeleton. +export const securityCommitments = tv({ + slots: { + root: "flex w-full flex-col gap-8 py-8", + group: "flex w-full flex-col gap-4", + groupHeader: "flex w-full flex-col gap-2", + grid: "grid grid-cols-3 gap-4", + }, +}); diff --git a/apps/compliance-portal/src/components/TrustedBy/TrustCenterReferenceListItem.tsx b/apps/compliance-portal/src/components/TrustedBy/TrustCenterReferenceListItem.tsx new file mode 100644 index 000000000..85d1219ce --- /dev/null +++ b/apps/compliance-portal/src/components/TrustedBy/TrustCenterReferenceListItem.tsx @@ -0,0 +1,60 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { Text } from "@probo/ui/src/v2/typography/Text"; +import { graphql, useFragment } from "react-relay"; + +import { MediaTile } from "#/components/MediaTile/MediaTile"; +import { externalHref } from "#/lib/url/hostname"; + +import type { TrustCenterReferenceListItem_reference$key } from "./__generated__/TrustCenterReferenceListItem_reference.graphql"; + +const trustCenterReferenceListItemFragment = graphql` + fragment TrustCenterReferenceListItem_reference on TrustCenterReference { + name + websiteUrl + logo { + downloadUrl + } + } +`; + +interface TrustCenterReferenceListItemProps { + referenceKey: TrustCenterReferenceListItem_reference$key; +} + +// A single "Trusted by" logo tile, linking to the reference's website. +export function TrustCenterReferenceListItem({ referenceKey }: TrustCenterReferenceListItemProps) { + const reference = useFragment(trustCenterReferenceListItemFragment, referenceKey); + + return ( + + } + label={( + + {reference.name} + + )} + /> + + ); +} diff --git a/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx b/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx new file mode 100644 index 000000000..c210b64c2 --- /dev/null +++ b/apps/compliance-portal/src/components/TrustedBy/TrustedBySection.tsx @@ -0,0 +1,59 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { useTranslation } from "react-i18next"; +import { graphql, useFragment } from "react-relay"; + +import { HomeSection } from "#/components/HomeSection/HomeSection"; + +import type { TrustedBySection_trustCenter$key } from "./__generated__/TrustedBySection_trustCenter.graphql"; +import { TrustCenterReferenceListItem } from "./TrustCenterReferenceListItem"; + +const trustedBySectionFragment = graphql` + fragment TrustedBySection_trustCenter on TrustCenter { + references(first: 12) { + edges { + node { + id + ...TrustCenterReferenceListItem_reference + } + } + } + } +`; + +interface TrustedBySectionProps { + trustCenterKey: TrustedBySection_trustCenter$key; +} + +// "Trusted by" section: a grid of customer / reference logos. +export function TrustedBySection({ trustCenterKey }: TrustedBySectionProps) { + const { t } = useTranslation(); + const data = useFragment(trustedBySectionFragment, trustCenterKey); + const references = data.references.edges.map(edge => edge.node); + + if (references.length === 0) { + return null; + } + + return ( + +
+ {references.map(reference => ( + + ))} +
+
+ ); +} diff --git a/apps/compliance-portal/src/components/TrustedBy/TrustedBySectionSkeleton.tsx b/apps/compliance-portal/src/components/TrustedBy/TrustedBySectionSkeleton.tsx new file mode 100644 index 000000000..d8a7ee342 --- /dev/null +++ b/apps/compliance-portal/src/components/TrustedBy/TrustedBySectionSkeleton.tsx @@ -0,0 +1,35 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { homeSection } from "#/components/HomeSection/variants"; +import { MediaTileSkeleton } from "#/components/MediaTile/MediaTileSkeleton"; + +export function TrustedBySectionSkeleton() { + const slots = homeSection(); + + return ( +
+
+ +
+
+ {Array.from({ length: 6 }, (_, index) => ( + + ))} +
+
+ ); +} diff --git a/apps/compliance-portal/src/lib/datetime/relativeTime.ts b/apps/compliance-portal/src/lib/datetime/relativeTime.ts new file mode 100644 index 000000000..53af1d487 --- /dev/null +++ b/apps/compliance-portal/src/lib/datetime/relativeTime.ts @@ -0,0 +1,41 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +const DIVISIONS: { amount: number; unit: Intl.RelativeTimeFormatUnit }[] = [ + { amount: 60, unit: "second" }, + { amount: 60, unit: "minute" }, + { amount: 24, unit: "hour" }, + { amount: 7, unit: "day" }, + { amount: 4.34524, unit: "week" }, + { amount: 12, unit: "month" }, + { amount: Number.POSITIVE_INFINITY, unit: "year" }, +]; + +// Locale-aware "x days ago" style formatting via Intl.RelativeTimeFormat. The +// locale must be the active i18next language so the output follows the UI. +export function formatRelativeTime(date: Date | string | number, locale: string): string { + const target = date instanceof Date ? date : new Date(date); + const formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto" }); + + let duration = (target.getTime() - Date.now()) / 1000; + + for (const division of DIVISIONS) { + if (Math.abs(duration) < division.amount) { + return formatter.format(Math.round(duration), division.unit); + } + duration /= division.amount; + } + + return formatter.format(Math.round(duration), "year"); +} diff --git a/apps/compliance-portal/src/lib/relay/resolvers/FrameworkLogoResolver.ts b/apps/compliance-portal/src/lib/relay/resolvers/FrameworkLogoResolver.ts new file mode 100644 index 000000000..904eca5f5 --- /dev/null +++ b/apps/compliance-portal/src/lib/relay/resolvers/FrameworkLogoResolver.ts @@ -0,0 +1,63 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import { graphql } from "react-relay"; +import { type LiveState, readFragment } from "relay-runtime"; + +import type { FrameworkLogoResolverFragment$key } from "./__generated__/FrameworkLogoResolverFragment.graphql"; + +function prefersDark(): boolean { + return typeof window !== "undefined" + && !!window.matchMedia + && window.matchMedia("(prefers-color-scheme: dark)").matches; +} + +/** + * @relayField Framework.themedLogoUrl: String + * @rootFragment FrameworkLogoResolverFragment + * @live + * + * Resolves the framework logo download URL for the current system color scheme: + * the dark logo (falling back to the light one) when the OS prefers dark, + * otherwise the light logo. Lives in the graph so consumers select a single + * field instead of threading `useSystemTheme` through URL selection. + */ +export function themedLogoUrl( + key: FrameworkLogoResolverFragment$key, +): LiveState { + const data = readFragment( + graphql` + fragment FrameworkLogoResolverFragment on Framework { + lightLogo { + downloadUrl + } + darkLogo { + downloadUrl + } + } + `, + key, + ); + const lightUrl = data.lightLogo?.downloadUrl ?? null; + const darkUrl = data.darkLogo?.downloadUrl ?? lightUrl; + + return { + read: () => (prefersDark() ? darkUrl : lightUrl), + subscribe: (callback) => { + const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)"); + mediaQuery.addEventListener("change", callback); + return () => mediaQuery.removeEventListener("change", callback); + }, + }; +} diff --git a/apps/compliance-portal/src/pages/HomePage.tsx b/apps/compliance-portal/src/pages/HomePage.tsx index ad82afaa2..d8cc52fa2 100644 --- a/apps/compliance-portal/src/pages/HomePage.tsx +++ b/apps/compliance-portal/src/pages/HomePage.tsx @@ -16,8 +16,12 @@ import { useTranslation } from "react-i18next"; import type { PreloadedQuery } from "react-relay"; import { graphql, usePreloadedQuery } from "react-relay"; +import { ComplianceFrameworksSection } from "#/components/ComplianceFrameworks/ComplianceFrameworksSection"; import { Hero } from "#/components/Hero/Hero"; import { OrganizationContactInfo } from "#/components/Hero/OrganizationContactInfo"; +import { RecentUpdatesSection } from "#/components/RecentUpdates/RecentUpdatesSection"; +import { SecurityCommitmentsSection } from "#/components/SecurityCommitments/SecurityCommitmentsSection"; +import { TrustedBySection } from "#/components/TrustedBy/TrustedBySection"; import type { HomePageQuery } from "./__generated__/HomePageQuery.graphql"; @@ -26,9 +30,11 @@ export const homePageQuery = graphql` currentTrustCenter @required(action: THROW) { organization { name - description ...OrganizationContactInfo_organization } + ...ComplianceFrameworksSection_trustCenter + ...TrustedBySection_trustCenter + ...RecentUpdatesSection_trustCenter } } `; @@ -40,14 +46,25 @@ interface HomePageProps { export function HomePage({ queryRef }: HomePageProps) { const { t } = useTranslation(); const data = usePreloadedQuery(homePageQuery, queryRef); - const { organization } = data.currentTrustCenter; + const { currentTrustCenter } = data; + const { organization } = currentTrustCenter; return ( - - - + <> + + + +
+
+ + + + +
+
+ ); } diff --git a/apps/compliance-portal/src/pages/HomePageSkeleton.tsx b/apps/compliance-portal/src/pages/HomePageSkeleton.tsx index e2e694dab..d1e222739 100644 --- a/apps/compliance-portal/src/pages/HomePageSkeleton.tsx +++ b/apps/compliance-portal/src/pages/HomePageSkeleton.tsx @@ -12,8 +12,24 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +import { ComplianceFrameworksSectionSkeleton } from "#/components/ComplianceFrameworks/ComplianceFrameworksSectionSkeleton"; import { HeroSkeleton } from "#/components/Hero/HeroSkeleton"; +import { RecentUpdatesSectionSkeleton } from "#/components/RecentUpdates/RecentUpdatesSectionSkeleton"; +import { SecurityCommitmentsSectionSkeleton } from "#/components/SecurityCommitments/SecurityCommitmentsSectionSkeleton"; +import { TrustedBySectionSkeleton } from "#/components/TrustedBy/TrustedBySectionSkeleton"; export function HomePageSkeleton() { - return ; + return ( + <> + +
+
+ + + + +
+
+ + ); } diff --git a/apps/compliance-portal/src/pages/MainLayout.tsx b/apps/compliance-portal/src/pages/MainLayout.tsx index 7e7303c79..566c356fa 100644 --- a/apps/compliance-portal/src/pages/MainLayout.tsx +++ b/apps/compliance-portal/src/pages/MainLayout.tsx @@ -12,10 +12,12 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. +import { useTranslation } from "react-i18next"; import type { PreloadedQuery } from "react-relay"; import { graphql, usePreloadedQuery } from "react-relay"; import { Outlet } from "react-router"; +import { PoweredBy } from "#/components/PoweredBy/PoweredBy"; import { TopBar } from "#/components/TopBar/TopBar"; import type { MainLayoutQuery } from "./__generated__/MainLayoutQuery.graphql"; @@ -31,12 +33,16 @@ interface MainLayoutProps { } export function MainLayout({ queryRef }: MainLayoutProps) { + const { t } = useTranslation(); const data = usePreloadedQuery(mainLayoutQuery, queryRef); return ( -
+
- +
+ +
+
); } diff --git a/packages/ui/src/v2/Card/Card.tsx b/packages/ui/src/v2/Card/Card.tsx index 255a9e3aa..2c8ac2df5 100644 --- a/packages/ui/src/v2/Card/Card.tsx +++ b/packages/ui/src/v2/Card/Card.tsx @@ -24,10 +24,10 @@ export type CardProps = ComponentProps<"div"> & VariantProps; // clickable card set `interactive` and wrap the content in an anchor/link. Use // CardInset for content that should bleed to the card's edges. export function Card(props: CardProps) { - const { size = 1, variant, interactive, className, children, ...rest } = props; + const { size = 1, padding = size, variant, interactive, className, children, ...rest } = props; return ( -
+
{children}
); diff --git a/packages/ui/src/v2/Card/variants.ts b/packages/ui/src/v2/Card/variants.ts index 27e57d812..acd6b4f19 100644 --- a/packages/ui/src/v2/Card/variants.ts +++ b/packages/ui/src/v2/Card/variants.ts @@ -19,17 +19,29 @@ import { tv } from "tailwind-variants/lite"; export const card = tv({ base: "block overflow-hidden", variants: { - // Padding scales with size; radius scales with it too (per the Figma). + // Radius scales with size (per the Figma). size: { - 1: "rounded-4 p-3", - 2: "rounded-4 p-4", - 3: "rounded-5 p-5", - 4: "rounded-5 p-6", - 5: "rounded-6 p-8", + 1: "rounded-4", + 2: "rounded-4", + 3: "rounded-5", + 4: "rounded-5", + 5: "rounded-6", + }, + // Padding defaults to match `size` (set in Card.tsx); pass `padding="none"` + // for cards whose regions own their own padding (e.g. full-bleed media). + padding: { + 1: "p-3", + 2: "p-4", + 3: "p-5", + 4: "p-6", + 5: "p-8", + none: "p-0", }, variant: { surface: "border border-sand-6 bg-sand-2", classic: "border border-sand-6 bg-sand-2 shadow-2", + // Soft surface: translucent neutral border on the app background. + soft: "border border-sand-a3 bg-sand-1", ghost: "", }, // Hover/active affordance for clickable cards. This tunes look only; wrap @@ -40,7 +52,7 @@ export const card = tv({ }, }, compoundVariants: [ - { variant: ["surface", "classic"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" }, + { variant: ["surface", "classic", "soft"], interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" }, { variant: "ghost", interactive: true, class: "hover:bg-sand-3 active:bg-sand-4" }, ], defaultVariants: { diff --git a/packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx b/packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx new file mode 100644 index 000000000..79b882679 --- /dev/null +++ b/packages/ui/src/v2/ProboLogo/ProboLogo.stories.tsx @@ -0,0 +1,37 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { Meta, StoryObj } from "@storybook/react"; + +import { ProboLogo } from "./ProboLogo"; + +export default { + title: "v2/ProboLogo", + component: ProboLogo, + render: () => , +} satisfies Meta; + +type Story = StoryObj; + +export const Playground: Story = {}; + +export const Sizes: Story = { + render: () => ( +
+ + + +
+ ), +}; diff --git a/packages/ui/src/v2/ProboLogo/ProboLogo.tsx b/packages/ui/src/v2/ProboLogo/ProboLogo.tsx new file mode 100644 index 000000000..374354493 --- /dev/null +++ b/packages/ui/src/v2/ProboLogo/ProboLogo.tsx @@ -0,0 +1,53 @@ +// Copyright (c) 2026 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +import type { ComponentProps } from "react"; +import { useId } from "react"; + +export type ProboLogoProps = ComponentProps<"svg">; + +// The Probo brand logo (picto + wordmark). Rendered in `currentColor`; the +// picto's mark is a transparent cutout (via mask) so the underlying surface +// shows through on any background. Size it with a height class (e.g. `h-6`). +export function ProboLogo(props: ProboLogoProps) { + const maskId = useId(); + + return ( + + + + + + + + + + ); +} diff --git a/packages/ui/src/v2/typography/variants.ts b/packages/ui/src/v2/typography/variants.ts index 225b5892f..bd28b7664 100644 --- a/packages/ui/src/v2/typography/variants.ts +++ b/packages/ui/src/v2/typography/variants.ts @@ -46,8 +46,10 @@ const typographyVariants = { }, // Hue only; the resolved text step comes from the color × highContrast // compound variants below (step 11 low-contrast, step 12 high-contrast). + // `faint` is a de-emphasized neutral (sand alpha 8) for fine-print metadata. color: { neutral: "", + faint: "", gold: "", red: "", green: "", @@ -63,6 +65,9 @@ const typographyVariants = { const colorCompoundVariants = [ { color: "neutral", highContrast: false, class: "text-sand-11" }, { color: "neutral", highContrast: true, class: "text-sand-12" }, + // Faint metadata; a single de-emphasized step regardless of highContrast. + { color: "faint", highContrast: false, class: "text-sand-a8" }, + { color: "faint", highContrast: true, class: "text-sand-a8" }, { color: "gold", highContrast: false, class: "text-gold-11" }, { color: "gold", highContrast: true, class: "text-gold-12" }, { color: "red", highContrast: false, class: "text-red-11" },