Add nda to trust center

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-09-09 16:17:24 +02:00
parent 28c41fa4ae
commit b06bd113f3
37 changed files with 2921 additions and 259 deletions

View File

@@ -127,7 +127,9 @@ export default function TrustCenterAccessPage() {
if (error) {
const isTokenError = error.toLowerCase().includes('token') ||
error.toLowerCase().includes('expired') ||
error.toLowerCase().includes('invalid');
error.toLowerCase().includes('invalid') ||
error.toLowerCase().includes('401') ||
error.toLowerCase().includes('unauthorized');
if (isTokenError) {
return <TokenErrorPage error={error} />;

View File

@@ -16,6 +16,7 @@ import {
useDialogRef,
IconTrashCan,
IconPencil,
IconCheckmark1,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
@@ -85,6 +86,7 @@ export default function TrustCenterAccessTab() {
email: string;
name: string;
active: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
createdAt: Date;
};
@@ -95,6 +97,7 @@ export default function TrustCenterAccessTab() {
email: edge.node.email,
name: edge.node.name,
active: edge.node.active,
hasAcceptedNonDisclosureAgreement: edge.node.hasAcceptedNonDisclosureAgreement,
createdAt: new Date(edge.node.createdAt)
})) ?? [];
@@ -203,6 +206,7 @@ export default function TrustCenterAccessTab() {
<Th>{__("Email")}</Th>
<Th>{__("Date")}</Th>
<Th>{__("Active")}</Th>
<Th>{__("NDA")}</Th>
<Th></Th>
</Tr>
</Thead>
@@ -220,6 +224,11 @@ export default function TrustCenterAccessTab() {
onChange={(active) => handleToggleActive(access.id, active)}
/>
</Td>
<Td>
{access.hasAcceptedNonDisclosureAgreement && (
<IconCheckmark1 size={16} className="text-txt-success" />
)}
</Td>
<Td noLink width={160} className="text-end">
<div className="flex gap-2 justify-end">
<Button

View File

@@ -4,6 +4,7 @@ import {
Button,
Card,
Checkbox,
Dropzone,
Field,
Input,
PageHeader,
@@ -12,9 +13,10 @@ import {
Tabs,
TabLink,
TabItem,
IconTrashCan,
} from "@probo/ui";
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
import { trustCenterQuery, useUpdateTrustCenterMutation } from "/hooks/graph/TrustCenterGraph";
import { trustCenterQuery, useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
import type { TrustCenterGraphQuery } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
import { useState } from "react";
import { useOrganizationId } from "/hooks/useOrganizationId";
@@ -32,6 +34,8 @@ export default function TrustCenterPage({ queryRef }: Props) {
const { organization } = usePreloadedQuery(trustCenterQuery, queryRef);
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
const [isActive, setIsActive] = useState(organization.trustCenter?.active || false);
const [slug, setSlug] = useState(organization.trustCenter?.slug || "");
const [isUpdatingSlug, setIsUpdatingSlug] = useState(false);
@@ -101,6 +105,57 @@ export default function TrustCenterPage({ queryRef }: Props) {
});
};
const handleNDAUpload = async (files: File[]) => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
if (files.length === 0) return;
const file = files[0];
await uploadNDA({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
fileName: file.name,
file: null,
},
},
uploadables: {
"input.file": file,
},
});
};
const handleNDADelete = async () => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
if (!confirm(__("Are you sure you want to delete the NDA file?"))) {
return;
}
await deleteNDA({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
},
},
});
};
const trustCenterUrl = organization.trustCenter?.slug
? `${window.location.origin}/trust/${organization.trustCenter.slug}`
: null;
@@ -211,6 +266,68 @@ export default function TrustCenterPage({ queryRef }: Props) {
</div>
</Card>
</div>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Non-Disclosure Agreement")}</h2>
{(isUploadingNDA || isDeletingNDA) && <Spinner />}
</div>
<Card padded className="space-y-4">
<div className="space-y-2">
{!organization.trustCenter?.ndaFileName ? (
<p className="text-sm text-txt-tertiary">
{__("Upload a Non-Disclosure Agreement that visitors must accept before accessing your trust center")}
</p>
) : (<></>)}
{organization.trustCenter?.ndaFileName ? (
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<p className="text-sm font-medium">
{organization.trustCenter.ndaFileName || __("Non-Disclosure Agreement")}
</p>
</div>
<p className="text-xs text-txt-tertiary">
{__("Visitors will need to accept this NDA before accessing your trust center")}
</p>
</div>
<div className="flex items-center gap-2">
<Button
type="button"
variant="secondary"
onClick={() => {
if (organization.trustCenter?.ndaFileUrl) {
window.open(organization.trustCenter.ndaFileUrl, '_blank');
}
}}
>
{__("Download PDF")}
</Button>
<Button
variant="quaternary"
icon={IconTrashCan}
onClick={handleNDADelete}
disabled={isDeletingNDA}
/>
</div>
</div>
</div>
) : (
<Dropzone
description={__("Upload PDF files up to 10MB")}
isUploading={isUploadingNDA}
onDrop={handleNDAUpload}
accept={{
"application/pdf": [".pdf"],
}}
maxSize={10}
/>
)}
</div>
</Card>
</div>
<div className="space-y-4">
<Tabs>
<TabItem