Redesign trust center console

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2025-10-28 13:44:41 +01:00
parent abeaaec734
commit 1f0a5dff5c
39 changed files with 2308 additions and 2423 deletions

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -18,7 +17,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getAuditStateVariant, getAuditStateLabel, formatDate, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
import type { TrustCenterAuditsCardFragment$key } from "./__generated__/TrustCenterAuditsCardFragment.graphql";
const trustCenterAuditFragment = graphql`
@@ -50,17 +48,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onChangeVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const audits = useMemo(() => {
return limit ? props.audits.slice(0, limit) : props.audits;
}, [props.audits, limit]);
const showMoreButton = limit !== null && props.audits.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (auditId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
@@ -74,11 +70,9 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper {...(variant === "card" ? { padded: true } : {})} className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Framework")}</Th>
@@ -116,7 +110,7 @@ export function TrustCenterAuditsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.audits.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -21,7 +20,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
const trustCenterDocumentFragment = graphql`
fragment TrustCenterDocumentsCardFragment on Document {
@@ -55,17 +53,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onChangeVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const documents = useMemo(() => {
return limit ? props.documents.slice(0, limit) : props.documents;
}, [props.documents, limit]);
const showMoreButton = limit !== null && props.documents.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (documentId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
@@ -79,11 +75,9 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -121,7 +115,7 @@ export function TrustCenterDocumentsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.documents.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -22,7 +21,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState, useCallback, useEffect } from "react";
import { sprintf, getTrustCenterVisibilityOptions } from "@probo/helpers";
import { formatDate } from "@probo/helpers";
import clsx from "clsx";
const trustCenterFileFragment = graphql`
fragment TrustCenterFilesCardFragment on TrustCenterFile {
@@ -52,17 +50,15 @@ type Props<Params> = {
onChangeVisibility: Mutation<Params>;
onEdit: (file: { id: string; name: string; category: string }) => void;
onDelete: (id: string) => void;
variant?: "card" | "table";
};
export function TrustCenterFilesCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const files = useMemo(() => {
return limit ? props.files.slice(0, limit) : props.files;
}, [props.files, limit]);
const showMoreButton = limit !== null && props.files.length > limit;
const variant = props.variant ?? "table";
const onChangeVisibility = (fileId: string, trustCenterVisibility: "NONE" | "PRIVATE" | "PUBLIC") => {
props.onChangeVisibility({
@@ -76,11 +72,9 @@ export function TrustCenterFilesCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -120,7 +114,7 @@ export function TrustCenterFilesCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.files.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}
@@ -215,7 +209,7 @@ function FileRow(props: {
title={__("Edit")}
/>
<Button
variant="secondary"
variant="danger"
icon={IconTrashCan}
onClick={() => props.onDelete(file.id)}
disabled={props.disabled}

View File

@@ -3,10 +3,16 @@ import { safeOpenUrl } from "@probo/helpers";
import {
Avatar,
Button,
Card,
Table,
Thead,
Tbody,
Tr,
Th,
Td,
IconPlusLarge,
IconTrashCan,
IconPencil,
IconArrowLink,
} from "@probo/ui";
import { type ReactNode, useRef } from "react";
import {
@@ -64,7 +70,6 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
</p>
</div>
<Button
variant="secondary"
icon={IconPlusLarge}
onClick={handleCreate}
>
@@ -72,33 +77,33 @@ export function TrustCenterReferencesSection({ trustCenterId }: Props) {
</Button>
</div>
<Card padded>
{references.length === 0 ? (
<div className="text-center py-12">
<div className="mx-auto w-12 h-12 bg-tertiary rounded-lg flex items-center justify-center mb-4">
<IconPlusLarge size={24} className="text-txt-tertiary" />
</div>
<h3 className="text-lg font-medium text-txt-primary mb-2">
{__("No references yet")}
</h3>
<p className="text-txt-tertiary mb-4">
{__("Add customer testimonials and partner references to build trust")}
</p>
</div>
) : (
<div className="space-y-4">
{references.map((reference: Reference) => (
<ReferenceRow
key={reference.id}
reference={reference}
onEdit={() => handleEdit(reference)}
connectionId={referencesConnectionId}
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
/>
))}
</div>
)}
</Card>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Description")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{references.length === 0 && (
<Tr>
<Td colSpan={3} className="text-center text-txt-secondary">
{__("No references available")}
</Td>
</Tr>
)}
{references.map((reference: Reference) => (
<ReferenceRow
key={reference.id}
reference={reference}
onEdit={() => handleEdit(reference)}
connectionId={referencesConnectionId}
onVisitWebsite={() => handleVisitWebsite(reference.websiteUrl)}
/>
))}
</Tbody>
</Table>
<TrustCenterReferenceDialog ref={dialogRef} />
</div>
@@ -113,52 +118,47 @@ type ReferenceRowProps = {
};
function ReferenceRow({ reference, onEdit, connectionId, onVisitWebsite }: ReferenceRowProps) {
return (
<div className="flex items-center justify-between p-4 bg-level-1 rounded-lg">
<div className="flex items-center space-x-4 flex-1">
<Avatar
src={reference.logoUrl}
name={reference.name}
size="l"
/>
<div className="flex-1 min-w-0">
<div className="flex items-center space-x-2 mb-1">
<button
type="button"
onClick={onVisitWebsite}
className="font-medium text-txt-primary truncate hover:text-primary hover:underline text-left cursor-pointer"
>
{reference.name}
</button>
</div>
<p className="text-sm text-txt-secondary line-clamp-2 mb-2">
{reference.description}
</p>
</div>
</div>
<div className="flex items-center justify-center space-x-2 ml-4">
<Button
variant="tertiary"
icon={IconPencil}
onClick={onEdit}
aria-label="Edit reference"
/>
<DeleteTrustCenterReferenceDialog
referenceId={reference.id}
referenceName={reference.name}
connectionId={connectionId}
>
<Button
variant="danger"
icon={IconTrashCan}
aria-label="Delete reference"
<Tr>
<Td>
<div className="flex items-center gap-3">
<Avatar
src={reference.logoUrl}
name={reference.name}
size="m"
/>
</DeleteTrustCenterReferenceDialog>
</div>
</div>
<span className="font-medium">{reference.name}</span>
</div>
</Td>
<Td>
<span className="text-txt-secondary line-clamp-2">
{reference.description}
</span>
</Td>
<Td noLink width={200} className="text-end">
<div className="flex gap-2 justify-end">
<Button
variant="secondary"
icon={IconArrowLink}
onClick={onVisitWebsite}
/>
<Button
variant="secondary"
icon={IconPencil}
onClick={onEdit}
/>
<DeleteTrustCenterReferenceDialog
referenceId={reference.id}
referenceName={reference.name}
connectionId={connectionId}
>
<Button
variant="danger"
icon={IconTrashCan}
/>
</DeleteTrustCenterReferenceDialog>
</div>
</Td>
</Tr>
);
}

View File

@@ -1,6 +1,5 @@
import { graphql } from "relay-runtime";
import {
Card,
Button,
Tr,
Td,
@@ -18,7 +17,6 @@ import { useFragment } from "react-relay";
import { useMemo, useState } from "react";
import { sprintf } from "@probo/helpers";
import { useOrganizationId } from "/hooks/useOrganizationId";
import clsx from "clsx";
import type { TrustCenterVendorsCardFragment$key } from "./__generated__/TrustCenterVendorsCardFragment.graphql";
const trustCenterVendorFragment = graphql`
@@ -46,17 +44,15 @@ type Props<Params> = {
params: Params;
disabled?: boolean;
onToggleVisibility: Mutation<Params>;
variant?: "card" | "table";
};
export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
const { __ } = useTranslate();
const [limit, setLimit] = useState<number | null>(4);
const [limit, setLimit] = useState<number | null>(100);
const vendors = useMemo(() => {
return limit ? props.vendors.slice(0, limit) : props.vendors;
}, [props.vendors, limit]);
const showMoreButton = limit !== null && props.vendors.length > limit;
const variant = props.variant ?? "table";
const onToggleVisibility = (vendorId: string, showOnTrustCenter: boolean) => {
props.onToggleVisibility({
@@ -70,11 +66,9 @@ export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
});
};
const Wrapper = variant === "card" ? Card : "div";
return (
<Wrapper padded className="space-y-[10px]">
<Table className={clsx(variant === "card" && "bg-invert")}>
<div className="space-y-[10px]">
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
@@ -111,7 +105,7 @@ export function TrustCenterVendorsCard<Params>(props: Props<Params>) {
{sprintf(__("Show %s more"), props.vendors.length - limit)}
</Button>
)}
</Wrapper>
</div>
);
}

View File

@@ -25,36 +25,9 @@ export const trustCenterAccessesPaginationFragment = graphql`
active
hasAcceptedNonDisclosureAgreement
createdAt
documentAccesses(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
edges {
node {
id
active
createdAt
updatedAt
document {
id
title
documentType
}
report {
id
filename
audit {
id
framework {
name
}
}
}
trustCenterFile {
id
name
category
}
}
}
}
lastTokenExpiresAt
pendingRequestCount
activeCount
}
}
}
@@ -87,36 +60,9 @@ export const createTrustCenterAccessMutation = graphql`
active
hasAcceptedNonDisclosureAgreement
createdAt
documentAccesses(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
edges {
node {
id
active
createdAt
updatedAt
document {
id
title
documentType
}
report {
id
filename
audit {
id
framework {
name
}
}
}
trustCenterFile {
id
name
category
}
}
}
}
lastTokenExpiresAt
pendingRequestCount
activeCount
}
}
}
@@ -136,13 +82,35 @@ export const updateTrustCenterAccessMutation = graphql`
hasAcceptedNonDisclosureAgreement
createdAt
updatedAt
documentAccesses(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
lastTokenExpiresAt
pendingRequestCount
activeCount
}
}
}
`;
export const deleteTrustCenterAccessMutation = graphql`
mutation TrustCenterAccessGraphDeleteMutation(
$input: DeleteTrustCenterAccessInput!
$connections: [ID!]!
) {
deleteTrustCenterAccess(input: $input) {
deletedTrustCenterAccessId @deleteEdge(connections: $connections)
}
}
`;
export const loadTrustCenterAccessDocumentAccessesQuery = graphql`
query TrustCenterAccessGraphLoadDocumentAccessesQuery($accessId: ID!) {
node(id: $accessId) {
... on TrustCenterAccess {
id
availableDocumentAccesses(first: 100, orderBy: { field: CREATED_AT, direction: DESC }) {
edges {
node {
id
active
createdAt
updatedAt
requested
document {
id
title
@@ -171,17 +139,6 @@ export const updateTrustCenterAccessMutation = graphql`
}
`;
export const deleteTrustCenterAccessMutation = graphql`
mutation TrustCenterAccessGraphDeleteMutation(
$input: DeleteTrustCenterAccessInput!
$connections: [ID!]!
) {
deleteTrustCenterAccess(input: $input) {
deletedTrustCenterAccessId @deleteEdge(connections: $connections)
}
}
`;
interface PaginatedData {
data: { node: any } | null;
hasNext: boolean;
@@ -197,7 +154,7 @@ export function useTrustCenterAccesses(trustCenterId: string): PaginatedData {
count: 10,
cursor: null
},
{ fetchPolicy: 'network-only' }
{ fetchPolicy: 'store-and-network' }
);
if (!trustCenterId) {

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<d527603a814e765a1a2be0900d1c59e1>>
* @generated SignedSource<<271260153dc611541b234266fbdd0c74>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE";
export type CreateTrustCenterAccessInput = {
active: boolean;
email: string;
@@ -26,41 +25,14 @@ export type TrustCenterAccessGraphCreateMutation$data = {
readonly cursor: any;
readonly node: {
readonly active: boolean;
readonly activeCount: number;
readonly createdAt: any;
readonly documentAccesses: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly active: boolean;
readonly createdAt: any;
readonly document: {
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
} | null | undefined;
readonly id: string;
readonly report: {
readonly audit: {
readonly framework: {
readonly name: string;
};
readonly id: string;
} | null | undefined;
readonly filename: string;
readonly id: string;
} | null | undefined;
readonly trustCenterFile: {
readonly category: string;
readonly id: string;
readonly name: string;
} | null | undefined;
readonly updatedAt: any;
};
}>;
};
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly lastTokenExpiresAt: any | null | undefined;
readonly name: string;
readonly pendingRequestCount: number;
};
};
};
@@ -91,122 +63,90 @@ v2 = [
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v10 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"concreteType": "Document",
"concreteType": "TrustCenterAccessEdge",
"kind": "LinkedField",
"name": "document",
"name": "trustCenterAccessEdge",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
v13 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
v14 = {
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v4/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastTokenExpiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
}
],
"storageKey": null
}
],
@@ -230,111 +170,7 @@ return {
"name": "createTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccessEdge",
"kind": "LinkedField",
"name": "trustCenterAccessEdge",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": (v10/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v7/*: any*/),
(v9/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v4/*: any*/),
(v13/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v6/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v14/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"storageKey": null
}
],
"storageKey": null
}
(v3/*: any*/)
],
"storageKey": null
}
@@ -359,112 +195,7 @@ return {
"name": "createTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccessEdge",
"kind": "LinkedField",
"name": "trustCenterAccessEdge",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": (v10/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v7/*: any*/),
(v9/*: any*/),
(v11/*: any*/),
(v12/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v4/*: any*/),
(v13/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v6/*: any*/),
(v4/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v14/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"storageKey": null
}
],
"storageKey": null
},
(v3/*: any*/),
{
"alias": null,
"args": null,
@@ -487,16 +218,16 @@ return {
]
},
"params": {
"cacheID": "4422a02632b77f81f3b5048b5acbac98",
"cacheID": "501c3c38f8d44e41a178514443803ce2",
"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 hasAcceptedNonDisclosureAgreement\n createdAt\n documentAccesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n active\n createdAt\n updatedAt\n document {\n id\n title\n documentType\n }\n report {\n id\n filename\n audit {\n id\n framework {\n name\n id\n }\n }\n }\n trustCenterFile {\n id\n name\n category\n }\n }\n }\n }\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 lastTokenExpiresAt\n pendingRequestCount\n activeCount\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "c09c8a22a45226949a08f2262ddd0b1c";
(node as any).hash = "bfe1841f269ba04a77e9b3ecf73665d2";
export default node;

View File

@@ -0,0 +1,391 @@
/**
* @generated SignedSource<<ef22c40e1c881ddd440a0468bae615f9>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE";
export type TrustCenterAccessGraphLoadDocumentAccessesQuery$variables = {
accessId: string;
};
export type TrustCenterAccessGraphLoadDocumentAccessesQuery$data = {
readonly node: {
readonly availableDocumentAccesses?: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly active: boolean;
readonly document: {
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
} | null | undefined;
readonly report: {
readonly audit: {
readonly framework: {
readonly name: string;
};
readonly id: string;
} | null | undefined;
readonly filename: string;
readonly id: string;
} | null | undefined;
readonly requested: boolean;
readonly trustCenterFile: {
readonly category: string;
readonly id: string;
readonly name: string;
} | null | undefined;
};
}>;
};
readonly id?: string;
};
};
export type TrustCenterAccessGraphLoadDocumentAccessesQuery = {
response: TrustCenterAccessGraphLoadDocumentAccessesQuery$data;
variables: TrustCenterAccessGraphLoadDocumentAccessesQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "accessId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "accessId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "requested",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "document",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v2/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterAccessGraphLoadDocumentAccessesQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"kind": "InlineFragment",
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "availableDocumentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v2/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v8/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v9/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "availableDocumentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"type": "TrustCenterAccess",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterAccessGraphLoadDocumentAccessesQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v3/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "availableDocumentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v2/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v8/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v9/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "availableDocumentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"type": "TrustCenterAccess",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "50de5f19f1c13ee4d4a0b125231b989f",
"id": null,
"metadata": {},
"name": "TrustCenterAccessGraphLoadDocumentAccessesQuery",
"operationKind": "query",
"text": "query TrustCenterAccessGraphLoadDocumentAccessesQuery(\n $accessId: ID!\n) {\n node(id: $accessId) {\n __typename\n ... on TrustCenterAccess {\n id\n availableDocumentAccesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n active\n requested\n document {\n id\n title\n documentType\n }\n report {\n id\n filename\n audit {\n id\n framework {\n name\n id\n }\n }\n }\n trustCenterFile {\n id\n name\n category\n }\n }\n }\n }\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "64c69558e335d4cecb14192d8c5b66b8";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<8c2482b7d32c42396e4645bd309e7cc2>>
* @generated SignedSource<<fedd4f94d88d48ba3152491a5c0101f4>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -64,15 +64,7 @@ v3 = {
"name": "id",
"storageKey": null
},
v4 = {
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
},
v5 = [
v4 = [
{
"kind": "Variable",
"name": "after",
@@ -83,29 +75,15 @@ v5 = [
"name": "first",
"variableName": "count"
},
(v4/*: any*/)
],
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
@@ -154,7 +132,7 @@ return {
"selections": [
{
"alias": null,
"args": (v5/*: any*/),
"args": (v4/*: any*/),
"concreteType": "TrustCenterAccessConnection",
"kind": "LinkedField",
"name": "accesses",
@@ -230,8 +208,20 @@ return {
"name": "email",
"storageKey": null
},
(v6/*: any*/),
(v7/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -239,146 +229,33 @@ return {
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
(v8/*: any*/),
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
},
(v4/*: any*/)
],
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "document",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v6/*: any*/),
(v3/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v3/*: any*/),
(v6/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastTokenExpiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
},
(v2/*: any*/)
],
@@ -404,7 +281,7 @@ return {
},
{
"alias": null,
"args": (v5/*: any*/),
"args": (v4/*: any*/),
"filters": [
"orderBy"
],
@@ -423,16 +300,16 @@ return {
]
},
"params": {
"cacheID": "12322ab403d03a821d2228ff68c3d191",
"cacheID": "98a87a363a8a26e30506346db052e11a",
"id": null,
"metadata": {},
"name": "TrustCenterAccessGraphPaginationQuery",
"operationKind": "query",
"text": "query TrustCenterAccessGraphPaginationQuery(\n $count: Int\n $cursor: CursorKey\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...TrustCenterAccessGraph_accesses\n id\n }\n}\n\nfragment TrustCenterAccessGraph_accesses on TrustCenter {\n accesses(first: $count, after: $cursor, 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 documentAccesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n active\n createdAt\n updatedAt\n document {\n id\n title\n documentType\n }\n report {\n id\n filename\n audit {\n id\n framework {\n name\n id\n }\n }\n }\n trustCenterFile {\n id\n name\n category\n }\n }\n }\n }\n __typename\n }\n }\n }\n id\n}\n"
"text": "query TrustCenterAccessGraphPaginationQuery(\n $count: Int\n $cursor: CursorKey\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...TrustCenterAccessGraph_accesses\n id\n }\n}\n\nfragment TrustCenterAccessGraph_accesses on TrustCenter {\n accesses(first: $count, after: $cursor, 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 lastTokenExpiresAt\n pendingRequestCount\n activeCount\n __typename\n }\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "9c7f3e866e2593c169b1f98d0e637667";
(node as any).hash = "10bb084980182a1e2334be77c29cf1ec";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<e0c9c25c78bb6c8cf63eb9437947a013>>
* @generated SignedSource<<d13da95487a7fd3dafa22397092b134c>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -63,15 +63,7 @@ v5 = {
"name": "__typename",
"storageKey": null
},
v6 = {
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
},
v7 = [
v6 = [
{
"kind": "Variable",
"name": "after",
@@ -82,29 +74,15 @@ v7 = [
"name": "first",
"variableName": "count"
},
(v6/*: any*/)
],
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v9 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v10 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
];
return {
"fragment": {
"argumentDefinitions": [
@@ -169,7 +147,7 @@ return {
"selections": [
{
"alias": null,
"args": (v7/*: any*/),
"args": (v6/*: any*/),
"concreteType": "TrustCenterAccessConnection",
"kind": "LinkedField",
"name": "accesses",
@@ -245,8 +223,20 @@ return {
"name": "email",
"storageKey": null
},
(v8/*: any*/),
(v9/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -254,146 +244,33 @@ return {
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
(v10/*: any*/),
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
},
(v6/*: any*/)
],
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "document",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v8/*: any*/),
(v4/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v4/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastTokenExpiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
},
(v5/*: any*/)
],
@@ -419,7 +296,7 @@ return {
},
{
"alias": null,
"args": (v7/*: any*/),
"args": (v6/*: any*/),
"filters": [
"orderBy"
],
@@ -438,12 +315,12 @@ return {
]
},
"params": {
"cacheID": "37deb80fc3ff8892d2e68f7588da4e55",
"cacheID": "703f94692a1696b15e0c92b282866673",
"id": null,
"metadata": {},
"name": "TrustCenterAccessGraphQuery",
"operationKind": "query",
"text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n $count: Int!\n $cursor: CursorKey\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n ...TrustCenterAccessGraph_accesses\n }\n id\n }\n}\n\nfragment TrustCenterAccessGraph_accesses on TrustCenter {\n accesses(first: $count, after: $cursor, 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 documentAccesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n active\n createdAt\n updatedAt\n document {\n id\n title\n documentType\n }\n report {\n id\n filename\n audit {\n id\n framework {\n name\n id\n }\n }\n }\n trustCenterFile {\n id\n name\n category\n }\n }\n }\n }\n __typename\n }\n }\n }\n id\n}\n"
"text": "query TrustCenterAccessGraphQuery(\n $trustCenterId: ID!\n $count: Int!\n $cursor: CursorKey\n) {\n node(id: $trustCenterId) {\n __typename\n ... on TrustCenter {\n id\n ...TrustCenterAccessGraph_accesses\n }\n id\n }\n}\n\nfragment TrustCenterAccessGraph_accesses on TrustCenter {\n accesses(first: $count, after: $cursor, 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 lastTokenExpiresAt\n pendingRequestCount\n activeCount\n __typename\n }\n }\n }\n id\n}\n"
}
};
})();

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<8ecf71138aa0165738506f5e589d3181>>
* @generated SignedSource<<25b79c235754d47097b2ca28f1f3e9a0>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE";
export type UpdateTrustCenterAccessInput = {
active?: boolean | null | undefined;
documentIds?: ReadonlyArray<string> | null | undefined;
@@ -25,41 +24,14 @@ export type TrustCenterAccessGraphUpdateMutation$data = {
readonly updateTrustCenterAccess: {
readonly trustCenterAccess: {
readonly active: boolean;
readonly activeCount: number;
readonly createdAt: any;
readonly documentAccesses: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly active: boolean;
readonly createdAt: any;
readonly document: {
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
} | null | undefined;
readonly id: string;
readonly report: {
readonly audit: {
readonly framework: {
readonly name: string;
};
readonly id: string;
} | null | undefined;
readonly filename: string;
readonly id: string;
} | null | undefined;
readonly trustCenterFile: {
readonly category: string;
readonly id: string;
readonly name: string;
} | null | undefined;
readonly updatedAt: any;
};
}>;
};
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly lastTokenExpiresAt: any | null | undefined;
readonly name: string;
readonly pendingRequestCount: number;
readonly updatedAt: any;
};
};
@@ -79,241 +51,111 @@ var v0 = [
],
v1 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v6 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
v7 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
v8 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
v9 = [
{
"kind": "Literal",
"name": "first",
"value": 100
},
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
v10 = {
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "document",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v2/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterAccessGraphUpdateMutation",
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateTrustCenterAccessPayload",
"kind": "LinkedField",
"name": "updateTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "UpdateTrustCenterAccessPayload",
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "updateTrustCenterAccess",
"name": "trustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": (v9/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v5/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v10/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v4/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastTokenExpiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "TrustCenterAccessGraphUpdateMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
@@ -322,126 +164,19 @@ return {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "TrustCenterAccessGraphUpdateMutation",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": "UpdateTrustCenterAccessPayload",
"kind": "LinkedField",
"name": "updateTrustCenterAccess",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterAccess",
"kind": "LinkedField",
"name": "trustCenterAccess",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v6/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
{
"alias": null,
"args": (v9/*: any*/),
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v5/*: any*/),
(v7/*: any*/),
(v8/*: any*/),
(v10/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v2/*: any*/),
(v11/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v4/*: any*/),
(v2/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
(v12/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
}
],
"storageKey": null
}
],
"storageKey": null
}
]
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "b66da9236d9f81563608b3aa5f730908",
"cacheID": "8b330240bc19bbebb3244a49dc0a6db0",
"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 hasAcceptedNonDisclosureAgreement\n createdAt\n updatedAt\n documentAccesses(first: 100, orderBy: {field: CREATED_AT, direction: DESC}) {\n edges {\n node {\n id\n active\n createdAt\n updatedAt\n document {\n id\n title\n documentType\n }\n report {\n id\n filename\n audit {\n id\n framework {\n name\n id\n }\n }\n }\n trustCenterFile {\n id\n name\n category\n }\n }\n }\n }\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 lastTokenExpiresAt\n pendingRequestCount\n activeCount\n }\n }\n}\n"
}
};
})();
(node as any).hash = "43e73cc9d80e898f1ba2a92eaf4646f5";
(node as any).hash = "f78b4d25b0977a14269491db951a1d9d";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<d572a906e6afd89e4a3f1dcffa5c0528>>
* @generated SignedSource<<a3da155281753945ab0645a04f0b74fb>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -9,7 +9,6 @@
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type DocumentType = "ISMS" | "OTHER" | "POLICY" | "PROCEDURE";
import { FragmentRefs } from "relay-runtime";
export type TrustCenterAccessGraph_accesses$data = {
readonly accesses: {
@@ -18,41 +17,14 @@ export type TrustCenterAccessGraph_accesses$data = {
readonly cursor: any;
readonly node: {
readonly active: boolean;
readonly activeCount: number;
readonly createdAt: any;
readonly documentAccesses: {
readonly edges: ReadonlyArray<{
readonly node: {
readonly active: boolean;
readonly createdAt: any;
readonly document: {
readonly documentType: DocumentType;
readonly id: string;
readonly title: string;
} | null | undefined;
readonly id: string;
readonly report: {
readonly audit: {
readonly framework: {
readonly name: string;
};
readonly id: string;
} | null | undefined;
readonly filename: string;
readonly id: string;
} | null | undefined;
readonly trustCenterFile: {
readonly category: string;
readonly id: string;
readonly name: string;
} | null | undefined;
readonly updatedAt: any;
};
}>;
};
readonly email: string;
readonly hasAcceptedNonDisclosureAgreement: boolean;
readonly id: string;
readonly lastTokenExpiresAt: any | null | undefined;
readonly name: string;
readonly pendingRequestCount: number;
};
}>;
readonly pageInfo: {
@@ -77,40 +49,11 @@ var v0 = [
"accesses"
],
v1 = {
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
},
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
v5 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
};
return {
"argumentDefinitions": [
@@ -157,7 +100,14 @@ return {
{
"alias": "accesses",
"args": [
(v1/*: any*/)
{
"kind": "Literal",
"name": "orderBy",
"value": {
"direction": "DESC",
"field": "CREATED_AT"
}
}
],
"concreteType": "TrustCenterAccessConnection",
"kind": "LinkedField",
@@ -226,7 +176,7 @@ return {
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v1/*: any*/),
{
"alias": null,
"args": null,
@@ -234,8 +184,20 @@ return {
"name": "email",
"storageKey": null
},
(v3/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "active",
"storageKey": null
},
{
"alias": null,
"args": null,
@@ -243,145 +205,33 @@ return {
"name": "hasAcceptedNonDisclosureAgreement",
"storageKey": null
},
(v5/*: any*/),
{
"alias": null,
"args": [
{
"kind": "Literal",
"name": "first",
"value": 100
},
(v1/*: any*/)
],
"concreteType": "TrustCenterDocumentAccessConnection",
"kind": "LinkedField",
"name": "documentAccesses",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccessEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "TrustCenterDocumentAccess",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Document",
"kind": "LinkedField",
"name": "document",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "title",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "documentType",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Report",
"kind": "LinkedField",
"name": "report",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "filename",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "Audit",
"kind": "LinkedField",
"name": "audit",
"plural": false,
"selections": [
(v2/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "Framework",
"kind": "LinkedField",
"name": "framework",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "TrustCenterFile",
"kind": "LinkedField",
"name": "trustCenterFile",
"plural": false,
"selections": [
(v2/*: any*/),
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "category",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": "documentAccesses(first:100,orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "lastTokenExpiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "pendingRequestCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "activeCount",
"storageKey": null
},
{
"alias": null,
@@ -411,13 +261,13 @@ return {
],
"storageKey": "__TrustCenterAccessGraph_accesses_connection(orderBy:{\"direction\":\"DESC\",\"field\":\"CREATED_AT\"})"
},
(v2/*: any*/)
(v1/*: any*/)
],
"type": "TrustCenter",
"abstractKey": null
};
})();
(node as any).hash = "9c7f3e866e2593c169b1f98d0e637667";
(node as any).hash = "10bb084980182a1e2334be77c29cf1ec";
export default node;

View File

@@ -12,7 +12,6 @@ import {
IconInboxEmpty,
IconPageTextLine,
IconSettingsGear2,
IconCheckmark1,
IconStore,
IconTodo,
IconListStack,
@@ -20,6 +19,7 @@ import {
IconShield,
IconRotateCw,
IconCircleProgress,
IconMedal,
Layout,
SidebarItem,
UserDropdown as UserDropdownRoot,
@@ -169,7 +169,7 @@ export function MainLayout() {
/>
<SidebarItem
label={__("Audits")}
icon={IconCheckmark1}
icon={IconMedal}
to={`${prefix}/audits`}
/>
<SidebarItem

View File

@@ -1,7 +1,6 @@
import {
Badge,
Button,
Card,
Checkbox,
Dialog,
DialogContent,
@@ -20,19 +19,21 @@ import {
IconCheckmark1,
IconCrossLargeX,
IconChevronDown,
IconPlusLarge,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { formatDate } from "@probo/helpers";
import { useOutletContext } from "react-router";
import { useState, useCallback, useEffect } from "react";
import { useState, useCallback, useEffect, useRef } from "react";
import { useQueryLoader, usePreloadedQuery } from 'react-relay';
import z from "zod";
import {
useTrustCenterAccesses,
createTrustCenterAccessMutation,
updateTrustCenterAccessMutation,
deleteTrustCenterAccessMutation
deleteTrustCenterAccessMutation,
loadTrustCenterAccessDocumentAccessesQuery
} from "/hooks/graph/TrustCenterAccessGraph";
import type { TrustCenterAccessGraph_accesses$data } from "/hooks/graph/__generated__/TrustCenterAccessGraph_accesses.graphql";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
@@ -42,9 +43,89 @@ type ContextType = {
trustCenter?: {
id: string;
};
documents?: {
edges: Array<{
node: {
id: string;
title: string;
documentType: string;
trustCenterVisibility: string;
};
}>;
};
audits?: {
edges: Array<{
node: {
id: string;
filename: string;
trustCenterVisibility: string;
framework: {
name: string;
};
};
}>;
};
trustCenterFiles?: {
edges: Array<{
node: {
id: string;
name: string;
category: string;
trustCenterVisibility: string;
};
}>;
};
};
};
type DocumentAccessInfo = {
id: string;
active: boolean;
requested: boolean;
document?: {
id: string;
title: string;
documentType: string;
} | null;
report?: {
id: string;
filename: string;
audit: {
id: string;
framework: {
name: string;
};
};
} | null;
trustCenterFile?: {
id: string;
name: string;
category: string;
} | null;
};
function DocumentAccessesLoader({
queryReference,
onDataLoaded
}: {
queryReference: any;
onDataLoaded: (documentAccesses: DocumentAccessInfo[]) => void;
}) {
const data = usePreloadedQuery(loadTrustCenterAccessDocumentAccessesQuery, queryReference);
useEffect(() => {
if (data && typeof data === 'object' && 'node' in data) {
const node = (data as any).node;
if (node?.availableDocumentAccesses?.edges) {
const documentAccesses = node.availableDocumentAccesses.edges.map((edge: any) => edge.node);
onDataLoaded(documentAccesses);
}
}
}, [data, onDataLoaded]);
return null;
}
export default function TrustCenterAccessTab() {
const { __ } = useTranslate();
const { organization } = useOutletContext<ContextType>();
@@ -75,10 +156,35 @@ export default function TrustCenterAccessTab() {
const dialogRef = useDialogRef();
const editDialogRef = useDialogRef();
const [editingAccess, setEditingAccess] = useState<AccessType | null>(null);
const [editingDocumentAccesses, setEditingDocumentAccesses] = useState<DocumentAccessType[]>([]);
const [selectedDocumentAccesses, setSelectedDocumentAccesses] = useState<Set<string>>(new Set());
const [pendingEditEmail, setPendingEditEmail] = useState<string | null>(null);
const [documentAccessesQueryReference, loadDocumentAccessesQuery] = useQueryLoader(loadTrustCenterAccessDocumentAccessesQuery);
const loadedAccessIdRef = useRef<string | null>(null);
const [isLoadingDocumentAccesses, setIsLoadingDocumentAccesses] = useState(false);
const formattedDocumentAccesses = editingAccess?.documentAccesses
useEffect(() => {
if (editingAccess?.id && loadedAccessIdRef.current !== editingAccess.id) {
loadedAccessIdRef.current = editingAccess.id;
setIsLoadingDocumentAccesses(true);
loadDocumentAccessesQuery({ accessId: editingAccess.id }, { fetchPolicy: 'network-only' });
}
}, [editingAccess?.id, loadDocumentAccessesQuery]);
const handleDocumentAccessesLoaded = useCallback((documentAccesses: DocumentAccessInfo[]) => {
setEditingDocumentAccesses(documentAccesses);
const activeIds = new Set<string>(
documentAccesses
.filter((docAccess) => docAccess.active)
.map((docAccess) => docAccess.document?.id || docAccess.report?.id || docAccess.trustCenterFile?.id)
.filter((id: unknown): id is string => typeof id === 'string')
);
setSelectedDocumentAccesses(activeIds);
setIsLoadingDocumentAccesses(false);
}, []);
const formattedDocumentAccesses = editingDocumentAccesses
?.map((docAccess) => getDocumentAccessInfo(docAccess, __))
?.filter((info) => info !== null) ?? [];
@@ -93,6 +199,7 @@ export default function TrustCenterAccessTab() {
type DocumentAccessType = {
id: string;
active: boolean;
requested: boolean;
document?: {
id: string;
title: string;
@@ -126,6 +233,8 @@ export default function TrustCenterAccessTab() {
type: __("Document"),
category: docAccess.document?.documentType,
id: docAccess.document?.id,
requested: docAccess.requested,
active: docAccess.active,
};
}
if (!!docAccess.report) {
@@ -135,6 +244,8 @@ export default function TrustCenterAccessTab() {
type: __("Report"),
category: docAccess.report?.audit?.framework?.name,
id: docAccess.report?.id,
requested: docAccess.requested,
active: docAccess.active,
};
}
if (!!docAccess.trustCenterFile) {
@@ -144,6 +255,8 @@ export default function TrustCenterAccessTab() {
type: __("File"),
category: docAccess.trustCenterFile?.category,
id: docAccess.trustCenterFile?.id,
requested: docAccess.requested,
active: docAccess.active,
};
}
@@ -157,28 +270,24 @@ export default function TrustCenterAccessTab() {
active: boolean;
hasAcceptedNonDisclosureAgreement: boolean;
createdAt: string;
documentAccesses: DocumentAccessType[];
lastTokenExpiresAt: string | null;
pendingRequestCount: number;
activeCount: number;
documentAccesses?: DocumentAccessType[];
};
const { data: trustCenterData, loadMore, hasNext, isLoadingNext } = useTrustCenterAccesses(organization.trustCenter?.id || "");
type AccessEdge = NonNullable<NonNullable<TrustCenterAccessGraph_accesses$data['accesses']>['edges']>[number];
type DocumentAccessEdge = NonNullable<NonNullable<NonNullable<AccessEdge>['node']['documentAccesses']>['edges']>[number];
const accesses: AccessType[] = trustCenterData?.node?.accesses?.edges?.map((edge: AccessEdge) => ({
const accesses: AccessType[] = trustCenterData?.node?.accesses?.edges?.map((edge: any) => ({
id: edge.node.id,
email: edge.node.email,
name: edge.node.name,
active: edge.node.active,
hasAcceptedNonDisclosureAgreement: edge.node.hasAcceptedNonDisclosureAgreement,
createdAt: edge.node.createdAt,
documentAccesses: edge.node.documentAccesses?.edges?.map((docEdge: DocumentAccessEdge) => ({
id: docEdge.node.id,
active: docEdge.node.active,
document: docEdge.node.document,
report: docEdge.node.report,
trustCenterFile: docEdge.node.trustCenterFile
})) ?? []
lastTokenExpiresAt: edge.node.lastTokenExpiresAt,
pendingRequestCount: edge.node.pendingRequestCount || 0,
activeCount: edge.node.activeCount || 0,
})) ?? [];
@@ -201,8 +310,6 @@ export default function TrustCenterAccessTab() {
connections: connectionId ? [connectionId] : [],
},
onSuccess: () => {
dialogRef.current?.close();
inviteForm.reset();
setPendingEditEmail(email);
},
});
@@ -220,32 +327,37 @@ export default function TrustCenterAccessTab() {
}, [deleteInvitation, trustCenterData]);
const getActiveDocumentIds = useCallback((access: AccessType) => {
return access.documentAccesses
.filter(docAccess => docAccess.active)
.map(docAccess => docAccess.document?.id || docAccess.report?.id || docAccess.trustCenterFile?.id)
.filter((id): id is string => id !== undefined);
}, []);
const handleEditAccess = useCallback((access: AccessType) => {
loadedAccessIdRef.current = null;
setEditingAccess(access);
setEditingDocumentAccesses([]);
setSelectedDocumentAccesses(new Set());
setIsLoadingDocumentAccesses(false);
editForm.reset({ name: access.name, active: access.active });
setSelectedDocumentAccesses(new Set(getActiveDocumentIds(access)));
editDialogRef.current?.open();
}, [editDialogRef, editForm, getActiveDocumentIds]);
}, [editDialogRef, editForm]);
useEffect(() => {
if (pendingEditEmail && accesses.length > 0) {
const newAccess = accesses.find(access => access.email === pendingEditEmail);
if (newAccess) {
setPendingEditEmail(null);
loadedAccessIdRef.current = null;
setEditingAccess(newAccess);
editForm.reset({ name: newAccess.name, active: true });
setSelectedDocumentAccesses(new Set(getActiveDocumentIds(newAccess)));
setEditingDocumentAccesses([]);
setSelectedDocumentAccesses(new Set());
editDialogRef.current?.open();
setTimeout(() => {
dialogRef.current?.close();
}, 50);
setTimeout(() => {
inviteForm.reset();
}, 300);
}
}
}, [accesses, pendingEditEmail, editForm, editDialogRef, getActiveDocumentIds]);
}, [accesses, pendingEditEmail, editForm, dialogRef, editDialogRef, inviteForm]);
const handleToggleDocumentAccess = useCallback((documentId: string, active: boolean) => {
setSelectedDocumentAccesses(prev => {
@@ -262,7 +374,7 @@ export default function TrustCenterAccessTab() {
const handleUpdateName = editForm.handleSubmit(async (data) => {
if (!editingAccess) return;
const { documentIds, reportIds, trustCenterFileIds } = editingAccess.documentAccesses.reduce(
const { documentIds, reportIds, trustCenterFileIds } = editingDocumentAccesses.reduce(
(acc, docAccess) => {
const id = docAccess.document?.id || docAccess.report?.id || docAccess.trustCenterFile?.id;
if (id && selectedDocumentAccesses.has(id)) {
@@ -294,6 +406,7 @@ export default function TrustCenterAccessTab() {
editDialogRef.current?.close();
setEditingAccess(null);
editForm.reset();
setEditingDocumentAccesses([]);
setSelectedDocumentAccesses(new Set());
},
});
@@ -309,7 +422,7 @@ export default function TrustCenterAccessTab() {
</p>
</div>
{organization.trustCenter?.id && (
<Button onClick={() => {
<Button icon={IconPlusLarge} onClick={() => {
inviteForm.reset();
dialogRef.current?.open();
}}>
@@ -318,126 +431,151 @@ export default function TrustCenterAccessTab() {
)}
</div>
<Card padded>
{!organization.trustCenter?.id ? (
<div className="text-center text-txt-tertiary py-8">
<Spinner />
</div>
) : accesses.length === 0 ? (
<div className="text-center text-txt-tertiary py-8">
{__("No external access granted yet")}
</div>
) : (
<>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Email")}</Th>
<Th>{__("Date")}</Th>
<Th>{__("Active")}</Th>
<Th>{__("Documents")}</Th>
<Th>{__("NDA")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{accesses.map((access) => {
const activeDocuments = access.documentAccesses.filter(doc => doc.active).length;
const totalDocuments = access.documentAccesses.length;
{!organization.trustCenter?.id ? (
<Table>
<Tbody>
<Tr>
<Td className="text-center text-txt-tertiary py-8">
<Spinner />
</Td>
</Tr>
</Tbody>
</Table>
) : accesses.length === 0 ? (
<Table>
<Tbody>
<Tr>
<Td className="text-center text-txt-tertiary py-8">
{__("No external access granted yet")}
</Td>
</Tr>
</Tbody>
</Table>
) : (
<>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Email")}</Th>
<Th>{__("Date")}</Th>
<Th>{__("Expires")}</Th>
<Th className="text-center">{__("Active")}</Th>
<Th className="text-center">{__("Access")}</Th>
<Th className="text-center">{__("Requests")}</Th>
<Th className="text-center">{__("NDA")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{accesses.map((access) => {
const isExpired = access.lastTokenExpiresAt ? new Date(access.lastTokenExpiresAt) < new Date() : false;
return (
<Tr
key={access.id}
onClick={() => handleEditAccess(access)}
className="cursor-pointer hover:bg-bg-secondary transition-colors"
>
<Td className="font-medium">{access.name}</Td>
<Td>{access.email}</Td>
<Td>
{formatDate(access.createdAt)}
</Td>
<Td>
return (
<Tr
key={access.id}
onClick={() => handleEditAccess(access)}
className="cursor-pointer hover:bg-bg-secondary transition-colors"
>
<Td className="font-medium">{access.name}</Td>
<Td>{access.email}</Td>
<Td>
{formatDate(access.createdAt)}
</Td>
<Td className={isExpired ? "text-txt-danger" : ""}>
{access.lastTokenExpiresAt ? formatDate(access.lastTokenExpiresAt) : "-"}
</Td>
<Td>
<div className="flex justify-center">
{access.active ? (
<IconCheckmark1 size={16} className="text-txt-success" />
) : (
<IconCrossLargeX size={16} className="text-txt-danger" />
)}
</Td>
<Td>
{totalDocuments > 0 ? `${activeDocuments}/${totalDocuments}` : '0/0'}
</Td>
<Td>
</div>
</Td>
<Td className="text-center">
{access.activeCount}
</Td>
<Td className="text-center">
{access.pendingRequestCount > 0 ? access.pendingRequestCount : ""}
</Td>
<Td>
<div className="flex justify-center">
{access.hasAcceptedNonDisclosureAgreement && (
<IconCheckmark1 size={16} className="text-txt-success" />
)}
</Td>
<Td noLink width={160} className="text-end">
<div
className="flex gap-2 justify-end"
onClick={(e) => e.stopPropagation()}
>
<Button
variant="secondary"
onClick={() => handleEditAccess(access)}
disabled={isUpdating}
icon={IconPencil}
/>
<Button
variant="secondary"
onClick={() => handleDelete(access.id)}
disabled={isDeleting}
icon={IconTrashCan}
/>
</div>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
{hasNext && (
<Button
variant="tertiary"
onClick={loadMore}
disabled={isLoadingNext}
className="mt-3 mx-auto"
icon={IconChevronDown}
>
{isLoadingNext && <Spinner />}
{__("Show More")}
</Button>
)}
</>
)}
</Card>
<Td noLink width={160} className="text-end">
<div
className="flex gap-2 justify-end"
onClick={(e) => e.stopPropagation()}
>
<Button
variant="secondary"
onClick={() => handleEditAccess(access)}
disabled={isUpdating}
icon={IconPencil}
/>
<Button
variant="danger"
onClick={() => handleDelete(access.id)}
disabled={isDeleting}
icon={IconTrashCan}
/>
</div>
</Td>
</Tr>
);
})}
</Tbody>
</Table>
{hasNext && (
<Button
variant="tertiary"
onClick={loadMore}
disabled={isLoadingNext}
className="mt-3 mx-auto"
icon={IconChevronDown}
>
{isLoadingNext && <Spinner />}
{__("Show More")}
</Button>
)}
</>
)}
<Dialog
ref={dialogRef}
title={__("Invite External Access")}
>
<form onSubmit={handleInvite}>
<DialogContent padded className="space-y-4">
<p className="text-txt-secondary text-sm">
{__("Send a 7-day access token to an external person to view your trust center")}
</p>
<DialogContent padded className="space-y-6">
<div>
<p className="text-txt-secondary text-sm mb-4">
{__("Send a 30-day access token to an external person to view your trust center")}
</p>
<Field
label={__("Full Name")}
required
error={inviteForm.formState.errors.name?.message}
{...inviteForm.register("name")}
placeholder={__("John Doe")}
/>
<Field
label={__("Full Name")}
required
error={inviteForm.formState.errors.name?.message}
{...inviteForm.register("name")}
placeholder={__("John Doe")}
/>
<Field
label={__("Email Address")}
required
error={inviteForm.formState.errors.email?.message}
type="email"
{...inviteForm.register("email")}
placeholder={__("john@example.com")}
/>
<div className="mt-4">
<Field
label={__("Email Address")}
required
error={inviteForm.formState.errors.email?.message}
type="email"
{...inviteForm.register("email")}
placeholder={__("john@example.com")}
/>
</div>
</div>
</DialogContent>
<DialogFooter>
@@ -453,6 +591,12 @@ export default function TrustCenterAccessTab() {
ref={editDialogRef}
title={__("Edit Access")}
>
{documentAccessesQueryReference && (
<DocumentAccessesLoader
queryReference={documentAccessesQueryReference}
onDataLoaded={handleDocumentAccessesLoaded}
/>
)}
<form onSubmit={handleUpdateName}>
<DialogContent padded className="space-y-6">
<div>
@@ -484,11 +628,37 @@ export default function TrustCenterAccessTab() {
</div>
</div>
{formattedDocumentAccesses.length > 0 && (
<div>
<h4 className="font-medium text-txt-primary mb-4">
<div>
<div className="flex justify-between items-center mb-4">
<h4 className="font-medium text-txt-primary">
{__("Document Access Permissions")}
</h4>
{!isLoadingDocumentAccesses && formattedDocumentAccesses.length > 0 && (
<Button
type="button"
variant="tertiary"
onClick={() => {
if (selectedDocumentAccesses.size === formattedDocumentAccesses.length) {
setSelectedDocumentAccesses(new Set());
} else {
const allIds = new Set(formattedDocumentAccesses.map(doc => doc.id).filter((id): id is string => !!id));
setSelectedDocumentAccesses(allIds);
}
}}
className="text-xs h-7 min-h-7"
>
{selectedDocumentAccesses.size === formattedDocumentAccesses.length
? __("Clear All")
: __("Select All")}
</Button>
)}
</div>
{isLoadingDocumentAccesses ? (
<div className="flex justify-center items-center py-12">
<Spinner />
</div>
) : formattedDocumentAccesses.length > 0 ? (
<div className="bg-bg-secondary rounded-lg overflow-hidden">
<Table>
<Thead>
@@ -496,6 +666,7 @@ export default function TrustCenterAccessTab() {
<Th>{__("Name")}</Th>
<Th>{__("Type")}</Th>
<Th>{__("Category")}</Th>
<Th></Th>
<Th>
<div className="flex justify-end">
{__("Access")}
@@ -505,7 +676,7 @@ export default function TrustCenterAccessTab() {
</Thead>
<Tbody>
{formattedDocumentAccesses.map((info) => {
const { variant, name, type, category, id } = info;
const { variant, name, type, category, id, requested } = info;
return (
<Tr key={id}>
@@ -524,6 +695,13 @@ export default function TrustCenterAccessTab() {
{category || "-"}
</div>
</Td>
<Td>
{requested && (
<Badge variant="warning">
{__("Requested")}
</Badge>
)}
</Td>
<Td>
<div className="flex justify-end">
<Checkbox
@@ -540,13 +718,17 @@ export default function TrustCenterAccessTab() {
</Tbody>
</Table>
</div>
</div>
)}
) : (
<div className="text-center text-txt-tertiary py-8">
{__("No documents available")}
</div>
)}
</div>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isUpdating}>
{isUpdating && <Spinner />}
<Button type="submit" disabled={isUpdating || isLoadingDocumentAccesses}>
{(isUpdating || isLoadingDocumentAccesses) && <Spinner />}
{__("Update Access")}
</Button>
</DialogFooter>

View File

@@ -1,4 +1,5 @@
import { Card, Spinner } from "@probo/ui";
import { Spinner } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
import { TrustCenterAuditsCard } from "/components/trustCenter/TrustCenterAuditsCard";
import { useTrustCenterAuditUpdate } from "/hooks/graph/TrustCenterAuditGraph";
@@ -15,6 +16,7 @@ type ContextType = {
};
export default function TrustCenterAuditsTab() {
const { __ } = useTranslate();
const { organization } = useOutletContext<ContextType>();
const [updateAuditVisibility, isUpdatingAudits] = useTrustCenterAuditUpdate();
@@ -23,17 +25,20 @@ export default function TrustCenterAuditsTab() {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-medium">{__("Audits")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Manage audit reports and compliance certifications")}
</p>
</div>
{isUpdatingAudits && <Spinner />}
</div>
<Card padded>
<TrustCenterAuditsCard
audits={audits}
params={{}}
disabled={isUpdatingAudits}
onChangeVisibility={updateAuditVisibility}
variant="table"
/>
</Card>
<TrustCenterAuditsCard
audits={audits}
params={{}}
disabled={isUpdatingAudits}
onChangeVisibility={updateAuditVisibility}
/>
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { Card, Spinner } from "@probo/ui";
import { Spinner } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
import { TrustCenterDocumentsCard } from "/components/trustCenter/TrustCenterDocumentsCard";
import { useUpdateDocumentVisibilityMutation } from "/hooks/graph/TrustCenterDocumentGraph";
@@ -15,6 +16,7 @@ type ContextType = {
};
export default function TrustCenterDocumentsTab() {
const { __ } = useTranslate();
const { organization } = useOutletContext<ContextType>();
const [updateDocumentVisibility, isUpdatingDocuments] = useUpdateDocumentVisibilityMutation();
@@ -23,17 +25,20 @@ export default function TrustCenterDocumentsTab() {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-medium">{__("Documents")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Manage policies, procedures and compliance documents")}
</p>
</div>
{isUpdatingDocuments && <Spinner />}
</div>
<Card padded>
<TrustCenterDocumentsCard
documents={documents}
params={{}}
disabled={isUpdatingDocuments}
onChangeVisibility={updateDocumentVisibility}
variant="table"
/>
</Card>
<TrustCenterDocumentsCard
documents={documents}
params={{}}
disabled={isUpdatingDocuments}
onChangeVisibility={updateDocumentVisibility}
/>
</div>
);
}

View File

@@ -1,6 +1,5 @@
import {
Button,
Card,
Dialog,
DialogContent,
DialogFooter,
@@ -10,6 +9,7 @@ import {
Dropzone,
Option,
Badge,
IconPlusLarge,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
@@ -200,7 +200,7 @@ export default function TrustCenterFilesTab() {
{__("Upload and manage files for your trust center")}
</p>
</div>
<Button onClick={() => createDialogRef.current?.open()}>
<Button icon={IconPlusLarge} onClick={() => createDialogRef.current?.open()}>
{__("Add File")}
</Button>
</div>
@@ -209,17 +209,14 @@ export default function TrustCenterFilesTab() {
<Spinner />
</div>
)}
<Card padded>
<TrustCenterFilesCard
files={files}
params={{}}
disabled={isUpdating || isDeleting}
onChangeVisibility={handleChangeVisibility}
onEdit={handleEdit}
onDelete={handleDeleteClick}
variant="table"
/>
</Card>
<TrustCenterFilesCard
files={files}
params={{}}
disabled={isUpdating || isDeleting}
onChangeVisibility={handleChangeVisibility}
onEdit={handleEdit}
onDelete={handleDeleteClick}
/>
<Dialog ref={createDialogRef} title={__("Add File")}>
<form onSubmit={handleCreate}>

View File

@@ -0,0 +1,248 @@
import { useTranslate } from "@probo/i18n";
import {
Button,
Card,
Checkbox,
Dropzone,
Spinner,
useToast,
IconTrashCan,
} from "@probo/ui";
import { useOutletContext } from "react-router";
import { useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
import type { TrustCenterGraphQuery$data } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
import { useState } from "react";
import { SlackConnections } from "../../../components/organizations/SlackConnection";
type ContextType = {
organization: TrustCenterGraphQuery$data["organization"];
};
export default function TrustCenterOverviewTab() {
const { __ } = useTranslate();
const { toast } = useToast();
const { organization } = useOutletContext<ContextType>();
const [updateTrustCenter, isUpdating] = useUpdateTrustCenterMutation();
const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation();
const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation();
const [isActive, setIsActive] = useState(organization.trustCenter?.active || false);
const handleToggleActive = async (active: boolean) => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
setIsActive(active);
updateTrustCenter({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
active,
},
},
onError: () => {
setIsActive(!active);
},
});
};
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?.id
? organization.customDomain?.domain
? `https://${organization.customDomain.domain}`
: `${window.location.origin}/trust/${organization.trustCenter.id}`
: null;
return (
<div className="space-y-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Trust Center Status")}</h2>
{isUpdating && <Spinner />}
</div>
<Card padded className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h3 className="font-medium">{__("Activate Trust Center")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Make your trust center publicly accessible to build customer confidence")}
</p>
</div>
<Checkbox
checked={isActive}
onChange={handleToggleActive}
/>
</div>
{isActive && trustCenterUrl && (
<div className="mt-4 p-4 bg-accent-light rounded-lg border border-accent">
<div className="flex items-center justify-between">
<div>
<h4 className="font-medium text-accent-dark">
{__("Your Trust Center is Live!")}
</h4>
<p className="text-sm text-accent-dark mt-1">
{__("Your customers can now access your trust center at:")}
</p>
<a
href={trustCenterUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm font-mono text-accent underline hover:no-underline"
>
{trustCenterUrl}
</a>
</div>
<Button
variant="secondary"
onClick={() => window.open(trustCenterUrl, '_blank', 'noopener,noreferrer')}
>
{__("View")}
</Button>
</div>
</div>
)}
{!isActive && (
<div className="mt-4 p-4 bg-tertiary rounded-lg border border-border-solid">
<h4 className="font-medium text-txt-secondary">
{__("Trust Center is Inactive")}
</h4>
<p className="text-sm text-txt-tertiary mt-1">
{__("Your trust center is currently not accessible to the public. Enable it to start sharing your compliance status.")}
</p>
</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">
<h2 className="text-base font-medium">{__("Integrations")}</h2>
<Card padded>
<SlackConnections
organizationId={organization.id!}
slackConnections={organization.slackConnections?.edges?.map((edge) => edge.node) ?? []}
/>
</Card>
</div>
</div>
);
}

View File

@@ -1,26 +1,23 @@
import { useTranslate } from "@probo/i18n";
import { usePageTitle } from "@probo/hooks";
import {
Button,
Card,
Checkbox,
Dropzone,
PageHeader,
Spinner,
useToast,
Tabs,
TabLink,
TabItem,
IconTrashCan,
Badge,
IconSettingsGear2,
IconMedal,
IconCheckmark1,
IconPageTextLine,
IconFolder2,
IconStore,
IconPeopleAdd,
} from "@probo/ui";
import { usePreloadedQuery, type PreloadedQuery } from "react-relay";
import { trustCenterQuery, useUpdateTrustCenterMutation, useUploadTrustCenterNDAMutation, useDeleteTrustCenterNDAMutation } from "/hooks/graph/TrustCenterGraph";
import { trustCenterQuery } from "/hooks/graph/TrustCenterGraph";
import type { TrustCenterGraphQuery } from "/hooks/graph/__generated__/TrustCenterGraphQuery.graphql";
import { useState } from "react";
import { useOrganizationId } from "/hooks/useOrganizationId";
import { Outlet, useLocation, Link } from "react-router";
import { TrustCenterReferencesSection } from "/components/trustCenter/TrustCenterReferencesSection";
import { SlackConnections } from "../../../components/organizations/SlackConnection";
import { Outlet } from "react-router";
type Props = {
queryRef: PreloadedQuery<TrustCenterGraphQuery>;
@@ -28,100 +25,12 @@ type Props = {
export default function TrustCenterPage({ queryRef }: Props) {
const { __ } = useTranslate();
const { toast } = useToast();
const organizationId = useOrganizationId();
const location = useLocation();
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);
usePageTitle(__("Trust Center"));
const handleToggleActive = async (active: boolean) => {
if (!organization.trustCenter?.id) {
toast({
title: __("Error"),
description: __("Trust center not found"),
variant: "error",
});
return;
}
setIsActive(active);
updateTrustCenter({
variables: {
input: {
trustCenterId: organization.trustCenter.id,
active,
},
},
onError: () => {
setIsActive(!active);
},
});
};
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?.id
? organization.customDomain?.domain
? `https://${organization.customDomain.domain}`
: `${window.location.origin}/trust/${organization.trustCenter.id}`
: null;
const isActive = organization.trustCenter?.active || false;
return (
<div className="space-y-6">
@@ -130,173 +39,44 @@ export default function TrustCenterPage({ queryRef }: Props) {
description={__(
"Configure your public trust center to showcase your security and compliance posture."
)}
/>
>
<Badge variant={isActive ? "success" : "danger"}>
{isActive ? __("Active") : __("Inactive")}
</Badge>
</PageHeader>
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Trust Center Status")}</h2>
{isUpdating && <Spinner />}
</div>
<Card padded className="space-y-4">
<div className="flex items-center justify-between">
<div className="space-y-1">
<h3 className="font-medium">{__("Activate Trust Center")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Make your trust center publicly accessible to build customer confidence")}
</p>
</div>
<Checkbox
checked={isActive}
onChange={handleToggleActive}
/>
</div>
<Tabs>
<TabLink to={`/organizations/${organizationId}/trust-center`} end>
<IconSettingsGear2 className="size-4" />
{__("Overview")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/trust-by`}>
<IconCheckmark1 className="size-4" />
{__("Trusted by")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/audits`}>
<IconMedal className="size-4" />
{__("Audits")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/documents`}>
<IconPageTextLine className="size-4" />
{__("Documents")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/files`}>
<IconFolder2 className="size-4" />
{__("Files")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/vendors`}>
<IconStore className="size-4" />
{__("Vendors")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/access`}>
<IconPeopleAdd className="size-4" />
{__("Access")}
</TabLink>
</Tabs>
{isActive && trustCenterUrl && (
<div className="mt-4 p-4 bg-accent-light rounded-lg border border-accent">
<div className="flex items-center justify-between">
<div>
<h4 className="font-medium text-accent-dark">
{__("Your Trust Center is Live!")}
</h4>
<p className="text-sm text-accent-dark mt-1">
{__("Your customers can now access your trust center at:")}
</p>
<a
href={trustCenterUrl}
target="_blank"
rel="noopener noreferrer"
className="text-sm font-mono text-accent underline hover:no-underline"
>
{trustCenterUrl}
</a>
</div>
<Button
variant="secondary"
onClick={() => window.open(trustCenterUrl, '_blank', 'noopener,noreferrer')}
>
{__("View")}
</Button>
</div>
</div>
)}
{!isActive && (
<div className="mt-4 p-4 bg-tertiary rounded-lg border border-border-solid">
<h4 className="font-medium text-txt-secondary">
{__("Trust Center is Inactive")}
</h4>
<p className="text-sm text-txt-tertiary mt-1">
{__("Your trust center is currently not accessible to the public. Enable it to start sharing your compliance status.")}
</p>
</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>
{organization.trustCenter?.id && (
<TrustCenterReferencesSection trustCenterId={organization.trustCenter.id} />
)}
<div className="space-y-4">
<h2 className="text-base font-medium">{__("Integrations")}</h2>
<Card padded>
<SlackConnections
organizationId={organization.id!}
slackConnections={organization.slackConnections?.edges?.map((edge) => edge.node) ?? []}
/>
</Card>
</div>
<div className="space-y-4">
<Tabs>
<TabItem
asChild
active={
location.pathname === `/organizations/${organizationId}/trust-center` ||
location.pathname === `/organizations/${organizationId}/trust-center/audits`
}
>
<Link to={`/organizations/${organizationId}/trust-center/audits`}>
{__("Audits")}
</Link>
</TabItem>
<TabLink to={`/organizations/${organizationId}/trust-center/vendors`}>
{__("Vendors")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/documents`}>
{__("Documents")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/files`}>
{__("Files")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/trust-center/access`}>
{__("Access")}
</TabLink>
</Tabs>
<Outlet context={{ organization }} />
</div>
<Outlet context={{ organization }} />
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { useOutletContext } from "react-router";
import { TrustCenterReferencesSection } from "/components/trustCenter/TrustCenterReferencesSection";
type ContextType = {
organization: {
trustCenter?: {
id: string;
} | null;
};
};
export default function TrustCenterReferencesTab() {
const { organization } = useOutletContext<ContextType>();
return (
<div className="space-y-4">
{organization.trustCenter?.id && (
<TrustCenterReferencesSection trustCenterId={organization.trustCenter.id} />
)}
</div>
);
}

View File

@@ -1,4 +1,5 @@
import { Card, Spinner } from "@probo/ui";
import { Spinner } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useOutletContext } from "react-router";
import { TrustCenterVendorsCard } from "/components/trustCenter/TrustCenterVendorsCard";
import { useTrustCenterVendorUpdate } from "/hooks/graph/TrustCenterVendorGraph";
@@ -15,6 +16,7 @@ type ContextType = {
};
export default function TrustCenterVendorsTab() {
const { __ } = useTranslate();
const { organization } = useOutletContext<ContextType>();
const [updateVendorVisibility, isUpdatingVendors] = useTrustCenterVendorUpdate();
@@ -23,17 +25,20 @@ export default function TrustCenterVendorsTab() {
return (
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-medium">{__("Vendors")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Manage vendor assessments and third-party risk information")}
</p>
</div>
{isUpdatingVendors && <Spinner />}
</div>
<Card padded>
<TrustCenterVendorsCard
vendors={vendors}
params={{}}
disabled={isUpdatingVendors}
onToggleVisibility={updateVendorVisibility}
variant="table"
/>
</Card>
<TrustCenterVendorsCard
vendors={vendors}
params={{}}
disabled={isUpdatingVendors}
onToggleVisibility={updateVendorVisibility}
/>
</div>
);
}

View File

@@ -20,7 +20,14 @@ export const trustCenterRoutes = [
index: true,
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/trustCenter/TrustCenterAuditsTab")
() => import("/pages/organizations/trustCenter/TrustCenterOverviewTab")
),
},
{
path: "trust-by",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/trustCenter/TrustCenterReferencesTab")
),
},
{
@@ -30,13 +37,6 @@ export const trustCenterRoutes = [
() => import("/pages/organizations/trustCenter/TrustCenterAuditsTab")
),
},
{
path: "vendors",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/trustCenter/TrustCenterVendorsTab")
),
},
{
path: "documents",
fallback: LinkCardSkeleton,
@@ -51,6 +51,13 @@ export const trustCenterRoutes = [
() => import("/pages/organizations/trustCenter/TrustCenterFilesTab")
),
},
{
path: "vendors",
fallback: LinkCardSkeleton,
Component: lazy(
() => import("/pages/organizations/trustCenter/TrustCenterVendorsTab")
),
},
{
path: "access",
fallback: LinkCardSkeleton,