Add trust center front

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-07-28 17:24:16 +02:00
parent 2ac8f31492
commit cb3d79502d
72 changed files with 18712 additions and 82 deletions

View File

@@ -0,0 +1,132 @@
import {
Card,
Tr,
Td,
Table,
Thead,
Tbody,
Th,
Button,
IconArrowDown,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { sprintf } from "@probo/helpers";
import { FrameworkLogo } from "/components/FrameworkLogo";
type Audit = {
id: string;
framework: {
name: string;
};
validFrom: string;
validUntil: string | null;
state: string;
createdAt: string;
report: {
id: string;
filename: string;
downloadUrl: string | null;
} | null;
reportUrl: string | null;
};
type Props = {
audits: Audit[];
organizationName: string;
isAuthenticated: boolean;
};
export function PublicTrustCenterAudits({ audits, organizationName, isAuthenticated }: Props) {
const { __ } = useTranslate();
if (audits.length === 0) {
return (
<Card padded>
<div className="text-center py-8">
<h2 className="text-xl font-semibold text-txt-primary mb-2">
{__("Compliance")}
</h2>
<p className="text-txt-secondary">
{__("No compliance reports are currently available.")}
</p>
</div>
</Card>
);
}
return (
<Card padded className="space-y-4">
<div>
<h2 className="text-xl font-semibold text-txt-primary">
{__("Compliance")}
</h2>
<p className="text-sm text-txt-secondary mt-1">
{sprintf(__("%s is compliant with the following frameworks"), organizationName)}
</p>
</div>
<Table>
<Thead>
<Tr>
<Th>{__("Framework")}</Th>
<Th>{__("Report")}</Th>
</Tr>
</Thead>
<Tbody>
{audits.map((audit) => {
const hasReport = audit.report || audit.reportUrl;
const downloadUrl = audit.report?.downloadUrl || audit.reportUrl;
const reportName = audit.report?.filename || __("Compliance Report");
return (
<Tr key={audit.id}>
<Td>
<div className="flex items-center gap-3">
<div className="flex-shrink-0">
<div className="w-8 h-8 [&>img]:w-8 [&>img]:h-8 [&>div]:w-8 [&>div]:h-8">
<FrameworkLogo name={audit.framework.name} />
</div>
</div>
<div className="font-medium">
{audit.framework.name}
</div>
</div>
</Td>
<Td>
{!hasReport ? (
<span className="text-txt-tertiary text-sm">
{__("No report")}
</span>
) : !isAuthenticated ? (
<span className="text-txt-tertiary text-sm">
{__("Not available")}
</span>
) : downloadUrl ? (
<Button
variant="secondary"
icon={IconArrowDown}
onClick={() => {
const link = document.createElement('a');
link.href = downloadUrl;
link.download = reportName;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}}
>
{__("Download")}
</Button>
) : (
<span className="text-txt-tertiary text-sm">
{__("Not available")}
</span>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</Card>
);
}

View File

@@ -0,0 +1,146 @@
import {
Card,
Tr,
Td,
Table,
Thead,
Tbody,
Th,
DocumentTypeBadge,
Button,
IconArrowDown,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { graphql } from "relay-runtime";
import { useMutation } from "react-relay";
import type { PublicTrustCenterDocumentsExportPDFMutation } from "./__generated__/PublicTrustCenterDocumentsExportPDFMutation.graphql";
const exportDocumentVersionPDFMutation = graphql`
mutation PublicTrustCenterDocumentsExportPDFMutation(
$input: ExportDocumentVersionPDFInput!
) {
exportDocumentVersionPDF(input: $input) {
data
}
}
`;
type Document = {
id: string;
title: string;
documentType: string;
versions: {
edges: Array<{
node: {
id: string;
status: string;
};
}>;
};
};
type Props = {
documents: Document[];
isAuthenticated: boolean;
};
export function PublicTrustCenterDocuments({ documents, isAuthenticated }: Props) {
const { __ } = useTranslate();
const [exportDocumentVersionPDF] = useMutation<PublicTrustCenterDocumentsExportPDFMutation>(exportDocumentVersionPDFMutation);
const handleDownload = (document: Document) => {
const latestVersion = document.versions.edges[0]?.node;
if (!latestVersion) return;
exportDocumentVersionPDF({
variables: {
input: { documentVersionId: latestVersion.id },
},
onCompleted: (data) => {
if (data.exportDocumentVersionPDF?.data) {
const link = window.document.createElement("a");
link.href = data.exportDocumentVersionPDF.data;
link.download = `${document.title}.pdf`;
window.document.body.appendChild(link);
link.click();
window.document.body.removeChild(link);
}
},
});
};
if (documents.length === 0) {
return (
<Card padded>
<div className="text-center py-8">
<h2 className="text-xl font-semibold text-txt-primary mb-2">
{__("Documents")}
</h2>
<p className="text-txt-secondary">
{__("No documents are currently available.")}
</p>
</div>
</Card>
);
}
return (
<Card padded className="space-y-4">
<div>
<h2 className="text-xl font-semibold text-txt-primary">
{__("Documents")}
</h2>
<p className="text-sm text-txt-secondary mt-1">
{__("Security and compliance documentation")}
</p>
</div>
<Table>
<Thead>
<Tr>
<Th className="w-1/2">{__("Document")}</Th>
<Th className="w-1/4">{__("Type")}</Th>
<Th className="w-1/4">{__("Download")}</Th>
</Tr>
</Thead>
<Tbody>
{documents.map((document) => {
const latestVersion = document.versions.edges[0]?.node;
return (
<Tr key={document.id}>
<Td>
<div className="font-medium">
{document.title}
</div>
</Td>
<Td>
<DocumentTypeBadge type={document.documentType} />
</Td>
<Td>
{!latestVersion ? (
<span className="text-txt-tertiary text-sm">
{__("No version available")}
</span>
) : !isAuthenticated ? (
<span className="text-txt-tertiary text-sm">
{__("Not available")}
</span>
) : (
<Button
variant="secondary"
icon={IconArrowDown}
onClick={() => handleDownload(document)}
>
{__("Download")}
</Button>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</Card>
);
}

View File

@@ -0,0 +1,117 @@
import {
Card,
Tr,
Td,
Table,
Thead,
Tbody,
Th,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { faviconUrl, sprintf } from "@probo/helpers";
type Vendor = {
id: string;
name: string;
category: string;
description: string | null;
createdAt: string;
privacyPolicyUrl?: string | null;
websiteUrl?: string | null;
};
type Props = {
vendors: Vendor[];
organizationName: string;
};
export function PublicTrustCenterVendors({ vendors, organizationName }: Props) {
const { __ } = useTranslate();
if (vendors.length === 0) {
return (
<Card padded>
<div className="text-center py-8">
<h2 className="text-xl font-semibold text-txt-primary mb-2">
{__("Vendors")}
</h2>
<p className="text-txt-secondary">
{__("No vendor information is currently available.")}
</p>
</div>
</Card>
);
}
return (
<Card padded className="space-y-4">
<div>
<h2 className="text-xl font-semibold text-txt-primary">
{__("Vendors")}
</h2>
<p className="text-sm text-txt-secondary mt-1">
{sprintf(__("Third-party vendors and service providers %s work with"), organizationName)}
</p>
</div>
<Table>
<Thead>
<Tr>
<Th>{__("Company")}</Th>
<Th>{__("Website")}</Th>
</Tr>
</Thead>
<Tbody>
{vendors.map((vendor) => {
const url = vendor.privacyPolicyUrl || vendor.websiteUrl;
const logo = faviconUrl(vendor.websiteUrl);
const getCleanUrl = (url: string) => {
try {
const parsedUrl = new URL(url);
return parsedUrl.hostname + parsedUrl.pathname + parsedUrl.search;
} catch {
return url.replace(/^https?:\/\//, '');
}
};
return (
<Tr key={vendor.id}>
<Td>
<div className="flex items-center space-x-3">
{logo && (
<img
src={logo}
alt={`${vendor.name} logo`}
className="w-8 h-8 object-contain rounded-full"
/>
)}
<div className="font-medium">
{vendor.name}
</div>
</div>
</Td>
<Td>
{url ? (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 underline"
>
{getCleanUrl(url)}
</a>
) : (
<span className="text-txt-secondary text-sm">
{__("No website available")}
</span>
)}
</Td>
</Tr>
);
})}
</Tbody>
</Table>
</Card>
);
}

View File

@@ -0,0 +1,92 @@
/**
* @generated SignedSource<<24fd0dcf15c98ad3d20762e4ccc83a1b>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type ExportDocumentVersionPDFInput = {
documentVersionId: string;
};
export type PublicTrustCenterDocumentsExportPDFMutation$variables = {
input: ExportDocumentVersionPDFInput;
};
export type PublicTrustCenterDocumentsExportPDFMutation$data = {
readonly exportDocumentVersionPDF: {
readonly data: string;
};
};
export type PublicTrustCenterDocumentsExportPDFMutation = {
response: PublicTrustCenterDocumentsExportPDFMutation$data;
variables: PublicTrustCenterDocumentsExportPDFMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "ExportDocumentVersionPDFPayload",
"kind": "LinkedField",
"name": "exportDocumentVersionPDF",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "data",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "PublicTrustCenterDocumentsExportPDFMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "PublicTrustCenterDocumentsExportPDFMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "b5d33d4c301cfa811bb74d52f61f52e8",
"id": null,
"metadata": {},
"name": "PublicTrustCenterDocumentsExportPDFMutation",
"operationKind": "mutation",
"text": "mutation PublicTrustCenterDocumentsExportPDFMutation(\n $input: ExportDocumentVersionPDFInput!\n) {\n exportDocumentVersionPDF(input: $input) {\n data\n }\n}\n"
}
};
})();
(node as any).hash = "b6a173895aea2450ad4ac1a4ec6aeb4e";
export default node;