Remove unused trust center code
Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1,415 +0,0 @@
|
||||
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||
import { GraphQLError } from "graphql";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { type CountryCode } from "@probo/helpers";
|
||||
|
||||
export interface TrustCenterDocument {
|
||||
id: string;
|
||||
title: string;
|
||||
documentType: string;
|
||||
}
|
||||
|
||||
export interface TrustCenterAudit {
|
||||
id: string;
|
||||
framework: {
|
||||
name: string;
|
||||
};
|
||||
report: {
|
||||
id: string;
|
||||
filename: string;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export interface TrustCenterVendor {
|
||||
id: string;
|
||||
name: string;
|
||||
category: string;
|
||||
privacyPolicyUrl?: string | null;
|
||||
websiteUrl?: string | null;
|
||||
countries: CountryCode[];
|
||||
}
|
||||
|
||||
interface TrustCenterQueryData {
|
||||
trustCenterBySlug: {
|
||||
id: string;
|
||||
active: boolean;
|
||||
slug: string;
|
||||
isUserAuthenticated: boolean;
|
||||
hasAcceptedNonDisclosureAgreement: boolean;
|
||||
ndaFileName: string | null;
|
||||
ndaFileUrl: string | null;
|
||||
organization: {
|
||||
id: string;
|
||||
name: string;
|
||||
logoUrl: string | null;
|
||||
};
|
||||
documents: {
|
||||
edges: Array<{
|
||||
node: TrustCenterDocument;
|
||||
}>;
|
||||
};
|
||||
audits: {
|
||||
edges: Array<{
|
||||
node: TrustCenterAudit;
|
||||
}>;
|
||||
};
|
||||
vendors: {
|
||||
edges: Array<{
|
||||
node: TrustCenterVendor;
|
||||
}>;
|
||||
};
|
||||
} | null;
|
||||
}
|
||||
|
||||
interface GraphQLResponse<T = unknown> {
|
||||
data?: T;
|
||||
errors?: GraphQLError[];
|
||||
}
|
||||
|
||||
interface ExportDocumentPDFData {
|
||||
exportDocumentPDF: {
|
||||
data: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ExportReportPDFData {
|
||||
exportReportPDF: {
|
||||
data: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateTrustCenterAccessData {
|
||||
createTrustCenterAccess: {
|
||||
trustCenterAccess: {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface TrustCenterQueryVariables {
|
||||
slug: string;
|
||||
}
|
||||
|
||||
interface ExportDocumentPDFVariables {
|
||||
input: {
|
||||
documentId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface CreateTrustCenterAccessVariables {
|
||||
input: {
|
||||
trustCenterId: string;
|
||||
email: string;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface AcceptNonDisclosureAgreementData {
|
||||
acceptNonDisclosureAgreement: {
|
||||
success: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
interface AcceptNonDisclosureAgreementVariables {
|
||||
input: {
|
||||
trustCenterId: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ExportReportPDFVariables {
|
||||
input: {
|
||||
reportId: string;
|
||||
};
|
||||
}
|
||||
|
||||
type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | ExportReportPDFVariables | CreateTrustCenterAccessVariables | AcceptNonDisclosureAgreementVariables | Record<string, never>;
|
||||
|
||||
async function trustCenterGraphQLRequest<T = unknown>(
|
||||
operationName: string,
|
||||
query: string,
|
||||
variables: GraphQLVariables = {}
|
||||
): Promise<GraphQLResponse<T>> {
|
||||
const response = await fetch(buildEndpoint("/api/trust/v1/graphql"), {
|
||||
method: "POST",
|
||||
credentials: "include",
|
||||
headers: {
|
||||
Accept: "application/graphql-response+json; charset=utf-8, application/json; charset=utf-8",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
operationName,
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
return result;
|
||||
}
|
||||
|
||||
function isCriticalError(error: GraphQLError): boolean {
|
||||
const message = error.message?.toLowerCase() || '';
|
||||
|
||||
if (
|
||||
message.includes('access denied') ||
|
||||
message.includes('authentication required') ||
|
||||
message.includes('user has not accepted nda') ||
|
||||
message.includes('no nda file found')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const TRUST_CENTER_QUERY = `
|
||||
query PublicTrustCenterPageQuery($slug: String!) {
|
||||
trustCenterBySlug(slug: $slug) {
|
||||
id
|
||||
active
|
||||
slug
|
||||
isUserAuthenticated
|
||||
hasAcceptedNonDisclosureAgreement
|
||||
ndaFileName
|
||||
ndaFileUrl
|
||||
organization {
|
||||
id
|
||||
name
|
||||
logoUrl
|
||||
description
|
||||
websiteUrl
|
||||
email
|
||||
headquarterAddress
|
||||
}
|
||||
documents(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
title
|
||||
documentType
|
||||
}
|
||||
}
|
||||
}
|
||||
audits(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
framework {
|
||||
name
|
||||
}
|
||||
report {
|
||||
id
|
||||
filename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
vendors(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
category
|
||||
websiteUrl
|
||||
privacyPolicyUrl
|
||||
countries
|
||||
}
|
||||
}
|
||||
}
|
||||
references(first: 100) {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
name
|
||||
description
|
||||
websiteUrl
|
||||
logoUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const EXPORT_DOCUMENT_PDF_MUTATION = `
|
||||
mutation PublicTrustCenterDocumentsExportPDFMutation(
|
||||
$input: ExportDocumentPDFInput!
|
||||
) {
|
||||
exportDocumentPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const EXPORT_REPORT_PDF_MUTATION = `
|
||||
mutation PublicTrustCenterAuditsExportReportPDFMutation(
|
||||
$input: ExportReportPDFInput!
|
||||
) {
|
||||
exportReportPDF(input: $input) {
|
||||
data
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const CREATE_TRUST_CENTER_ACCESS_MUTATION = `
|
||||
mutation PublicTrustCenterAccessRequestDialogMutation(
|
||||
$input: CreateTrustCenterAccessInput!
|
||||
) {
|
||||
createTrustCenterAccess(input: $input) {
|
||||
trustCenterAccess {
|
||||
id
|
||||
email
|
||||
name
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const ACCEPT_NDA_MUTATION = `
|
||||
mutation AcceptNonDisclosureAgreementMutation(
|
||||
$input: AcceptNonDisclosureAgreementInput!
|
||||
) {
|
||||
acceptNonDisclosureAgreement(input: $input) {
|
||||
success
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export function useTrustCenterQuery(slug: string) {
|
||||
return useQuery<TrustCenterQueryData>({
|
||||
queryKey: ["trust-center", slug],
|
||||
queryFn: async () => {
|
||||
const result = await trustCenterGraphQLRequest<TrustCenterQueryData>(
|
||||
"PublicTrustCenterPageQuery",
|
||||
TRUST_CENTER_QUERY,
|
||||
{ slug }
|
||||
);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
const criticalErrors = result.errors.filter(isCriticalError);
|
||||
|
||||
if (criticalErrors.length > 0) {
|
||||
throw new Error(
|
||||
`GraphQL error: ${criticalErrors.map((e) => e.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from GraphQL query");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
enabled: !!slug,
|
||||
staleTime: 5 * 60 * 1000, // 5 minutes
|
||||
retry: (failureCount, error) => {
|
||||
if (error.message.includes("UNAUTHENTICATED") || error.message.includes("401")) {
|
||||
return false;
|
||||
}
|
||||
return failureCount < 3;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useExportDocumentPDF() {
|
||||
return useMutation<ExportDocumentPDFData, Error, string>({
|
||||
mutationFn: async (documentId: string) => {
|
||||
const result = await trustCenterGraphQLRequest<ExportDocumentPDFData>(
|
||||
"PublicTrustCenterDocumentsExportPDFMutation",
|
||||
EXPORT_DOCUMENT_PDF_MUTATION,
|
||||
{ input: { documentId } }
|
||||
);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
throw new Error(
|
||||
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from mutation");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useExportReportPDF() {
|
||||
return useMutation<ExportReportPDFData, Error, string>({
|
||||
mutationFn: async (reportId: string) => {
|
||||
const result = await trustCenterGraphQLRequest<ExportReportPDFData>(
|
||||
"PublicTrustCenterAuditsExportReportPDFMutation",
|
||||
EXPORT_REPORT_PDF_MUTATION,
|
||||
{ input: { reportId } }
|
||||
);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
throw new Error(
|
||||
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from mutation");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useCreateTrustCenterAccess() {
|
||||
return useMutation<CreateTrustCenterAccessData, Error, { trustCenterId: string; email: string; name: string }>({
|
||||
mutationFn: async (input: { trustCenterId: string; email: string; name: string }) => {
|
||||
const result = await trustCenterGraphQLRequest<CreateTrustCenterAccessData>(
|
||||
"PublicTrustCenterAccessRequestDialogMutation",
|
||||
CREATE_TRUST_CENTER_ACCESS_MUTATION,
|
||||
{ input }
|
||||
);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
throw new Error(
|
||||
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from mutation");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useAcceptNonDisclosureAgreement() {
|
||||
return useMutation<AcceptNonDisclosureAgreementData, Error, { trustCenterId: string }>({
|
||||
mutationFn: async (input: { trustCenterId: string }) => {
|
||||
const result = await trustCenterGraphQLRequest<AcceptNonDisclosureAgreementData>(
|
||||
"AcceptNonDisclosureAgreementMutation",
|
||||
ACCEPT_NDA_MUTATION,
|
||||
{ input }
|
||||
);
|
||||
|
||||
if (result.errors && result.errors.length > 0) {
|
||||
throw new Error(
|
||||
`GraphQL error: ${result.errors.map((e) => e.message).join(", ")}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!result.data) {
|
||||
throw new Error("No data returned from mutation");
|
||||
}
|
||||
|
||||
return result.data;
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useParams, useNavigate, useSearchParams } from "react-router";
|
||||
import { useState, useEffect } from "react";
|
||||
import { PageSkeleton } from "/components/skeletons/PageSkeleton";
|
||||
import { PageError } from "/components/PageError";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { IconClock, IconWarning } from "@probo/ui";
|
||||
|
||||
function TokenErrorPage({ error }: { error: string }) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const isExpiredToken = error.toLowerCase().includes('expired');
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-level-0 flex items-center justify-center p-4">
|
||||
<div className="max-w-md w-full text-center space-y-6">
|
||||
<div className="space-y-4">
|
||||
{isExpiredToken ? (
|
||||
<div className="space-y-3">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-amber-100 rounded-full">
|
||||
<IconClock size={32} className="text-amber-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-semibold text-txt-primary">
|
||||
{__("Access Link Expired")}
|
||||
</h1>
|
||||
<p className="text-txt-secondary">
|
||||
{__("This access link has expired. Trust center access links are valid for 7 days for security reasons.")}
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="space-y-3">
|
||||
<div className="inline-flex items-center justify-center w-16 h-16 bg-red-100 rounded-full">
|
||||
<IconWarning size={32} className="text-red-600" />
|
||||
</div>
|
||||
<h1 className="text-2xl font-semibold text-txt-primary">
|
||||
{__("Invalid Access Link")}
|
||||
</h1>
|
||||
<p className="text-txt-secondary">
|
||||
{__("This access link is not valid. It may have been revoked or the link might be incorrect.")}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="bg-level-1 border border-border-low rounded-lg p-4 space-y-3">
|
||||
<h3 className="font-medium text-txt-primary">{__("What can you do?")}</h3>
|
||||
<ul className="text-sm text-txt-secondary space-y-2 text-left">
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary-600 mt-1">•</span>
|
||||
<span>{__("Contact the person who sent you this link to request a new access invitation")}</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary-600 mt-1">•</span>
|
||||
<span>{__("Check if you received a newer email with an updated access link")}</span>
|
||||
</li>
|
||||
<li className="flex items-start gap-2">
|
||||
<span className="text-primary-600 mt-1">•</span>
|
||||
<span>{__("Verify that you copied the entire link correctly from the email")}</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function TrustCenterAccessPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const [searchParams] = useSearchParams();
|
||||
const token = searchParams.get('token');
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!slug) {
|
||||
setError(__("Invalid trust center"));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
setError(__("Invalid or missing access token"));
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(buildEndpoint('/api/trust/v1/auth/authenticate'), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
body: JSON.stringify({ token }),
|
||||
})
|
||||
.then(async response => {
|
||||
if (!response.ok) {
|
||||
try {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
|
||||
} catch {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
navigate(`/trust/${slug}`);
|
||||
} else {
|
||||
setError(data.message || __("Authentication failed"));
|
||||
setLoading(false);
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
setError(error.message || __("Authentication failed"));
|
||||
setLoading(false);
|
||||
});
|
||||
}, [slug, token, __, navigate]);
|
||||
|
||||
if (loading) {
|
||||
return <PageSkeleton />;
|
||||
}
|
||||
|
||||
if (error) {
|
||||
const isTokenError = error.toLowerCase().includes('token') ||
|
||||
error.toLowerCase().includes('expired') ||
|
||||
error.toLowerCase().includes('invalid') ||
|
||||
error.toLowerCase().includes('401') ||
|
||||
error.toLowerCase().includes('unauthorized');
|
||||
|
||||
if (isTokenError) {
|
||||
return <TokenErrorPage error={error} />;
|
||||
}
|
||||
|
||||
return <PageError error={error} />;
|
||||
}
|
||||
|
||||
return <div>{__("Redirecting to trust center...")}</div>;
|
||||
}
|
||||
@@ -111,18 +111,6 @@ const routes = [
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/trust/:slug",
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
fallback: PageSkeleton,
|
||||
Component: lazy(() => import("./trust/pages/PublicTrustCenterPage")),
|
||||
},
|
||||
{
|
||||
path: "/trust/:slug/access",
|
||||
ErrorBoundary: ErrorBoundary,
|
||||
fallback: PageSkeleton,
|
||||
Component: lazy(() => import("./pages/TrustCenterAccessPage")),
|
||||
},
|
||||
{
|
||||
path: "/organizations/:organizationId",
|
||||
Component: MainLayout,
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
Button,
|
||||
Checkbox,
|
||||
IconLock,
|
||||
IconArrowDown,
|
||||
useToast,
|
||||
useDialogRef
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useAcceptNonDisclosureAgreement } from "/hooks/useTrustCenterQueries";
|
||||
import { buildEndpoint } from "/providers/RelayProviders";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
|
||||
type Props = {
|
||||
trustCenterId: string;
|
||||
organizationName: string;
|
||||
ndaFileName?: string | null;
|
||||
ndaFileUrl?: string | null;
|
||||
};
|
||||
|
||||
export function NDAAcceptanceDialog({ trustCenterId, organizationName, ndaFileName, ndaFileUrl }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const [isChecked, setIsChecked] = useState(false);
|
||||
const dialogRef = useDialogRef();
|
||||
const acceptNdaMutation = useAcceptNonDisclosureAgreement();
|
||||
|
||||
useEffect(() => {
|
||||
dialogRef.current?.open();
|
||||
}, []);
|
||||
|
||||
const handleLogout = async () => {
|
||||
try {
|
||||
const response = await fetch(buildEndpoint('/api/trust/v1/auth/logout'), {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'include',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Logout failed");
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: __("Logout failed"),
|
||||
variant: "error",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleAccept = () => {
|
||||
if (!isChecked) {
|
||||
toast({
|
||||
title: __("Agreement Required"),
|
||||
description: __("Please check the box to confirm your agreement"),
|
||||
variant: "error",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
acceptNdaMutation.mutate(
|
||||
{ trustCenterId },
|
||||
{
|
||||
onSuccess: () => {
|
||||
window.location.reload();
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: __("Failed to accept the Non-Disclosure Agreement"),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
handleLogout();
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ref={dialogRef}
|
||||
closable={false}
|
||||
onClose={handleCancel}
|
||||
>
|
||||
<DialogContent>
|
||||
<div className="space-y-3 p-4">
|
||||
<div className="text-center">
|
||||
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-amber-50 border border-amber-200 mb-3">
|
||||
<IconLock className="h-6 w-6 text-amber-600" />
|
||||
</div>
|
||||
<h2 className="text-lg font-semibold text-txt-primary mb-2">
|
||||
{__("Non-Disclosure Agreement")}
|
||||
</h2>
|
||||
<p className="text-sm text-txt-secondary">
|
||||
{sprintf(__("To access %s's trust center, you must accept the Non-Disclosure Agreement."), organizationName)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="bg-level-1 p-3 rounded-lg border border-border-subtle">
|
||||
{ndaFileName && ndaFileUrl ? (
|
||||
<div className="text-center">
|
||||
<p className="text-sm font-medium text-txt-primary mb-3">
|
||||
{__("Please review and download the Non-Disclosure Agreement:")}
|
||||
</p>
|
||||
<div className="flex justify-center">
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconArrowDown}
|
||||
onClick={() => {
|
||||
const link = document.createElement('a');
|
||||
link.href = ndaFileUrl;
|
||||
link.download = ndaFileName || 'NDA.pdf';
|
||||
link.target = '_blank';
|
||||
link.rel = 'noopener noreferrer';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
}}
|
||||
>
|
||||
{sprintf(__("Download %s"), ndaFileName)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<p className="text-sm font-medium text-txt-primary mb-2">
|
||||
{__("By accepting this agreement, you commit to:")}
|
||||
</p>
|
||||
<ul className="text-sm text-txt-secondary space-y-0.5">
|
||||
<li className="flex items-start">
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
|
||||
{__("Keep confidential information secure")}
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
|
||||
{__("Not share or disclose sensitive data")}
|
||||
</li>
|
||||
<li className="flex items-start">
|
||||
<span className="inline-block w-2 h-2 rounded-full bg-txt-tertiary mt-1.5 mr-2 flex-shrink-0"></span>
|
||||
{__("Use information only for authorized purposes")}
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="flex items-start space-x-2 p-3 bg-level-0 rounded-lg border border-border-subtle">
|
||||
<div className="mt-0.5">
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
onChange={setIsChecked}
|
||||
/>
|
||||
</div>
|
||||
<label
|
||||
className="text-sm text-txt-primary cursor-pointer flex-1"
|
||||
onClick={() => setIsChecked(!isChecked)}
|
||||
>
|
||||
{__("I agree to the terms of the Non-Disclosure Agreement and will handle all information accordingly.")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
|
||||
<DialogFooter exitLabel={__("Disconnect")}>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={handleAccept}
|
||||
disabled={!isChecked || acceptNdaMutation.isPending}
|
||||
>
|
||||
{acceptNdaMutation.isPending ? __("Accepting...") : __("Accept & Continue")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogFooter,
|
||||
DialogContent,
|
||||
Button,
|
||||
Field,
|
||||
useToast,
|
||||
useDialogRef,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { useFormWithSchema } from "/hooks/useFormWithSchema";
|
||||
import { z } from "zod";
|
||||
import { useCreateTrustCenterAccess } from "/hooks/useTrustCenterQueries";
|
||||
|
||||
type Props = {
|
||||
trigger: React.ReactNode;
|
||||
trustCenterId: string;
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
export function PublicTrustCenterAccessRequestDialog({
|
||||
trigger,
|
||||
trustCenterId,
|
||||
organizationName
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const dialogRef = useDialogRef();
|
||||
|
||||
const mutation = useCreateTrustCenterAccess();
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(1, __("Name is required")).min(2, __("Name must be at least 2 characters long")),
|
||||
email: z.string().min(1, __("Email is required")).email(__("Please enter a valid email address")),
|
||||
});
|
||||
|
||||
const { register, handleSubmit, formState, reset } = useFormWithSchema(schema, {
|
||||
defaultValues: { name: "", email: "" },
|
||||
});
|
||||
|
||||
const onSubmit = handleSubmit(async (data) => {
|
||||
setIsSubmitting(true);
|
||||
mutation.mutate(
|
||||
{
|
||||
trustCenterId,
|
||||
email: data.email,
|
||||
name: data.name,
|
||||
},
|
||||
{
|
||||
onSuccess: (result) => {
|
||||
if (result.createTrustCenterAccess) {
|
||||
toast({
|
||||
title: __("Request Submitted"),
|
||||
description: __("Your access request has been submitted. You will receive an email if your request is approved."),
|
||||
variant: "success",
|
||||
});
|
||||
|
||||
reset();
|
||||
dialogRef.current?.close();
|
||||
}
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
onError: (_: Error) => {
|
||||
toast({
|
||||
title: __("Error"),
|
||||
description: __("An error occurred while submitting your request."),
|
||||
variant: "error",
|
||||
});
|
||||
setIsSubmitting(false);
|
||||
},
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
ref={dialogRef}
|
||||
trigger={trigger}
|
||||
title={__("Request Access")}
|
||||
>
|
||||
<form onSubmit={onSubmit}>
|
||||
<DialogContent padded className="space-y-4">
|
||||
<div className="text-sm text-txt-secondary">
|
||||
{sprintf(__("Request access to %s's Trust Center. Your request will be reviewed and you will receive an email notification with access instructions if approved."), organizationName)}
|
||||
</div>
|
||||
|
||||
<Field
|
||||
label={__("Your Name")}
|
||||
required
|
||||
error={formState.errors.name?.message}
|
||||
{...register("name")}
|
||||
placeholder={__("Enter your full name")}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
|
||||
<Field
|
||||
label={__("Email Address")}
|
||||
required
|
||||
type="email"
|
||||
error={formState.errors.email?.message}
|
||||
{...register("email")}
|
||||
placeholder={__("Enter your email address")}
|
||||
disabled={isSubmitting}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
||||
<DialogFooter>
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={isSubmitting || mutation.isPending}
|
||||
>
|
||||
{isSubmitting || mutation.isPending ? __("Submitting...") : __("Submit Request")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</form>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
import {
|
||||
Card,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
Button,
|
||||
IconArrowDown,
|
||||
IconLock,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { sprintf } from "@probo/helpers";
|
||||
import { FrameworkLogo } from "/components/FrameworkLogo";
|
||||
import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog";
|
||||
import { useExportReportPDF } from "../../hooks/useTrustCenterQueries";
|
||||
import type { TrustCenterAudit } from "../pages/PublicTrustCenterPage";
|
||||
|
||||
type Props = {
|
||||
audits: TrustCenterAudit[];
|
||||
organizationName: string;
|
||||
isAuthenticated: boolean;
|
||||
trustCenterId: string;
|
||||
};
|
||||
|
||||
export function PublicTrustCenterAudits({
|
||||
audits,
|
||||
organizationName,
|
||||
isAuthenticated,
|
||||
trustCenterId
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
|
||||
const mutation = useExportReportPDF();
|
||||
|
||||
const handleDownload = (report: NonNullable<TrustCenterAudit["report"]>) => {
|
||||
mutation.mutate(report.id, {
|
||||
onSuccess: (data) => {
|
||||
if (data.exportReportPDF?.data) {
|
||||
const link = window.document.createElement("a");
|
||||
link.href = data.exportReportPDF.data;
|
||||
link.download = `${report.filename}`;
|
||||
window.document.body.appendChild(link);
|
||||
link.click();
|
||||
window.document.body.removeChild(link);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: __("Download Failed"),
|
||||
description: __("Unable to download the report. Please try again."),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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 !== null;
|
||||
|
||||
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 ? (
|
||||
<PublicTrustCenterAccessRequestDialog
|
||||
trigger={
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconLock}
|
||||
>
|
||||
{__("Request Access")}
|
||||
</Button>
|
||||
}
|
||||
trustCenterId={trustCenterId}
|
||||
organizationName={organizationName}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconArrowDown}
|
||||
onClick={() => handleDownload(audit.report!)}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{mutation.isPending ? __("Downloading...") : __("Download")}
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
import {
|
||||
Card,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
DocumentTypeBadge,
|
||||
Button,
|
||||
IconArrowDown,
|
||||
IconLock,
|
||||
useToast,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { useExportDocumentPDF, type TrustCenterDocument } from "/hooks/useTrustCenterQueries";
|
||||
import { PublicTrustCenterAccessRequestDialog } from "./PublicTrustCenterAccessRequestDialog";
|
||||
|
||||
type Props = {
|
||||
documents: TrustCenterDocument[];
|
||||
isAuthenticated: boolean;
|
||||
trustCenterId: string;
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
export function PublicTrustCenterDocuments({
|
||||
documents,
|
||||
isAuthenticated,
|
||||
trustCenterId,
|
||||
organizationName
|
||||
}: Props) {
|
||||
const { __ } = useTranslate();
|
||||
const { toast } = useToast();
|
||||
|
||||
const mutation = useExportDocumentPDF();
|
||||
|
||||
const handleDownload = (document: TrustCenterDocument) => {
|
||||
mutation.mutate(document.id, {
|
||||
onSuccess: (data) => {
|
||||
if (data.exportDocumentPDF?.data) {
|
||||
const link = window.document.createElement("a");
|
||||
link.href = data.exportDocumentPDF.data;
|
||||
link.download = `${document.title}.pdf`;
|
||||
window.document.body.appendChild(link);
|
||||
link.click();
|
||||
window.document.body.removeChild(link);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
toast({
|
||||
title: __("Download Failed"),
|
||||
description: __("Unable to download the document. Please try again."),
|
||||
variant: "error",
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
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) => {
|
||||
return (
|
||||
<Tr key={document.id}>
|
||||
<Td>
|
||||
<div className="font-medium">
|
||||
{document.title}
|
||||
</div>
|
||||
</Td>
|
||||
<Td>
|
||||
<DocumentTypeBadge type={document.documentType} />
|
||||
</Td>
|
||||
<Td>
|
||||
{!isAuthenticated ? (
|
||||
<PublicTrustCenterAccessRequestDialog
|
||||
trigger={
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconLock}
|
||||
>
|
||||
{__("Request Access")}
|
||||
</Button>
|
||||
}
|
||||
trustCenterId={trustCenterId}
|
||||
organizationName={organizationName}
|
||||
/>
|
||||
) : (
|
||||
<Button
|
||||
variant="secondary"
|
||||
icon={IconArrowDown}
|
||||
onClick={() => handleDownload(document)}
|
||||
disabled={mutation.isPending}
|
||||
>
|
||||
{mutation.isPending ? __("Downloading...") : __("Download")}
|
||||
</Button>
|
||||
)}
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
import {
|
||||
Card,
|
||||
Tr,
|
||||
Td,
|
||||
Table,
|
||||
Thead,
|
||||
Tbody,
|
||||
Th,
|
||||
} from "@probo/ui";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { faviconUrl, sprintf, getCountryName, type CountryCode } from "@probo/helpers";
|
||||
import type { TrustCenterVendor } from "../pages/PublicTrustCenterPage";
|
||||
|
||||
type Props = {
|
||||
vendors: TrustCenterVendor[];
|
||||
organizationName: string;
|
||||
};
|
||||
|
||||
export function PublicTrustCenterVendors({ vendors, organizationName }: Props) {
|
||||
const { __ } = useTranslate();
|
||||
|
||||
const hasCountriesData = vendors.some(vendor => vendor.countries && vendor.countries.length > 0);
|
||||
|
||||
if (vendors.length === 0) {
|
||||
return (
|
||||
<Card padded>
|
||||
<div className="text-center py-8">
|
||||
<h2 className="text-xl font-semibold text-txt-primary mb-2">
|
||||
{__("Subcontractors")}
|
||||
</h2>
|
||||
<p className="text-txt-secondary">
|
||||
{__("No subcontractor information is currently available.")}
|
||||
</p>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Card padded className="space-y-4">
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-txt-primary">
|
||||
{__("Subcontractors")}
|
||||
</h2>
|
||||
<p className="text-sm text-txt-secondary mt-1">
|
||||
{sprintf(__("Third-party subcontractors %s work with"), organizationName)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<Table>
|
||||
<Thead>
|
||||
<Tr>
|
||||
<Th>{__("Company")}</Th>
|
||||
<Th>{__("Website")}</Th>
|
||||
{hasCountriesData && <Th>{__("Countries")}</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?:\/\//, '');
|
||||
}
|
||||
};
|
||||
|
||||
const formatCountries = (countries: CountryCode[]) => {
|
||||
return countries.map(code => getCountryName(__, code)).join(", ");
|
||||
};
|
||||
|
||||
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-txt-info hover:opacity-80 underline transition-opacity"
|
||||
>
|
||||
{getCleanUrl(url)}
|
||||
</a>
|
||||
) : (
|
||||
<span className="text-txt-secondary text-sm">
|
||||
{__("No website available")}
|
||||
</span>
|
||||
)}
|
||||
</Td>
|
||||
{hasCountriesData && (
|
||||
<Td>
|
||||
<span className="text-txt-secondary text-sm">
|
||||
{formatCountries(vendor.countries)}
|
||||
</span>
|
||||
</Td>
|
||||
)}
|
||||
</Tr>
|
||||
);
|
||||
})}
|
||||
</Tbody>
|
||||
</Table>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import { useParams, Navigate } from "react-router";
|
||||
import { usePageTitle } from "@probo/hooks";
|
||||
import { useTranslate } from "@probo/i18n";
|
||||
import { PublicTrustCenterLayout } from "/layouts/PublicTrustCenterLayout";
|
||||
import { PublicTrustCenterAudits } from "../components/PublicTrustCenterAudits";
|
||||
import { PublicTrustCenterVendors } from "../components/PublicTrustCenterVendors";
|
||||
import { PublicTrustCenterDocuments } from "../components/PublicTrustCenterDocuments";
|
||||
import { NDAAcceptanceDialog } from "../components/NDAAcceptanceDialog";
|
||||
import { Spinner } from "@probo/ui";
|
||||
import { useTrustCenterQuery, type TrustCenterDocument, type TrustCenterAudit, type TrustCenterVendor } from "/hooks/useTrustCenterQueries";
|
||||
|
||||
export type { TrustCenterDocument, TrustCenterAudit, TrustCenterVendor };
|
||||
|
||||
export default function PublicTrustCenterPage() {
|
||||
const { __ } = useTranslate();
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
|
||||
const { data, isLoading, error } = useTrustCenterQuery(slug || "");
|
||||
|
||||
const organization = data?.trustCenterBySlug?.organization;
|
||||
const organizationName = organization?.name || "";
|
||||
|
||||
usePageTitle(
|
||||
organizationName ? `${organizationName} - Trust Center` : "Trust Center"
|
||||
);
|
||||
|
||||
if (!slug) {
|
||||
return <Navigate to="/" replace />;
|
||||
}
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<Spinner />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{__("Error Loading Trust Center")}
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
{__("There was an error loading the trust center. Please try again later.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!data?.trustCenterBySlug) {
|
||||
return (
|
||||
<div className="flex items-center justify-center min-h-screen">
|
||||
<div className="text-center">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
{__("Trust Center Not Found")}
|
||||
</h1>
|
||||
<p className="text-gray-600">
|
||||
{__("The trust center you're looking for doesn't exist.")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const { trustCenterBySlug } = data;
|
||||
const { documents, audits, vendors, isUserAuthenticated, hasAcceptedNonDisclosureAgreement } = trustCenterBySlug;
|
||||
|
||||
const trustCenterDocuments = documents.edges.map((edge) => edge.node) as TrustCenterDocument[];
|
||||
const trustCenterAudits = audits.edges.map((edge) => edge.node) as TrustCenterAudit[];
|
||||
const trustCenterVendors = vendors.edges.map((edge) => edge.node) as TrustCenterVendor[];
|
||||
|
||||
const showNdaDialog = isUserAuthenticated && !hasAcceptedNonDisclosureAgreement;
|
||||
|
||||
return (
|
||||
<>
|
||||
{showNdaDialog && (
|
||||
<NDAAcceptanceDialog
|
||||
trustCenterId={trustCenterBySlug.id}
|
||||
organizationName={organizationName}
|
||||
ndaFileName={trustCenterBySlug.ndaFileName}
|
||||
ndaFileUrl={trustCenterBySlug.ndaFileUrl}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PublicTrustCenterLayout
|
||||
organizationName={organizationName}
|
||||
organizationLogo={organization?.logoUrl}
|
||||
isAuthenticated={isUserAuthenticated}
|
||||
>
|
||||
<div className="space-y-12">
|
||||
<PublicTrustCenterAudits
|
||||
audits={trustCenterAudits}
|
||||
organizationName={organizationName}
|
||||
isAuthenticated={isUserAuthenticated}
|
||||
trustCenterId={trustCenterBySlug.id}
|
||||
/>
|
||||
<PublicTrustCenterDocuments
|
||||
documents={trustCenterDocuments}
|
||||
organizationName={organizationName}
|
||||
isAuthenticated={isUserAuthenticated}
|
||||
trustCenterId={trustCenterBySlug.id}
|
||||
/>
|
||||
<PublicTrustCenterVendors
|
||||
vendors={trustCenterVendors}
|
||||
organizationName={organizationName}
|
||||
/>
|
||||
</div>
|
||||
</PublicTrustCenterLayout>
|
||||
</>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user