Add CompliancePageLayout

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-01-28 10:15:14 +04:00
parent d3c2f0bf7d
commit 3d25343508
6 changed files with 305 additions and 5 deletions

View File

@@ -0,0 +1,164 @@
/**
* @generated SignedSource<<6b8fb96ca82a23677b5a8d4b22b1685f>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type CompliancePageLayoutQuery$variables = {
organizationId: string;
};
export type CompliancePageLayoutQuery$data = {
readonly organization: {
readonly __typename: "Organization";
readonly trustCenter: {
readonly active: boolean;
} | null | undefined;
} | {
// This will never be '%other', but we need some
// value in case none of the concrete values match.
readonly __typename: "%other";
};
};
export type CompliancePageLayoutQuery = {
response: CompliancePageLayoutQuery$data;
variables: CompliancePageLayoutQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageLayoutQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageLayoutQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/)
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
},
(v4/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "dca289c0f586e539bfee9c68ccd14e37",
"id": null,
"metadata": {},
"name": "CompliancePageLayoutQuery",
"operationKind": "query",
"text": "query CompliancePageLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n trustCenter {\n active\n id\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "0134cd2bfcc281c33161c3693172af0c";
export default node;

View File

@@ -198,11 +198,18 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) {
/>
)}
{organization.canGetTrustCenter && (
<SidebarItem
label={__("Trust Center")}
icon={IconShield}
to={`${prefix}/trust-center`}
/>
<>
<SidebarItem
label={__("Compliance Page")}
icon={IconShield}
to={`${prefix}/compliance-page`}
/>
<SidebarItem
label={__("Trust Center")}
icon={IconShield}
to={`${prefix}/trust-center`}
/>
</>
)}
{organization.canUpdateOrganization && (
<SidebarItem

View File

@@ -0,0 +1,84 @@
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Badge, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui";
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { Outlet } from "react-router";
import { graphql } from "relay-runtime";
import type { CompliancePageLayoutQuery } from "#/__generated__/core/CompliancePageLayoutQuery.graphql";
import { useOrganizationId } from "#/hooks/useOrganizationId";
export const compliancePageLayoutQuery = graphql`
query CompliancePageLayoutQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
__typename
... on Organization {
trustCenter {
active
}
}
}
}
`;
export function CompliancePageLayout(props: { queryRef: PreloadedQuery<CompliancePageLayoutQuery> }) {
const { queryRef } = props;
const organizationId = useOrganizationId();
const { __ } = useTranslate();
usePageTitle(__("Compliance Page"));
const { organization } = usePreloadedQuery<CompliancePageLayoutQuery>(compliancePageLayoutQuery, queryRef);
if (organization.__typename !== "Organization") {
throw new Error("invalid type for node");
}
return (
<div className="space-y-6">
<PageHeader
title={__("Compliance Page")}
description={__(
"Configure your public compliance page to showcase your security and compliance posture.",
)}
>
<Badge variant={organization.trustCenter?.active ? "success" : "danger"}>
{organization.trustCenter?.active ? __("Active") : __("Inactive")}
</Badge>
</PageHeader>
<Tabs>
<TabLink to={`/organizations/${organizationId}/compliance-page`} end>
<IconSettingsGear2 className="size-4" />
{__("Overview")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/trust-by`}>
<IconCheckmark1 className="size-4" />
{__("Trusted by")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/audits`}>
<IconMedal className="size-4" />
{__("Audits")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/documents`}>
<IconPageTextLine className="size-4" />
{__("Documents")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/files`}>
<IconFolder2 className="size-4" />
{__("Files")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/vendors`}>
<IconStore className="size-4" />
{__("Vendors")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/access`}>
<IconPeopleAdd className="size-4" />
{__("Access")}
</TabLink>
</Tabs>
<Outlet />
</div>
);
}

View File

@@ -0,0 +1,32 @@
import { useEffect } from "react";
import { useQueryLoader } from "react-relay";
import type { CompliancePageLayoutQuery } from "#/__generated__/core/CompliancePageLayoutQuery.graphql";
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
import { useOrganizationId } from "#/hooks/useOrganizationId";
import { CoreRelayProvider } from "#/providers/CoreRelayProvider";
import { CompliancePageLayout, compliancePageLayoutQuery } from "./CompliancePageLayout";
function CompliancePageLayoutQueryLoader() {
const organizationId = useOrganizationId();
const [queryRef, loadQuery] = useQueryLoader<CompliancePageLayoutQuery>(compliancePageLayoutQuery);
useEffect(() => {
if (!queryRef) {
loadQuery({ organizationId });
}
});
if (!queryRef) return <PageSkeleton />;
return <CompliancePageLayout queryRef={queryRef} />;
}
export default function CompliancePageLayoutLoader() {
return (
<CoreRelayProvider>
<CompliancePageLayoutQueryLoader />
</CoreRelayProvider>
);
}

View File

@@ -0,0 +1,11 @@
import { lazy } from "@probo/react-lazy";
import { PageSkeleton } from "#/components/skeletons/PageSkeleton";
export const compliancePageRoutes = [
{
path: "compliance-page",
Fallback: PageSkeleton,
Component: lazy(() => import("#/pages/organizations/compliance-page/CompliancePageLayoutLoader")),
},
];

View File

@@ -16,6 +16,7 @@ import {
import { PageError } from "./components/PageError";
import { PageSkeleton } from "./components/skeletons/PageSkeleton";
import { ViewerLayoutLoading } from "./pages/iam/memberships/ViewerLayoutLoading";
import { compliancePageRoutes } from "./pages/organizations/compliance-page/routes";
import { CurrentUser } from "./providers/CurrentUser";
import { assetRoutes } from "./routes/assetRoutes";
import { auditRoutes } from "./routes/auditRoutes";
@@ -248,6 +249,7 @@ const routes = [
...rightsRequestRoutes,
...processingActivityRoutes,
...statesOfApplicabilityRoutes,
...compliancePageRoutes,
...trustCenterRoutes,
...snapshotsRoutes,
{