diff --git a/apps/console/src/hooks/graph/TrustCenterAccessGraph.ts b/apps/console/src/hooks/graph/TrustCenterAccessGraph.ts index d1f7552ae..6845d497c 100644 --- a/apps/console/src/hooks/graph/TrustCenterAccessGraph.ts +++ b/apps/console/src/hooks/graph/TrustCenterAccessGraph.ts @@ -26,6 +26,7 @@ export const trustCenterAccessesQuery = graphql` email name active + hasAcceptedNonDisclosureAgreement createdAt } } @@ -48,6 +49,7 @@ export const createTrustCenterAccessMutation = graphql` email name active + hasAcceptedNonDisclosureAgreement createdAt } } @@ -65,6 +67,7 @@ export const updateTrustCenterAccessMutation = graphql` email name active + hasAcceptedNonDisclosureAgreement createdAt updatedAt } diff --git a/apps/console/src/hooks/graph/TrustCenterGraph.ts b/apps/console/src/hooks/graph/TrustCenterGraph.ts index c4973682c..6835fad99 100644 --- a/apps/console/src/hooks/graph/TrustCenterGraph.ts +++ b/apps/console/src/hooks/graph/TrustCenterGraph.ts @@ -12,6 +12,8 @@ export const trustCenterQuery = graphql` id active slug + ndaFileName + ndaFileUrl createdAt updatedAt } @@ -66,3 +68,48 @@ export function useUpdateTrustCenterMutation() { } ); } + +export const uploadTrustCenterNDAMutation = graphql` + mutation TrustCenterGraphUploadNDAMutation($input: UploadTrustCenterNDAInput!) { + uploadTrustCenterNDA(input: $input) { + trustCenter { + id + ndaFileName + updatedAt + } + } + } +`; + +export function useUploadTrustCenterNDAMutation() { + return useMutationWithToasts( + uploadTrustCenterNDAMutation, + { + successMessage: "NDA uploaded successfully", + errorMessage: "Failed to upload NDA", + } + ); +} + +export const deleteTrustCenterNDAMutation = graphql` + mutation TrustCenterGraphDeleteNDAMutation($input: DeleteTrustCenterNDAInput!) { + deleteTrustCenterNDA(input: $input) { + trustCenter { + id + ndaFileName + updatedAt + } + } + } +`; + +export function useDeleteTrustCenterNDAMutation() { + return useMutationWithToasts( + deleteTrustCenterNDAMutation, + { + successMessage: "NDA deleted successfully", + errorMessage: "Failed to delete NDA", + } + ); +} + diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts index 3320092aa..3e3f0e813 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphCreateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<5ccaab70e4cb8c4b5bee7fbe6ba2c0b6>> * @lightSyntaxTransform * @nogrep */ @@ -27,6 +27,7 @@ export type TrustCenterAccessGraphCreateMutation$data = { readonly active: boolean; readonly createdAt: any; readonly email: string; + readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly name: string; }; @@ -107,6 +108,13 @@ v3 = { "name": "active", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasAcceptedNonDisclosureAgreement", + "storageKey": null + }, { "alias": null, "args": null, @@ -186,16 +194,16 @@ return { ] }, "params": { - "cacheID": "eafddcd0263963235d3249c22eb50593", + "cacheID": "fa88b100be7598cf46159cf79199c398", "id": null, "metadata": {}, "name": "TrustCenterAccessGraphCreateMutation", "operationKind": "mutation", - "text": "mutation TrustCenterAccessGraphCreateMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccessEdge {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n }\n }\n }\n}\n" + "text": "mutation TrustCenterAccessGraphCreateMutation(\n $input: CreateTrustCenterAccessInput!\n) {\n createTrustCenterAccess(input: $input) {\n trustCenterAccessEdge {\n cursor\n node {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n }\n }\n }\n}\n" } }; })(); -(node as any).hash = "99676fee0b2de06a92cdad66c577eee7"; +(node as any).hash = "6c6c1344730e7d908a5c6392f578ca81"; export default node; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts index 050c16803..9b8e8e52c 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<2333a6a7d5415f1a08a5612dcaceee8f>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -22,6 +22,7 @@ export type TrustCenterAccessGraphQuery$data = { readonly active: boolean; readonly createdAt: any; readonly email: string; + readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly name: string; }; @@ -163,6 +164,13 @@ v5 = [ "name": "active", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasAcceptedNonDisclosureAgreement", + "storageKey": null + }, { "alias": null, "args": null, @@ -290,7 +298,7 @@ return { ] }, "params": { - "cacheID": "5b83cd4ae2434ce2e00de7230d264432", + "cacheID": "0286ec03fb4ae012ada7bae8a2234152", "id": null, "metadata": { "connection": [ @@ -307,11 +315,11 @@ return { }, "name": "TrustCenterAccessGraphQuery", "operationKind": "query", - "text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n accesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n email\n name\n active\n createdAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n" + "text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n accesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n pageInfo {\n hasNextPage\n hasPreviousPage\n startCursor\n endCursor\n }\n edges {\n cursor\n node {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n __typename\n }\n }\n }\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "af598fd2af198e63ed84fd618857a985"; +(node as any).hash = "f837b96937f1397ac1a3065f58386e4d"; export default node; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts index 11414b288..99ae2288f 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterAccessGraphUpdateMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<6de2ecb63a58c88c3008368061943b49>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -23,6 +23,7 @@ export type TrustCenterAccessGraphUpdateMutation$data = { readonly active: boolean; readonly createdAt: any; readonly email: string; + readonly hasAcceptedNonDisclosureAgreement: boolean; readonly id: string; readonly name: string; readonly updatedAt: any; @@ -93,6 +94,13 @@ v1 = [ "name": "active", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasAcceptedNonDisclosureAgreement", + "storageKey": null + }, { "alias": null, "args": null, @@ -132,16 +140,16 @@ return { "selections": (v1/*: any*/) }, "params": { - "cacheID": "683ee01fb5173b49b0a7f3c2d99cf002", + "cacheID": "223169ee8a4f65008047097ecff68fc5", "id": null, "metadata": {}, "name": "TrustCenterAccessGraphUpdateMutation", "operationKind": "mutation", - "text": "mutation TrustCenterAccessGraphUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n createdAt\n updatedAt\n }\n }\n}\n" + "text": "mutation TrustCenterAccessGraphUpdateMutation(\n $input: UpdateTrustCenterAccessInput!\n) {\n updateTrustCenterAccess(input: $input) {\n trustCenterAccess {\n id\n email\n name\n active\n hasAcceptedNonDisclosureAgreement\n createdAt\n updatedAt\n }\n }\n}\n" } }; })(); -(node as any).hash = "cccd083b0047f6bc7b504b7494205e9d"; +(node as any).hash = "0da1f737e6ea1db7a1b9930c4b6cc545"; export default node; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterGraphDeleteNDAMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphDeleteNDAMutation.graphql.ts new file mode 100644 index 000000000..df59160bf --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphDeleteNDAMutation.graphql.ts @@ -0,0 +1,121 @@ +/** + * @generated SignedSource<<43a8665719f251c1bae028ff7821b14a>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteTrustCenterNDAInput = { + trustCenterId: string; +}; +export type TrustCenterGraphDeleteNDAMutation$variables = { + input: DeleteTrustCenterNDAInput; +}; +export type TrustCenterGraphDeleteNDAMutation$data = { + readonly deleteTrustCenterNDA: { + readonly trustCenter: { + readonly id: string; + readonly ndaFileName: string | null | undefined; + readonly updatedAt: any; + }; + }; +}; +export type TrustCenterGraphDeleteNDAMutation = { + response: TrustCenterGraphDeleteNDAMutation$data; + variables: TrustCenterGraphDeleteNDAMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "DeleteTrustCenterNDAPayload", + "kind": "LinkedField", + "name": "deleteTrustCenterNDA", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "TrustCenterGraphDeleteNDAMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "TrustCenterGraphDeleteNDAMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "fa3ebf3593c396bf05819d357d9f909f", + "id": null, + "metadata": {}, + "name": "TrustCenterGraphDeleteNDAMutation", + "operationKind": "mutation", + "text": "mutation TrustCenterGraphDeleteNDAMutation(\n $input: DeleteTrustCenterNDAInput!\n) {\n deleteTrustCenterNDA(input: $input) {\n trustCenter {\n id\n ndaFileName\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "9c055069b9e2e7432fed7509f6578a07"; + +export default node; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterGraphQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphQuery.graphql.ts index 31ace1bc4..11e80ac64 100644 --- a/apps/console/src/hooks/graph/__generated__/TrustCenterGraphQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<25550a93c5459c3b405c782be19ac888>> + * @generated SignedSource<<9dbea87f398ce8189cb2431d7c03912c>> * @lightSyntaxTransform * @nogrep */ @@ -37,6 +37,8 @@ export type TrustCenterGraphQuery$data = { readonly active: boolean; readonly createdAt: any; readonly id: string; + readonly ndaFileName: string | null | undefined; + readonly ndaFileUrl: string | null | undefined; readonly slug: string; readonly updatedAt: any; } | null | undefined; @@ -114,6 +116,20 @@ v5 = { "name": "slug", "storageKey": null }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileUrl", + "storageKey": null + }, (v4/*: any*/), { "alias": null, @@ -544,16 +560,16 @@ return { ] }, "params": { - "cacheID": "28c1a32327e2fee9320fd091100ec800", + "cacheID": "8dff30a639470418ba7151cb5743d3cc", "id": null, "metadata": {}, "name": "TrustCenterGraphQuery", "operationKind": "query", - "text": "query TrustCenterGraphQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n trustCenter {\n id\n active\n slug\n createdAt\n updatedAt\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...TrustCenterDocumentsCardFragment\n }\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n ...TrustCenterAuditsCardFragment\n }\n }\n }\n vendors(first: 100) {\n edges {\n node {\n id\n ...TrustCenterVendorsCardFragment\n }\n }\n }\n }\n id\n }\n}\n\nfragment TrustCenterAuditsCardFragment on Audit {\n id\n name\n framework {\n name\n id\n }\n validFrom\n validUntil\n state\n showOnTrustCenter\n createdAt\n}\n\nfragment TrustCenterDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n showOnTrustCenter\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment TrustCenterVendorsCardFragment on Vendor {\n id\n name\n category\n description\n showOnTrustCenter\n createdAt\n}\n" + "text": "query TrustCenterGraphQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n trustCenter {\n id\n active\n slug\n ndaFileName\n ndaFileUrl\n createdAt\n updatedAt\n }\n documents(first: 100) {\n edges {\n node {\n id\n ...TrustCenterDocumentsCardFragment\n }\n }\n }\n audits(first: 100) {\n edges {\n node {\n id\n ...TrustCenterAuditsCardFragment\n }\n }\n }\n vendors(first: 100) {\n edges {\n node {\n id\n ...TrustCenterVendorsCardFragment\n }\n }\n }\n }\n id\n }\n}\n\nfragment TrustCenterAuditsCardFragment on Audit {\n id\n name\n framework {\n name\n id\n }\n validFrom\n validUntil\n state\n showOnTrustCenter\n createdAt\n}\n\nfragment TrustCenterDocumentsCardFragment on Document {\n id\n title\n createdAt\n documentType\n showOnTrustCenter\n versions(first: 1) {\n edges {\n node {\n id\n status\n }\n }\n }\n}\n\nfragment TrustCenterVendorsCardFragment on Vendor {\n id\n name\n category\n description\n showOnTrustCenter\n createdAt\n}\n" } }; })(); -(node as any).hash = "260529b3ca03240b07989f278bb7a539"; +(node as any).hash = "21b2915ae5002dd54c990013e94adf5d"; export default node; diff --git a/apps/console/src/hooks/graph/__generated__/TrustCenterGraphUploadNDAMutation.graphql.ts b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphUploadNDAMutation.graphql.ts new file mode 100644 index 000000000..3412166c4 --- /dev/null +++ b/apps/console/src/hooks/graph/__generated__/TrustCenterGraphUploadNDAMutation.graphql.ts @@ -0,0 +1,123 @@ +/** + * @generated SignedSource<<966d4aa638042469c717ddc96e5c5d78>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type UploadTrustCenterNDAInput = { + file: any; + fileName: string; + trustCenterId: string; +}; +export type TrustCenterGraphUploadNDAMutation$variables = { + input: UploadTrustCenterNDAInput; +}; +export type TrustCenterGraphUploadNDAMutation$data = { + readonly uploadTrustCenterNDA: { + readonly trustCenter: { + readonly id: string; + readonly ndaFileName: string | null | undefined; + readonly updatedAt: any; + }; + }; +}; +export type TrustCenterGraphUploadNDAMutation = { + response: TrustCenterGraphUploadNDAMutation$data; + variables: TrustCenterGraphUploadNDAMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "UploadTrustCenterNDAPayload", + "kind": "LinkedField", + "name": "uploadTrustCenterNDA", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "TrustCenterGraphUploadNDAMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "TrustCenterGraphUploadNDAMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "09a5dbf73f3e279173691b235f9da2e2", + "id": null, + "metadata": {}, + "name": "TrustCenterGraphUploadNDAMutation", + "operationKind": "mutation", + "text": "mutation TrustCenterGraphUploadNDAMutation(\n $input: UploadTrustCenterNDAInput!\n) {\n uploadTrustCenterNDA(input: $input) {\n trustCenter {\n id\n ndaFileName\n updatedAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "b9877f8a5b9c2c12addeb939360719f1"; + +export default node; diff --git a/apps/console/src/hooks/useTrustCenterQueries.ts b/apps/console/src/hooks/useTrustCenterQueries.ts index 2eb3ad01b..22698340a 100644 --- a/apps/console/src/hooks/useTrustCenterQueries.ts +++ b/apps/console/src/hooks/useTrustCenterQueries.ts @@ -34,6 +34,9 @@ interface TrustCenterQueryData { active: boolean; slug: string; isUserAuthenticated: boolean; + hasAcceptedNonDisclosureAgreement: boolean; + ndaFileName: string | null; + ndaFileUrl: string | null; organization: { id: string; name: string; @@ -96,7 +99,19 @@ interface CreateTrustCenterAccessVariables { }; } -type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | CreateTrustCenterAccessVariables | Record; +interface AcceptNonDisclosureAgreementData { + acceptNonDisclosureAgreement: { + success: boolean; + }; +} + +interface AcceptNonDisclosureAgreementVariables { + input: { + trustCenterId: string; + }; +} + +type GraphQLVariables = TrustCenterQueryVariables | ExportDocumentPDFVariables | CreateTrustCenterAccessVariables | AcceptNonDisclosureAgreementVariables | Record; async function trustCenterGraphQLRequest( operationName: string, @@ -127,12 +142,14 @@ async function trustCenterGraphQLRequest( function isCriticalError(error: GraphQLError): boolean { const message = error.message?.toLowerCase() || ''; - const path = error.path || []; - if (message.includes('access denied') || message.includes('authentication required')) { - if (path.length > 2) { - return false; - } + 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; @@ -145,6 +162,9 @@ const TRUST_CENTER_QUERY = ` active slug isUserAuthenticated + hasAcceptedNonDisclosureAgreement + ndaFileName + ndaFileUrl organization { id name @@ -213,6 +233,16 @@ const CREATE_TRUST_CENTER_ACCESS_MUTATION = ` } `; +const ACCEPT_NDA_MUTATION = ` + mutation AcceptNonDisclosureAgreementMutation( + $input: AcceptNonDisclosureAgreementInput! + ) { + acceptNonDisclosureAgreement(input: $input) { + success + } + } +`; + export function useTrustCenterQuery(slug: string) { return useQuery({ queryKey: ["trust-center", slug], @@ -297,3 +327,27 @@ export function useCreateTrustCenterAccess() { }, }); } + +export function useAcceptNonDisclosureAgreement() { + return useMutation({ + mutationFn: async (input: { trustCenterId: string }) => { + const result = await trustCenterGraphQLRequest( + "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; + }, + }); +} diff --git a/apps/console/src/pages/TrustCenterAccessPage.tsx b/apps/console/src/pages/TrustCenterAccessPage.tsx index 81bd5c61a..5074a2a5c 100644 --- a/apps/console/src/pages/TrustCenterAccessPage.tsx +++ b/apps/console/src/pages/TrustCenterAccessPage.tsx @@ -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 ; diff --git a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx index ef8cc7b4b..b35eef637 100644 --- a/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx +++ b/apps/console/src/pages/organizations/trustCenter/TrustCenterAccessTab.tsx @@ -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() { {__("Email")} {__("Date")} {__("Active")} + {__("NDA")} @@ -220,6 +224,11 @@ export default function TrustCenterAccessTab() { onChange={(active) => handleToggleActive(access.id, active)} /> + + {access.hasAcceptedNonDisclosureAgreement && ( + + )} +
+ +
+
+

{__("Non-Disclosure Agreement")}

+ {(isUploadingNDA || isDeletingNDA) && } +
+ +
+ {!organization.trustCenter?.ndaFileName ? ( +

+ {__("Upload a Non-Disclosure Agreement that visitors must accept before accessing your trust center")} +

+ ) : (<>)} + {organization.trustCenter?.ndaFileName ? ( +
+
+
+
+

+ {organization.trustCenter.ndaFileName || __("Non-Disclosure Agreement")} +

+
+

+ {__("Visitors will need to accept this NDA before accessing your trust center")} +

+
+
+ +
+
+
+ ) : ( + + )} +
+
+
+
{ + 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 ( + + +
+
+
+ +
+

+ {__("Non-Disclosure Agreement")} +

+

+ {sprintf(__("To access %s's trust center, you must accept the Non-Disclosure Agreement."), organizationName)} +

+
+ +
+ {ndaFileName && ndaFileUrl ? ( +
+

+ {__("Please review and download the Non-Disclosure Agreement:")} +

+
+ +
+
+ ) : ( + <> +

+ {__("By accepting this agreement, you commit to:")} +

+
    +
  • + + {__("Keep confidential information secure")} +
  • +
  • + + {__("Not share or disclose sensitive data")} +
  • +
  • + + {__("Use information only for authorized purposes")} +
  • +
+ + )} +
+ +
+
+ +
+ +
+
+
+ + + + +
+ ); +} diff --git a/apps/console/src/trust/pages/PublicTrustCenterPage.tsx b/apps/console/src/trust/pages/PublicTrustCenterPage.tsx index 62194e3d1..758137cbc 100644 --- a/apps/console/src/trust/pages/PublicTrustCenterPage.tsx +++ b/apps/console/src/trust/pages/PublicTrustCenterPage.tsx @@ -5,6 +5,7 @@ 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"; @@ -66,36 +67,49 @@ export default function PublicTrustCenterPage() { } const { trustCenterBySlug } = data; - const { documents, audits, vendors, isUserAuthenticated } = trustCenterBySlug; + 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 && ( + - - -
-
+ )} + + +
+ + + +
+
+ ); } diff --git a/packages/ui/src/Molecules/Dialog/Dialog.tsx b/packages/ui/src/Molecules/Dialog/Dialog.tsx index 75d63f45a..4179c43c7 100644 --- a/packages/ui/src/Molecules/Dialog/Dialog.tsx +++ b/packages/ui/src/Molecules/Dialog/Dialog.tsx @@ -50,6 +50,7 @@ type Props = { className?: string; ref?: DialogRef; onClose?: () => void; + closable?: boolean; }; export const useDialogRef = (): DialogRef => { @@ -64,6 +65,7 @@ export function Dialog({ ref, defaultOpen, onClose, + closable = true, }: Props) { const { overlay, content, header, title: titleClassname } = dialog(); const [open, setOpen] = useState(!!defaultOpen); @@ -80,41 +82,55 @@ export function Dialog({ } const onOpenChange = (open: boolean) => { + if (!open && !closable) { + return; + } setOpen(open); if (!open) { onClose?.(); } }; + const contentProps = closable ? {} : { + onEscapeKeyDown: (e: Event) => e.preventDefault(), + onPointerDownOutside: (e: Event) => e.preventDefault(), + onInteractOutside: (e: Event) => e.preventDefault(), + }; + return ( - + {trigger && {trigger}} {title ? (
{title} + {closable && ( + +
+ ) : ( + closable && (
- ) : ( - -