Add compliance-portal Subprocessors page
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é <emile@probo.com>
This commit is contained in:
@@ -27,9 +27,6 @@
|
||||
"documents": {
|
||||
"title": "Documents"
|
||||
},
|
||||
"subprocessors": {
|
||||
"title": "Subprocessors"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Updates",
|
||||
"subscribe": "Subscribe to updates"
|
||||
|
||||
@@ -27,9 +27,6 @@
|
||||
"documents": {
|
||||
"title": "Documents"
|
||||
},
|
||||
"subprocessors": {
|
||||
"title": "Sous-traitants"
|
||||
},
|
||||
"updates": {
|
||||
"title": "Mises à jour",
|
||||
"subscribe": "S'abonner aux mises à jour"
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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<SubprocessorsPageQuery>;
|
||||
}
|
||||
|
||||
export function SubprocessorsPage({ queryRef }: SubprocessorsPageProps) {
|
||||
const { t } = useTranslation("subprocessors");
|
||||
const data = usePreloadedQuery<SubprocessorsPageQuery>(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 (
|
||||
<>
|
||||
<PageHeader title={t("title")} count={subprocessors.totalCount} />
|
||||
<div className="flex w-full flex-col items-center px-8 py-8">
|
||||
<div className="flex w-full max-w-5xl flex-col gap-8">
|
||||
{groups.length === 0
|
||||
? <SubprocessorsEmpty />
|
||||
: groups.map(group => (
|
||||
<SubprocessorCategorySection
|
||||
key={group.category}
|
||||
category={group.category}
|
||||
subprocessors={group.nodes}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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>(subprocessorsPageQuery);
|
||||
|
||||
useEffect(() => {
|
||||
loadQuery({});
|
||||
}, [loadQuery]);
|
||||
|
||||
if (!queryRef) {
|
||||
return <SubprocessorsPageSkeleton />;
|
||||
}
|
||||
|
||||
return <SubprocessorsPage queryRef={queryRef} />;
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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 (
|
||||
<>
|
||||
<HeaderBand>
|
||||
<div className="flex w-full flex-col gap-2">
|
||||
<HeadingSkeleton size={7} className="w-64" />
|
||||
</div>
|
||||
</HeaderBand>
|
||||
<div className="flex w-full flex-col items-center px-8 py-8">
|
||||
<div className="flex w-full max-w-5xl flex-col gap-4">
|
||||
<TextSkeleton size={3} className="w-48" />
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{CARD_PLACEHOLDERS.map(placeholder => (
|
||||
<div key={placeholder} className="h-56 animate-pulse rounded-5 bg-sand-3" />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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 (
|
||||
<section className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-1">
|
||||
<Text size={3} weight="medium" color="neutral" highContrast>
|
||||
{t(`categories.${category}.label`)}
|
||||
</Text>
|
||||
<Text size={2} color="neutral">
|
||||
{t(`categories.${category}.description`)}
|
||||
</Text>
|
||||
</div>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
|
||||
{subprocessors.map(subprocessor => (
|
||||
<SubprocessorListItem key={subprocessor.id} subprocessorKey={subprocessor} />
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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 (
|
||||
<Card variant="soft" size={3} padding="none">
|
||||
<div className="relative flex items-center overflow-hidden p-8">
|
||||
{logoUrl != null && (
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt=""
|
||||
aria-hidden
|
||||
className="pointer-events-none absolute inset-0 size-full scale-150 object-cover opacity-10 blur-lg"
|
||||
/>
|
||||
)}
|
||||
<div className="pointer-events-none absolute inset-0 bg-linear-to-b from-sand-1/0 to-sand-1" />
|
||||
<div className="relative z-10 flex size-10 items-center justify-center overflow-hidden rounded-2 bg-sand-1">
|
||||
{logoUrl != null
|
||||
? <img src={logoUrl} alt="" className="size-full object-cover" />
|
||||
: <BuildingsIcon size={24} weight="duotone" className="text-sand-9" />}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-2 px-8 pb-8">
|
||||
<Text size={4} weight="medium" color="neutral" highContrast>
|
||||
{subprocessor.name}
|
||||
</Text>
|
||||
{subprocessor.description != null && subprocessor.description !== "" && (
|
||||
<Text size={2} color="neutral">
|
||||
{subprocessor.description}
|
||||
</Text>
|
||||
)}
|
||||
{countries !== "" && (
|
||||
<div className="flex items-start gap-1">
|
||||
<MapPinSimpleIcon className="mt-0.5 size-4 shrink-0 text-gold-11" />
|
||||
<Text size={1} color="gold">
|
||||
{countries}
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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 (
|
||||
<div className="flex flex-col items-center gap-5 py-8 text-center">
|
||||
<MagnifyingGlassIcon className="size-6 text-sand-a8" />
|
||||
<div className="flex max-w-xs flex-col items-center gap-2">
|
||||
<Text size={2} weight="medium" color="faint">
|
||||
{t("empty.title")}
|
||||
</Text>
|
||||
<Text size={2} color="faint">
|
||||
{t("empty.description")}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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<T> {
|
||||
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<T extends { readonly category: string }>(
|
||||
nodes: readonly T[],
|
||||
getLabel: (category: string) => string,
|
||||
): CategoryGroup<T>[] {
|
||||
const groups = new Map<string, T[]>();
|
||||
|
||||
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)));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
||||
//
|
||||
// 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]);
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 <PageHeader title={t("subprocessors.title")} />;
|
||||
}
|
||||
export const subprocessorRoutes = [
|
||||
{
|
||||
path: "subprocessors",
|
||||
Fallback: SubprocessorsPageSkeleton,
|
||||
Component: lazy(() => import("./SubprocessorsPageLoader")),
|
||||
},
|
||||
] satisfies AppRoute[];
|
||||
@@ -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")),
|
||||
|
||||
Reference in New Issue
Block a user