From f1f4c931046ea0a4f61f8894ed382638670350f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Tue, 30 Jun 2026 14:27:41 +0200 Subject: [PATCH] Add compliance-portal Subprocessors page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the Subprocessors placeholder with the real trust-center page from the Figma design: subprocessor cards grouped by their backend category, each showing a favicon logo over a blurred backdrop, the name, the description, and the hosting regions. Country codes render through Intl.DisplayNames and the section labels/descriptions come from a new page-scoped i18n namespace. Migrate the route to the per-resource folder layout (pages/subprocessors/ with its own routes.ts, loader, page, skeleton, _components, _lib, and _locales) and drop the old flat stub. Filtering is added separately. Signed-off-by: Émile Ré --- .../compliance-portal/src/_locales/en-US.json | 3 - .../compliance-portal/src/_locales/fr-FR.json | 3 - .../pages/subprocessors/SubprocessorsPage.tsx | 74 +++++++++++++ .../subprocessors/SubprocessorsPageLoader.tsx | 34 ++++++ .../SubprocessorsPageSkeleton.tsx | 42 ++++++++ .../SubprocessorCategorySection.tsx | 55 ++++++++++ .../_components/SubprocessorListItem.tsx | 84 +++++++++++++++ .../_components/SubprocessorsEmpty.tsx | 36 +++++++ .../subprocessors/_lib/groupByCategory.ts | 40 +++++++ .../subprocessors/_lib/useCountryLabel.ts | 41 +++++++ .../pages/subprocessors/_locales/en-US.json | 101 ++++++++++++++++++ .../pages/subprocessors/_locales/fr-FR.json | 101 ++++++++++++++++++ .../routes.ts} | 18 ++-- apps/compliance-portal/src/routes.tsx | 6 +- 14 files changed, 620 insertions(+), 18 deletions(-) create mode 100644 apps/compliance-portal/src/pages/subprocessors/SubprocessorsPage.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageLoader.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageSkeleton.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorCategorySection.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorListItem.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorsEmpty.tsx create mode 100644 apps/compliance-portal/src/pages/subprocessors/_lib/groupByCategory.ts create mode 100644 apps/compliance-portal/src/pages/subprocessors/_lib/useCountryLabel.ts create mode 100644 apps/compliance-portal/src/pages/subprocessors/_locales/en-US.json create mode 100644 apps/compliance-portal/src/pages/subprocessors/_locales/fr-FR.json rename apps/compliance-portal/src/pages/{SubprocessorsPage.tsx => subprocessors/routes.ts} (66%) diff --git a/apps/compliance-portal/src/_locales/en-US.json b/apps/compliance-portal/src/_locales/en-US.json index 094711e33..03a5e7663 100644 --- a/apps/compliance-portal/src/_locales/en-US.json +++ b/apps/compliance-portal/src/_locales/en-US.json @@ -27,9 +27,6 @@ "documents": { "title": "Documents" }, - "subprocessors": { - "title": "Subprocessors" - }, "updates": { "title": "Updates", "subscribe": "Subscribe to updates" diff --git a/apps/compliance-portal/src/_locales/fr-FR.json b/apps/compliance-portal/src/_locales/fr-FR.json index d6c2f868c..4612b2f66 100644 --- a/apps/compliance-portal/src/_locales/fr-FR.json +++ b/apps/compliance-portal/src/_locales/fr-FR.json @@ -27,9 +27,6 @@ "documents": { "title": "Documents" }, - "subprocessors": { - "title": "Sous-traitants" - }, "updates": { "title": "Mises à jour", "subscribe": "S'abonner aux mises à jour" diff --git a/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPage.tsx b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPage.tsx new file mode 100644 index 000000000..faeae9940 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPage.tsx @@ -0,0 +1,74 @@ +// 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 type { PreloadedQuery } from "react-relay"; +import { graphql, usePreloadedQuery } from "react-relay"; + +import { PageHeader } from "#/components/PageHeader/PageHeader"; + +import { SubprocessorCategorySection } from "./_components/SubprocessorCategorySection"; +import type { SubprocessorNode } from "./_components/SubprocessorCategorySection"; +import { SubprocessorsEmpty } from "./_components/SubprocessorsEmpty"; +import { groupByCategory } from "./_lib/groupByCategory"; +import type { SubprocessorsPageQuery } from "./__generated__/SubprocessorsPageQuery.graphql"; + +export const subprocessorsPageQuery = graphql` + query SubprocessorsPageQuery { + currentTrustCenter @required(action: THROW) { + subprocessors(first: 250) { + totalCount + edges { + node { + id + category + ...SubprocessorListItem_subprocessor + } + } + } + } + } +`; + +interface SubprocessorsPageProps { + queryRef: PreloadedQuery; +} + +export function SubprocessorsPage({ queryRef }: SubprocessorsPageProps) { + const { t } = useTranslation("subprocessors"); + const data = usePreloadedQuery(subprocessorsPageQuery, queryRef); + const { subprocessors } = data.currentTrustCenter; + + const nodes: SubprocessorNode[] = subprocessors.edges.map(edge => edge.node); + const groups = groupByCategory(nodes, category => t(`categories.${category}.label`)); + + return ( + <> + +
+
+ {groups.length === 0 + ? + : groups.map(group => ( + + ))} +
+
+ + ); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageLoader.tsx b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageLoader.tsx new file mode 100644 index 000000000..388031ce1 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageLoader.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 { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import { SubprocessorsPage, subprocessorsPageQuery } from "./SubprocessorsPage"; +import { SubprocessorsPageSkeleton } from "./SubprocessorsPageSkeleton"; +import type { SubprocessorsPageQuery } from "./__generated__/SubprocessorsPageQuery.graphql"; + +export default function SubprocessorsPageLoader() { + const [queryRef, loadQuery] = useQueryLoader(subprocessorsPageQuery); + + useEffect(() => { + loadQuery({}); + }, [loadQuery]); + + if (!queryRef) { + return ; + } + + return ; +} diff --git a/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageSkeleton.tsx b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageSkeleton.tsx new file mode 100644 index 000000000..606191145 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/SubprocessorsPageSkeleton.tsx @@ -0,0 +1,42 @@ +// 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 { HeadingSkeleton } from "@probo/ui/src/v2/typography/HeadingSkeleton"; +import { TextSkeleton } from "@probo/ui/src/v2/typography/TextSkeleton"; + +import { HeaderBand } from "#/components/HeaderBand/HeaderBand"; + +const CARD_PLACEHOLDERS = ["a", "b", "c", "d", "e", "f"]; + +export function SubprocessorsPageSkeleton() { + return ( + <> + +
+ +
+
+
+
+ +
+ {CARD_PLACEHOLDERS.map(placeholder => ( +
+ ))} +
+
+
+ + ); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorCategorySection.tsx b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorCategorySection.tsx new file mode 100644 index 000000000..c7c7faa19 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorCategorySection.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 { Text } from "@probo/ui/src/v2/typography/Text"; +import { useTranslation } from "react-i18next"; + +import type { SubprocessorListItem_subprocessor$key } from "./__generated__/SubprocessorListItem_subprocessor.graphql"; +import { SubprocessorListItem } from "./SubprocessorListItem"; + +// A subprocessor edge node: the list-item fragment key plus the fields the page +// reads to group and key the list. +export type SubprocessorNode = SubprocessorListItem_subprocessor$key & { + readonly id: string; + readonly category: string; +}; + +interface SubprocessorCategorySectionProps { + category: string; + subprocessors: readonly SubprocessorNode[]; +} + +// One category group: a localized header (label + description) above a +// responsive grid of subprocessor cards. +export function SubprocessorCategorySection({ category, subprocessors }: SubprocessorCategorySectionProps) { + const { t } = useTranslation("subprocessors"); + + return ( +
+
+ + {t(`categories.${category}.label`)} + + + {t(`categories.${category}.description`)} + +
+
+ {subprocessors.map(subprocessor => ( + + ))} +
+
+ ); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorListItem.tsx b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorListItem.tsx new file mode 100644 index 000000000..f313edc8c --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorListItem.tsx @@ -0,0 +1,84 @@ +// 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 { BuildingsIcon, MapPinSimpleIcon } from "@phosphor-icons/react"; +import { faviconUrl } from "@probo/helpers"; +import { Card } from "@probo/ui/src/v2/Card/Card"; +import { Text } from "@probo/ui/src/v2/typography/Text"; +import { graphql, useFragment } from "react-relay"; + +import { useCountryLabel } from "../_lib/useCountryLabel"; + +import type { SubprocessorListItem_subprocessor$key } from "./__generated__/SubprocessorListItem_subprocessor.graphql"; + +const subprocessorListItemFragment = graphql` + fragment SubprocessorListItem_subprocessor on Subprocessor { + name + description + websiteUrl + countries + } +`; + +interface SubprocessorListItemProps { + subprocessorKey: SubprocessorListItem_subprocessor$key; +} + +// A single subprocessor card: a favicon logo over a blurred backdrop, the name, +// description, and the hosting regions. +export function SubprocessorListItem({ subprocessorKey }: SubprocessorListItemProps) { + const subprocessor = useFragment(subprocessorListItemFragment, subprocessorKey); + const countryLabel = useCountryLabel(); + const logoUrl = faviconUrl(subprocessor.websiteUrl); + const countries = subprocessor.countries.map(countryLabel).join(", "); + + return ( + +
+ {logoUrl != null && ( + + )} +
+
+ {logoUrl != null + ? + : } +
+
+
+ + {subprocessor.name} + + {subprocessor.description != null && subprocessor.description !== "" && ( + + {subprocessor.description} + + )} + {countries !== "" && ( +
+ + + {countries} + +
+ )} +
+ + ); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorsEmpty.tsx b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorsEmpty.tsx new file mode 100644 index 000000000..720fd4cfc --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_components/SubprocessorsEmpty.tsx @@ -0,0 +1,36 @@ +// 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 { MagnifyingGlassIcon } from "@phosphor-icons/react"; +import { Text } from "@probo/ui/src/v2/typography/Text"; +import { useTranslation } from "react-i18next"; + +// Empty state shown when the trust center lists no subprocessors. +export function SubprocessorsEmpty() { + const { t } = useTranslation("subprocessors"); + + return ( +
+ +
+ + {t("empty.title")} + + + {t("empty.description")} + +
+
+ ); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_lib/groupByCategory.ts b/apps/compliance-portal/src/pages/subprocessors/_lib/groupByCategory.ts new file mode 100644 index 000000000..84b967009 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_lib/groupByCategory.ts @@ -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. + +export interface CategoryGroup { + category: string; + nodes: T[]; +} + +// Groups subprocessor nodes into their (non-empty) categories, ordered by the +// localized category label. Presentational only — filtering happens server-side. +export function groupByCategory( + nodes: readonly T[], + getLabel: (category: string) => string, +): CategoryGroup[] { + const groups = new Map(); + + for (const node of nodes) { + const existing = groups.get(node.category); + if (existing) { + existing.push(node); + } else { + groups.set(node.category, [node]); + } + } + + return [...groups.entries()] + .map(([category, groupNodes]) => ({ category, nodes: groupNodes })) + .sort((a, b) => getLabel(a.category).localeCompare(getLabel(b.category))); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_lib/useCountryLabel.ts b/apps/compliance-portal/src/pages/subprocessors/_lib/useCountryLabel.ts new file mode 100644 index 000000000..45bf16d1f --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_lib/useCountryLabel.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. + +import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; + +// Resolves a trust-center CountryCode to a localized display name. ISO 3166 +// alpha-2 codes go through Intl.DisplayNames (locale-aware); the schema's two +// pseudo-regions (GLOBAL, EU) fall back to translated labels. +export function useCountryLabel(): (code: string) => string { + const { t, i18n } = useTranslation("subprocessors"); + + return useMemo(() => { + const display = new Intl.DisplayNames([i18n.language], { type: "region" }); + + return (code: string): string => { + if (code === "GLOBAL") { + return t("regions.global"); + } + if (code === "EU") { + return t("regions.eu"); + } + try { + return display.of(code) ?? code; + } catch { + return code; + } + }; + }, [i18n.language, t]); +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_locales/en-US.json b/apps/compliance-portal/src/pages/subprocessors/_locales/en-US.json new file mode 100644 index 000000000..ec6d7a03e --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_locales/en-US.json @@ -0,0 +1,101 @@ +{ + "title": "Subprocessors", + "empty": { + "title": "No subprocessors listed.", + "description": "This trust center has not published any subprocessors yet." + }, + "regions": { + "global": "Global", + "eu": "European Union" + }, + "categories": { + "ANALYTICS": { + "label": "Analytics", + "description": "Usage analytics and product telemetry." + }, + "CLOUD_MONITORING": { + "label": "Cloud Monitoring", + "description": "Observability, logging, and uptime monitoring." + }, + "CLOUD_PROVIDER": { + "label": "Cloud Provider", + "description": "Cloud hosting, compute, storage, and networking." + }, + "COLLABORATION": { + "label": "Collaboration", + "description": "Communication, scheduling, and teamwork tools." + }, + "CUSTOMER_SUPPORT": { + "label": "Customer Support", + "description": "Help desk, ticketing, and customer messaging." + }, + "DATA_STORAGE_AND_PROCESSING": { + "label": "Data Storage and Processing", + "description": "Databases, data warehouses, and processing pipelines." + }, + "DOCUMENT_MANAGEMENT": { + "label": "Document Management", + "description": "Document storage, signing, and e-signature." + }, + "EMPLOYEE_MANAGEMENT": { + "label": "Employee Management", + "description": "HR, payroll, and people operations." + }, + "ENGINEERING": { + "label": "Engineering", + "description": "Developer tooling and engineering platforms." + }, + "FINANCE": { + "label": "Finance", + "description": "Billing, payments, and accounting." + }, + "IDENTITY_PROVIDER": { + "label": "Identity Provider", + "description": "Authentication, SSO, and identity management." + }, + "IT": { + "label": "IT", + "description": "IT administration and device management." + }, + "MARKETING": { + "label": "Marketing", + "description": "Marketing, CRM, and audience engagement." + }, + "OFFICE_OPERATIONS": { + "label": "Office Operations", + "description": "Workplace and office operations." + }, + "OTHER": { + "label": "Other", + "description": "Other supporting services." + }, + "PASSWORD_MANAGEMENT": { + "label": "Password Management", + "description": "Credential and secret management." + }, + "PRODUCT_AND_DESIGN": { + "label": "Product and Design", + "description": "Product and design tooling." + }, + "PROFESSIONAL_SERVICES": { + "label": "Professional Services", + "description": "Consulting and professional services." + }, + "RECRUITING": { + "label": "Recruiting", + "description": "Hiring, sourcing, and applicant tracking." + }, + "SALES": { + "label": "Sales", + "description": "Sales enablement and CRM." + }, + "SECURITY": { + "label": "Security", + "description": "Security, monitoring, and secure access." + }, + "VERSION_CONTROL": { + "label": "Version Control", + "description": "Source control and code hosting." + } + } +} diff --git a/apps/compliance-portal/src/pages/subprocessors/_locales/fr-FR.json b/apps/compliance-portal/src/pages/subprocessors/_locales/fr-FR.json new file mode 100644 index 000000000..f9390a342 --- /dev/null +++ b/apps/compliance-portal/src/pages/subprocessors/_locales/fr-FR.json @@ -0,0 +1,101 @@ +{ + "title": "Sous-traitants", + "empty": { + "title": "Aucun sous-traitant répertorié.", + "description": "Ce centre de confiance n'a pas encore publié de sous-traitants." + }, + "regions": { + "global": "International", + "eu": "Union européenne" + }, + "categories": { + "ANALYTICS": { + "label": "Analytique", + "description": "Analyse d'usage et télémétrie produit." + }, + "CLOUD_MONITORING": { + "label": "Supervision cloud", + "description": "Observabilité, journalisation et supervision." + }, + "CLOUD_PROVIDER": { + "label": "Fournisseur cloud", + "description": "Hébergement, calcul, stockage et réseau cloud." + }, + "COLLABORATION": { + "label": "Collaboration", + "description": "Communication, planification et travail d'équipe." + }, + "CUSTOMER_SUPPORT": { + "label": "Support client", + "description": "Assistance, tickets et messagerie client." + }, + "DATA_STORAGE_AND_PROCESSING": { + "label": "Stockage et traitement des données", + "description": "Bases de données, entrepôts et traitements." + }, + "DOCUMENT_MANAGEMENT": { + "label": "Gestion documentaire", + "description": "Stockage, signature et e-signature de documents." + }, + "EMPLOYEE_MANAGEMENT": { + "label": "Gestion des employés", + "description": "RH, paie et opérations RH." + }, + "ENGINEERING": { + "label": "Ingénierie", + "description": "Outils et plateformes d'ingénierie." + }, + "FINANCE": { + "label": "Finance", + "description": "Facturation, paiements et comptabilité." + }, + "IDENTITY_PROVIDER": { + "label": "Fournisseur d'identité", + "description": "Authentification, SSO et gestion des identités." + }, + "IT": { + "label": "Informatique", + "description": "Administration informatique et gestion des appareils." + }, + "MARKETING": { + "label": "Marketing", + "description": "Marketing, CRM et engagement de l'audience." + }, + "OFFICE_OPERATIONS": { + "label": "Opérations de bureau", + "description": "Opérations et gestion des bureaux." + }, + "OTHER": { + "label": "Autre", + "description": "Autres services de support." + }, + "PASSWORD_MANAGEMENT": { + "label": "Gestion des mots de passe", + "description": "Gestion des identifiants et des secrets." + }, + "PRODUCT_AND_DESIGN": { + "label": "Produit et design", + "description": "Outils de produit et de design." + }, + "PROFESSIONAL_SERVICES": { + "label": "Services professionnels", + "description": "Conseil et services professionnels." + }, + "RECRUITING": { + "label": "Recrutement", + "description": "Recrutement, sourcing et suivi des candidatures." + }, + "SALES": { + "label": "Ventes", + "description": "Aide à la vente et CRM." + }, + "SECURITY": { + "label": "Sécurité", + "description": "Sécurité, supervision et accès sécurisé." + }, + "VERSION_CONTROL": { + "label": "Gestion de versions", + "description": "Gestion de sources et hébergement de code." + } + } +} diff --git a/apps/compliance-portal/src/pages/SubprocessorsPage.tsx b/apps/compliance-portal/src/pages/subprocessors/routes.ts similarity index 66% rename from apps/compliance-portal/src/pages/SubprocessorsPage.tsx rename to apps/compliance-portal/src/pages/subprocessors/routes.ts index 869b82609..17df8a74c 100644 --- a/apps/compliance-portal/src/pages/SubprocessorsPage.tsx +++ b/apps/compliance-portal/src/pages/subprocessors/routes.ts @@ -12,13 +12,15 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -import { useTranslation } from "react-i18next"; +import { lazy } from "@probo/react-lazy"; +import type { AppRoute } from "@probo/routes"; -import { PageHeader } from "#/components/PageHeader/PageHeader"; +import { SubprocessorsPageSkeleton } from "./SubprocessorsPageSkeleton"; -// Toolbar (category/region filters, search, Download CSV) and the subprocessor -// count are deferred until the v2 Select/TextField components exist. -export default function SubprocessorsPage() { - const { t } = useTranslation(); - return ; -} +export const subprocessorRoutes = [ + { + path: "subprocessors", + Fallback: SubprocessorsPageSkeleton, + Component: lazy(() => import("./SubprocessorsPageLoader")), + }, +] satisfies AppRoute[]; diff --git a/apps/compliance-portal/src/routes.tsx b/apps/compliance-portal/src/routes.tsx index 996602e12..75218ca53 100644 --- a/apps/compliance-portal/src/routes.tsx +++ b/apps/compliance-portal/src/routes.tsx @@ -19,6 +19,7 @@ import { createBrowserRouter } from "react-router"; import { getPathPrefix } from "#/lib/http/pathPrefix"; import { HomePageSkeleton } from "#/pages/HomePageSkeleton"; import { MainLayoutSkeleton } from "#/pages/MainLayoutSkeleton"; +import { subprocessorRoutes } from "#/pages/subprocessors/routes"; const routes = [ { @@ -35,10 +36,7 @@ const routes = [ path: "documents", Component: lazy(() => import("#/pages/DocumentsPage")), }, - { - path: "subprocessors", - Component: lazy(() => import("#/pages/SubprocessorsPage")), - }, + ...subprocessorRoutes, { path: "updates", Component: lazy(() => import("#/pages/UpdatesPage")),