diff --git a/apps/console/src/__generated__/core/CompliancePageNDASectionFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageNDASectionFragment.graphql.ts new file mode 100644 index 000000000..52649f7cb --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageNDASectionFragment.graphql.ts @@ -0,0 +1,113 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageNDASectionFragment$data = { + readonly compliancePage: { + readonly canDeleteNDA: boolean; + readonly canUpdate: boolean; + readonly canUploadNDA: boolean; + readonly id: string; + readonly ndaFileName: string | null | undefined; + readonly ndaFileUrl: string | null | undefined; + } | null | undefined; + readonly " $fragmentType": "CompliancePageNDASectionFragment"; +}; +export type CompliancePageNDASectionFragment$key = { + readonly " $data"?: CompliancePageNDASectionFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageNDASectionFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageNDASectionFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileUrl", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:update\")" + }, + { + "alias": "canUploadNDA", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:upload-nda" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:upload-nda\")" + }, + { + "alias": "canDeleteNDA", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:delete-nda" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:delete-nda\")" + } + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null +}; + +(node as any).hash = "3a55696877cb2d258edf0b9ed615de91"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageOverviewPageQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageOverviewPageQuery.graphql.ts new file mode 100644 index 000000000..0e3948146 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageOverviewPageQuery.graphql.ts @@ -0,0 +1,313 @@ +/** + * @generated SignedSource<<18bb7ea1b507c374c33d9f4f7a56db2d>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageOverviewPageQuery$variables = { + organizationId: string; +}; +export type CompliancePageOverviewPageQuery$data = { + readonly organization: { + readonly compliancePage?: { + readonly canGetNDA: boolean; + } | null | undefined; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageNDASectionFragment" | "CompliancePageSlackSectionFragment" | "CompliancePageStatusSectionFragment">; + }; +}; +export type CompliancePageOverviewPageQuery = { + response: CompliancePageOverviewPageQuery$data; + variables: CompliancePageOverviewPageQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": "canGetNDA", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:get-nda" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:get-nda\")" +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageOverviewPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v2/*: any*/) + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageStatusSectionFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageNDASectionFragment" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageSlackSectionFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageOverviewPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "active", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:update\")" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "ndaFileUrl", + "storageKey": null + }, + { + "alias": "canUploadNDA", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:upload-nda" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:upload-nda\")" + }, + { + "alias": "canDeleteNDA", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:delete-nda" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:delete-nda\")" + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "CustomDomain", + "kind": "LinkedField", + "name": "customDomain", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "domain", + "storageKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "SlackConnectionConnection", + "kind": "LinkedField", + "name": "slackConnections", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "SlackConnectionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "SlackConnection", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "channel", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "slackConnections(first:100)" + } + ], + "type": "Organization", + "abstractKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "0041132d3ac46a3193d9acbd6f27bdf5", + "id": null, + "metadata": {}, + "name": "CompliancePageOverviewPageQuery", + "operationKind": "query", + "text": "query CompliancePageOverviewPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n canGetNDA: permission(action: \"core:trust-center:get-nda\")\n id\n }\n }\n ...CompliancePageStatusSectionFragment\n ...CompliancePageNDASectionFragment\n ...CompliancePageSlackSectionFragment\n id\n }\n}\n\nfragment CompliancePageNDASectionFragment on Organization {\n compliancePage: trustCenter {\n id\n ndaFileName\n ndaFileUrl\n canUpdate: permission(action: \"core:trust-center:update\")\n canUploadNDA: permission(action: \"core:trust-center:upload-nda\")\n canDeleteNDA: permission(action: \"core:trust-center:delete-nda\")\n }\n}\n\nfragment CompliancePageSlackSectionFragment on Organization {\n compliancePage: trustCenter {\n canUpdate: permission(action: \"core:trust-center:update\")\n id\n }\n slackConnections(first: 100) {\n edges {\n node {\n id\n channel\n createdAt\n }\n }\n }\n}\n\nfragment CompliancePageStatusSectionFragment on Organization {\n customDomain {\n domain\n id\n }\n compliancePage: trustCenter {\n id\n active\n canUpdate: permission(action: \"core:trust-center:update\")\n }\n}\n" + } +}; +})(); + +(node as any).hash = "689133b7f1c380266e6f15a60c527c91"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageSlackSectionFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageSlackSectionFragment.graphql.ts new file mode 100644 index 000000000..c3f8ca713 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageSlackSectionFragment.graphql.ts @@ -0,0 +1,130 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageSlackSectionFragment$data = { + readonly compliancePage: { + readonly canUpdate: boolean; + } | null | undefined; + readonly slackConnections: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly channel: string | null | undefined; + readonly createdAt: string; + readonly id: string; + }; + }>; + }; + readonly " $fragmentType": "CompliancePageSlackSectionFragment"; +}; +export type CompliancePageSlackSectionFragment$key = { + readonly " $data"?: CompliancePageSlackSectionFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageSlackSectionFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageSlackSectionFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:update\")" + } + ], + "storageKey": null + }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } + ], + "concreteType": "SlackConnectionConnection", + "kind": "LinkedField", + "name": "slackConnections", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "SlackConnectionEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "SlackConnection", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "channel", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": "slackConnections(first:100)" + } + ], + "type": "Organization", + "abstractKey": null +}; + +(node as any).hash = "48af0130884a7e0070e64ead2afe5046"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageStatusSectionFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageStatusSectionFragment.graphql.ts new file mode 100644 index 000000000..fc23f8d43 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageStatusSectionFragment.graphql.ts @@ -0,0 +1,98 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageStatusSectionFragment$data = { + readonly compliancePage: { + readonly active: boolean; + readonly canUpdate: boolean; + readonly id: string; + } | null | undefined; + readonly customDomain: { + readonly domain: string; + } | null | undefined; + readonly " $fragmentType": "CompliancePageStatusSectionFragment"; +}; +export type CompliancePageStatusSectionFragment$key = { + readonly " $data"?: CompliancePageStatusSectionFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageStatusSectionFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageStatusSectionFragment", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "CustomDomain", + "kind": "LinkedField", + "name": "customDomain", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "domain", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "active", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center:update\")" + } + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null +}; + +(node as any).hash = "07af542e3be959de0daf6fcb165830b0"; + +export default node; diff --git a/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPage.tsx b/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPage.tsx new file mode 100644 index 000000000..ffb449191 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPage.tsx @@ -0,0 +1,44 @@ +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageOverviewPageQuery } from "#/__generated__/core/CompliancePageOverviewPageQuery.graphql"; + +import { CompliancePageNDASection } from "./_components/CompliancePageNDASection"; +import { CompliancePageSlackSection } from "./_components/CompliancePageSlackSection"; +import { CompliancePageStatusSection } from "./_components/CompliancePageStatusSection"; + +export const compliancePageOverviewPageQuery = graphql` + query CompliancePageOverviewPageQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + ... on Organization { + compliancePage: trustCenter { + canGetNDA: permission(action: "core:trust-center:get-nda") + } + } + ...CompliancePageStatusSectionFragment + ...CompliancePageNDASectionFragment + ...CompliancePageSlackSectionFragment + } + } +`; + +export function CompliancePageOverviewPage(props: { queryRef: PreloadedQuery }) { + const { queryRef } = props; + + const { organization } = usePreloadedQuery( + compliancePageOverviewPageQuery, + queryRef, + ); + + return ( +
+ + + {organization.compliancePage?.canGetNDA && ( + + )} + + +
+ ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPageLoader.tsx b/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPageLoader.tsx new file mode 100644 index 000000000..d32c134ec --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/overview/CompliancePageOverviewPageLoader.tsx @@ -0,0 +1,32 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import type { CompliancePageOverviewPageQuery } from "#/__generated__/core/CompliancePageOverviewPageQuery.graphql"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; + +import { CompliancePageOverviewPage, compliancePageOverviewPageQuery } from "./CompliancePageOverviewPage"; + +function CompliancePageOverviewPageQueryLoader() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] = useQueryLoader(compliancePageOverviewPageQuery); + + useEffect(() => { + if (!queryRef) { + loadQuery({ organizationId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function CompliancePageOverviewPageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageNDASection.tsx b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageNDASection.tsx new file mode 100644 index 000000000..e15117af3 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageNDASection.tsx @@ -0,0 +1,175 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Card, Dropzone, IconTrashCan, Spinner, useToast } from "@probo/ui"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageNDASectionFragment$key } from "#/__generated__/core/CompliancePageNDASectionFragment.graphql"; +import { useDeleteTrustCenterNDAMutation, useUploadTrustCenterNDAMutation } from "#/hooks/graph/TrustCenterGraph"; + +const fragment = graphql` + fragment CompliancePageNDASectionFragment on Organization { + compliancePage: trustCenter { + id + ndaFileName + ndaFileUrl + canUpdate: permission(action: "core:trust-center:update") + canUploadNDA: permission(action: "core:trust-center:upload-nda") + canDeleteNDA: permission(action: "core:trust-center:delete-nda") + } + } +`; + +export function CompliancePageNDASection(props: { fragmentRef: CompliancePageNDASectionFragment$key }) { + const { fragmentRef } = props; + + const { __ } = useTranslate(); + const { toast } = useToast(); + + const organization = useFragment(fragment, fragmentRef); + + const [uploadNDA, isUploadingNDA] = useUploadTrustCenterNDAMutation(); + const [deleteNDA, isDeletingNDA] = useDeleteTrustCenterNDAMutation(); + + const handleNDAUpload = async (files: File[]) => { + if (!organization.compliancePage?.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.compliancePage.id, + fileName: file.name, + file: null, + }, + }, + uploadables: { + "input.file": file, + }, + }); + }; + + const handleNDADelete = async () => { + if (!organization.compliancePage?.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.compliancePage.id, + }, + }, + }); + }; + + return ( +
+
+

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

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

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

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

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

+
+

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

+
+
+ + {organization.compliancePage?.canDeleteNDA && ( +
+
+
+ ) + : ( + <> + {organization.compliancePage?.canUpdate + ? ( + void handleNDAUpload(files)} + accept={{ + "application/pdf": [".pdf"], + }} + maxSize={10} + /> + ) + : ( +

+ {__("No NDA file uploaded")} +

+ )} + + )} +
+
+
+ ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageSlackSection.tsx b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageSlackSection.tsx new file mode 100644 index 000000000..25d8c9359 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageSlackSection.tsx @@ -0,0 +1,102 @@ +import { sprintf } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { Badge, Button, Card } from "@probo/ui"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageSlackSectionFragment$key } from "#/__generated__/core/CompliancePageSlackSectionFragment.graphql"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; + +const fragment = graphql` + fragment CompliancePageSlackSectionFragment on Organization { + compliancePage: trustCenter { + canUpdate: permission(action: "core:trust-center:update") + } + slackConnections(first: 100) { + edges { + node { + id + channel + createdAt + } + } + } + } +`; + +export function CompliancePageSlackSection(props: { fragmentRef: CompliancePageSlackSectionFragment$key }) { + const { fragmentRef } = props; + + const organizationId = useOrganizationId(); + const { __, dateTimeFormat } = useTranslate(); + + const organization = useFragment(fragment, fragmentRef); + + return ( +
+

{__("Integrations")}

+ +
+ {organization.slackConnections.edges.map(({ node: slackConnection }) => ( + +
+ +
+
+

Slack

+

+ {slackConnection.createdAt + ? ( + <> + {sprintf( + __("Connected on %s"), + dateTimeFormat(slackConnection.createdAt), + )} + {slackConnection.channel && ( + <> + {" • "} + {sprintf(__("Channel: %s"), slackConnection.channel)} + + )} + + ) + : __("Manage your trust center access with slack")} +

+
+ {slackConnection.createdAt + ? ( +
+ + {__("Connected")} + +
+ ) + : ( + organization.compliancePage?.canUpdate && ( + + ) + )} +
+ ))} +
+
+
+ ); +} + +function getSlackConnectionUrl(organizationId: string): string { + const baseUrl = import.meta.env.VITE_API_URL || window.location.origin; + const url = new URL("/api/console/v1/connectors/initiate", baseUrl); + url.searchParams.append("organization_id", organizationId); + url.searchParams.append("provider", "SLACK"); + const redirectUrl = `/organizations/${organizationId}/compliance-page`; + url.searchParams.append("continue", redirectUrl); + const finalUrl = url.toString(); + return finalUrl; +}; diff --git a/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageStatusSection.tsx b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageStatusSection.tsx new file mode 100644 index 000000000..0c49e1a2e --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/overview/_components/CompliancePageStatusSection.tsx @@ -0,0 +1,131 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Card, Checkbox, Spinner, useToast } from "@probo/ui"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageStatusSectionFragment$key } from "#/__generated__/core/CompliancePageStatusSectionFragment.graphql"; +import { useUpdateTrustCenterMutation } from "#/hooks/graph/TrustCenterGraph"; + +const fragment = graphql` + fragment CompliancePageStatusSectionFragment on Organization { + customDomain { + domain + } + compliancePage: trustCenter { + id + active + canUpdate: permission(action: "core:trust-center:update") + } + } +`; + +export function CompliancePageStatusSection(props: { + fragmentRef: CompliancePageStatusSectionFragment$key; +}) { + const { fragmentRef } = props; + + const { __ } = useTranslate(); + const { toast } = useToast(); + + const organization = useFragment( + fragment, + fragmentRef, + ); + + const compliancePageUrl = organization.compliancePage?.id + ? organization.customDomain?.domain + ? `https://${organization.customDomain.domain}` + : `${window.location.origin}/trust/${organization.compliancePage.id}` + : null; + + const [updateCompliancePage, isUpdating] = useUpdateTrustCenterMutation(); + + const handleToggleActive = async (active: boolean) => { + if (!organization.compliancePage?.id) { + toast({ + title: __("Error"), + description: __("Trust center not found"), + variant: "error", + }); + return; + } + + await updateCompliancePage({ + variables: { + input: { + trustCenterId: organization.compliancePage.id, + active, + }, + }, + }); + }; + + return ( +
+
+

{__("Trust Center Status")}

+ {isUpdating && } +
+ +
+
+

{__("Activate Compliance Page")}

+

+ {__( + "Make your Compliance Page publicly accessible to build customer confidence", + )} +

+
+ void handleToggleActive(checked)} + disabled={!organization.compliancePage?.canUpdate} + /> +
+ + {organization.compliancePage?.active && compliancePageUrl && ( +
+
+
+

+ {__("Your Compliance Page is live!")} +

+

+ {__("Your customers can now access your compliance page at:")} +

+ + {compliancePageUrl} + +
+ +
+
+ )} + + {!organization.compliancePage?.active && ( +
+

+ {__("Compliance Page is inactive")} +

+

+ {__( + "Your Compliance Page is currently not accessible to the public. Enable it to start sharing your compliance status.", + )} +

+
+ )} +
+
+ ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/routes.ts b/apps/console/src/pages/organizations/compliance-page/routes.ts index 75894c25b..339ebad48 100644 --- a/apps/console/src/pages/organizations/compliance-page/routes.ts +++ b/apps/console/src/pages/organizations/compliance-page/routes.ts @@ -1,5 +1,6 @@ import { lazy } from "@probo/react-lazy"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; import { PageSkeleton } from "#/components/skeletons/PageSkeleton"; export const compliancePageRoutes = [ @@ -7,5 +8,12 @@ export const compliancePageRoutes = [ path: "compliance-page", Fallback: PageSkeleton, Component: lazy(() => import("#/pages/organizations/compliance-page/CompliancePageLayoutLoader")), + children: [ + { + index: true, + Fallback: LinkCardSkeleton, + Component: lazy(() => import("#/pages/organizations/compliance-page/overview/CompliancePageOverviewPageLoader")), + }, + ], }, ];