From 26a45f9c6fc0cbc54eee486c448a5a5250671ca9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 28 Jan 2026 15:21:44 +0400 Subject: [PATCH] Add references page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../core/CompliancePageLayoutQuery.graphql.ts | 14 +- ...liancePageReferenceListFragment.graphql.ts | 209 +++++++++++ ...cePageReferenceListItemFragment.graphql.ts | 110 ++++++ ...ompliancePageReferenceListQuery.graphql.ts | 336 +++++++++++++++++ ...mpliancePageReferencesPageQuery.graphql.ts | 349 ++++++++++++++++++ .../TrustCenterReferenceDialog.tsx | 15 +- .../compliance-page/CompliancePageLayout.tsx | 10 +- .../CompliancePageReferencesPage.tsx | 82 ++++ .../CompliancePageReferencesPageLoader.tsx | 32 ++ .../CompliancePageReferenceList.tsx | 134 +++++++ .../CompliancePageReferenceListItem.tsx | 103 ++++++ .../organizations/compliance-page/routes.ts | 5 + 12 files changed, 1376 insertions(+), 23 deletions(-) create mode 100644 apps/console/src/__generated__/core/CompliancePageReferenceListFragment.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageReferenceListItemFragment.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageReferenceListQuery.graphql.ts create mode 100644 apps/console/src/__generated__/core/CompliancePageReferencesPageQuery.graphql.ts create mode 100644 apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPage.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceList.tsx create mode 100644 apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceListItem.tsx diff --git a/apps/console/src/__generated__/core/CompliancePageLayoutQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageLayoutQuery.graphql.ts index c6ddb6093..b1409635d 100644 --- a/apps/console/src/__generated__/core/CompliancePageLayoutQuery.graphql.ts +++ b/apps/console/src/__generated__/core/CompliancePageLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<6b8fb96ca82a23677b5a8d4b22b1685f>> + * @generated SignedSource<<7000743fa590bee3bbcf0a18def47ed0>> * @lightSyntaxTransform * @nogrep */ @@ -15,7 +15,7 @@ export type CompliancePageLayoutQuery$variables = { export type CompliancePageLayoutQuery$data = { readonly organization: { readonly __typename: "Organization"; - readonly trustCenter: { + readonly compliancePage: { readonly active: boolean; } | null | undefined; } | { @@ -85,7 +85,7 @@ return { "kind": "InlineFragment", "selections": [ { - "alias": null, + "alias": "compliancePage", "args": null, "concreteType": "TrustCenter", "kind": "LinkedField", @@ -126,7 +126,7 @@ return { "kind": "InlineFragment", "selections": [ { - "alias": null, + "alias": "compliancePage", "args": null, "concreteType": "TrustCenter", "kind": "LinkedField", @@ -149,16 +149,16 @@ return { ] }, "params": { - "cacheID": "dca289c0f586e539bfee9c68ccd14e37", + "cacheID": "4bbef5c143ac18fe30a6639af67368ba", "id": null, "metadata": {}, "name": "CompliancePageLayoutQuery", "operationKind": "query", - "text": "query CompliancePageLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n trustCenter {\n active\n id\n }\n }\n id\n }\n}\n" + "text": "query CompliancePageLayoutQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n active\n id\n }\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "0134cd2bfcc281c33161c3693172af0c"; +(node as any).hash = "29df1368a1d61815d82c172c94f1ca5e"; export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageReferenceListFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageReferenceListFragment.graphql.ts new file mode 100644 index 000000000..dea6d0c5d --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageReferenceListFragment.graphql.ts @@ -0,0 +1,209 @@ +/** + * @generated SignedSource<<0fb850d4fd73390187553a1b9d8aa5f6>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageReferenceListFragment$data = { + readonly id: string; + readonly references: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly rank: number; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageReferenceListItemFragment">; + }; + }>; + }; + readonly " $fragmentType": "CompliancePageReferenceListFragment"; +}; +export type CompliancePageReferenceListFragment$key = { + readonly " $data"?: CompliancePageReferenceListFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageReferenceListFragment">; +}; + +import CompliancePageReferenceListQuery_graphql from './CompliancePageReferenceListQuery.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "references" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": 100, + "kind": "LocalArgument", + "name": "first" + }, + { + "defaultValue": { + "direction": "ASC", + "field": "RANK" + }, + "kind": "LocalArgument", + "name": "order" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": "first", + "cursor": "after", + "direction": "forward", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "first", + "cursor": "after" + }, + "backward": null, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": CompliancePageReferenceListQuery_graphql, + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "CompliancePageReferenceListFragment", + "selections": [ + { + "alias": "references", + "args": [ + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } + ], + "concreteType": "TrustCenterReferenceConnection", + "kind": "LinkedField", + "name": "__CompliancePageReferenceList_references_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReferenceEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReference", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rank", + "storageKey": null + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageReferenceListItemFragment" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + (v1/*: any*/) + ], + "type": "TrustCenter", + "abstractKey": null +}; +})(); + +(node as any).hash = "83c6f67e205efe88843125bbbd5798d3"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageReferenceListItemFragment.graphql.ts b/apps/console/src/__generated__/core/CompliancePageReferenceListItemFragment.graphql.ts new file mode 100644 index 000000000..f3872319c --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageReferenceListItemFragment.graphql.ts @@ -0,0 +1,110 @@ +/** + * @generated SignedSource<<655f5778a0f492990e2eb26b73715be7>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageReferenceListItemFragment$data = { + readonly canDelete: boolean; + readonly canUpdate: boolean; + readonly description: string | null | undefined; + readonly id: string; + readonly logoUrl: string; + readonly name: string; + readonly rank: number; + readonly websiteUrl: string; + readonly " $fragmentType": "CompliancePageReferenceListItemFragment"; +}; +export type CompliancePageReferenceListItemFragment$key = { + readonly " $data"?: CompliancePageReferenceListItemFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageReferenceListItemFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageReferenceListItemFragment", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "logoUrl", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rank", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:delete\")" + } + ], + "type": "TrustCenterReference", + "abstractKey": null +}; + +(node as any).hash = "d07f6e7e647e14b87586e7902ecb0dab"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageReferenceListQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageReferenceListQuery.graphql.ts new file mode 100644 index 000000000..0e041fa22 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageReferenceListQuery.graphql.ts @@ -0,0 +1,336 @@ +/** + * @generated SignedSource<<2dfb2ea900e5aa65a42fec34286e1fa7>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type OrderDirection = "ASC" | "DESC"; +export type TrustCenterReferenceOrderField = "CREATED_AT" | "NAME" | "RANK" | "UPDATED_AT"; +export type TrustCenterReferenceOrder = { + direction: OrderDirection; + field: TrustCenterReferenceOrderField; +}; +export type CompliancePageReferenceListQuery$variables = { + after?: string | null | undefined; + first?: number | null | undefined; + id: string; + order?: TrustCenterReferenceOrder | null | undefined; +}; +export type CompliancePageReferenceListQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageReferenceListFragment">; + }; +}; +export type CompliancePageReferenceListQuery = { + response: CompliancePageReferenceListQuery$data; + variables: CompliancePageReferenceListQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" +}, +v1 = { + "defaultValue": 100, + "kind": "LocalArgument", + "name": "first" +}, +v2 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" +}, +v3 = { + "defaultValue": { + "direction": "ASC", + "field": "RANK" + }, + "kind": "LocalArgument", + "name": "order" +}, +v4 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v5 = { + "kind": "Variable", + "name": "after", + "variableName": "after" +}, +v6 = { + "kind": "Variable", + "name": "first", + "variableName": "first" +}, +v7 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v8 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v9 = [ + (v5/*: any*/), + (v6/*: any*/), + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } +]; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v3/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageReferenceListQuery", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": [ + (v5/*: any*/), + (v6/*: any*/), + { + "kind": "Variable", + "name": "order", + "variableName": "order" + } + ], + "kind": "FragmentSpread", + "name": "CompliancePageReferenceListFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v3/*: any*/), + (v2/*: any*/) + ], + "kind": "Operation", + "name": "CompliancePageReferenceListQuery", + "selections": [ + { + "alias": null, + "args": (v4/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v7/*: any*/), + (v8/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v9/*: any*/), + "concreteType": "TrustCenterReferenceConnection", + "kind": "LinkedField", + "name": "references", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReferenceEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReference", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v8/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rank", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "logoUrl", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:delete\")" + }, + (v7/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v9/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "CompliancePageReferenceList_references", + "kind": "LinkedHandle", + "name": "references" + } + ], + "type": "TrustCenter", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "ed8ea2477e21fd09f68b36b861b808e9", + "id": null, + "metadata": {}, + "name": "CompliancePageReferenceListQuery", + "operationKind": "query", + "text": "query CompliancePageReferenceListQuery(\n $after: CursorKey = null\n $first: Int = 100\n $order: TrustCenterReferenceOrder = {field: RANK, direction: ASC}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompliancePageReferenceListFragment_gOVF5\n id\n }\n}\n\nfragment CompliancePageReferenceListFragment_gOVF5 on TrustCenter {\n references(first: $first, after: $after, orderBy: $order) {\n edges {\n node {\n id\n rank\n ...CompliancePageReferenceListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompliancePageReferenceListItemFragment on TrustCenterReference {\n id\n logoUrl\n name\n description\n rank\n websiteUrl\n canUpdate: permission(action: \"core:trust-center-reference:update\")\n canDelete: permission(action: \"core:trust-center-reference:delete\")\n}\n" + } +}; +})(); + +(node as any).hash = "83c6f67e205efe88843125bbbd5798d3"; + +export default node; diff --git a/apps/console/src/__generated__/core/CompliancePageReferencesPageQuery.graphql.ts b/apps/console/src/__generated__/core/CompliancePageReferencesPageQuery.graphql.ts new file mode 100644 index 000000000..81e96da79 --- /dev/null +++ b/apps/console/src/__generated__/core/CompliancePageReferencesPageQuery.graphql.ts @@ -0,0 +1,349 @@ +/** + * @generated SignedSource<<0a0f91bc4b1622f7dea5c39185904169>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type CompliancePageReferencesPageQuery$variables = { + organizationId: string; +}; +export type CompliancePageReferencesPageQuery$data = { + readonly organization: { + readonly __typename: "Organization"; + readonly compliancePage: { + readonly canCreateReference: boolean; + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"CompliancePageReferenceListFragment">; + }; + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other"; + }; +}; +export type CompliancePageReferencesPageQuery = { + response: CompliancePageReferencesPageQuery$data; + variables: CompliancePageReferencesPageQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": "canCreateReference", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:create" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:create\")" +}, +v5 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + }, + { + "kind": "Literal", + "name": "orderBy", + "value": { + "direction": "ASC", + "field": "RANK" + } + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CompliancePageReferencesPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "CompliancePageReferenceListFragment" + } + ], + "storageKey": null + }, + "action": "THROW" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CompliancePageReferencesPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": "compliancePage", + "args": null, + "concreteType": "TrustCenter", + "kind": "LinkedField", + "name": "trustCenter", + "plural": false, + "selections": [ + (v3/*: any*/), + (v4/*: any*/), + { + "alias": null, + "args": (v5/*: any*/), + "concreteType": "TrustCenterReferenceConnection", + "kind": "LinkedField", + "name": "references", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReferenceEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "TrustCenterReference", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "rank", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "logoUrl", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "description", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "websiteUrl", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "core:trust-center-reference:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"core:trust-center-reference:delete\")" + }, + (v2/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": "references(first:100,orderBy:{\"direction\":\"ASC\",\"field\":\"RANK\"})" + }, + { + "alias": null, + "args": (v5/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "CompliancePageReferenceList_references", + "kind": "LinkedHandle", + "name": "references" + } + ], + "storageKey": null + } + ], + "type": "Organization", + "abstractKey": null + }, + (v3/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "d352c6f8d833eb8c105e72184c070a85", + "id": null, + "metadata": {}, + "name": "CompliancePageReferencesPageQuery", + "operationKind": "query", + "text": "query CompliancePageReferencesPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n canCreateReference: permission(action: \"core:trust-center-reference:create\")\n ...CompliancePageReferenceListFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageReferenceListFragment on TrustCenter {\n references(first: 100, orderBy: {field: RANK, direction: ASC}) {\n edges {\n node {\n id\n rank\n ...CompliancePageReferenceListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n id\n}\n\nfragment CompliancePageReferenceListItemFragment on TrustCenterReference {\n id\n logoUrl\n name\n description\n rank\n websiteUrl\n canUpdate: permission(action: \"core:trust-center-reference:update\")\n canDelete: permission(action: \"core:trust-center-reference:delete\")\n}\n" + } +}; +})(); + +(node as any).hash = "9c5e008e8f12d028e3b280a833ec3fc5"; + +export default node; diff --git a/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx b/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx index ca2cb6ba4..edc6f242f 100644 --- a/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx +++ b/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx @@ -13,6 +13,7 @@ import { import { forwardRef, type ReactNode, useImperativeHandle, useState } from "react"; import { z } from "zod"; +import type { CompliancePageReferenceListItemFragment$data } from "#/__generated__/core/CompliancePageReferenceListItemFragment.graphql"; import { useCreateTrustCenterReferenceMutation, useUpdateTrustCenterReferenceMutation, @@ -30,15 +31,7 @@ type ReferenceFormData = z.infer; export type TrustCenterReferenceDialogRef = { openCreate: (trustCenterId: string, connectionId: string) => void; - openEdit: (reference: Reference) => void; -}; - -type Reference = { - id: string; - name: string; - description?: string | null; - websiteUrl: string; - rank: number; + openEdit: (reference: CompliancePageReferenceListItemFragment$data) => void; }; export const TrustCenterReferenceDialog = forwardRef( @@ -48,7 +41,7 @@ export const TrustCenterReferenceDialog = forwardRef("create"); const [trustCenterId, setTrustCenterId] = useState(""); const [connectionId, setConnectionId] = useState(""); - const [editReference, setEditReference] = useState(null); + const [editReference, setEditReference] = useState(null); const [uploadedFile, setUploadedFile] = useState(null); const [createReference, isCreating] = useCreateTrustCenterReferenceMutation(); @@ -79,7 +72,7 @@ export const TrustCenterReferenceDialog = forwardRef { + openEdit: (reference: CompliancePageReferenceListItemFragment$data) => { setMode("edit"); setEditReference(reference); setUploadedFile(null); diff --git a/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx b/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx index 0814e219e..f327c20bd 100644 --- a/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx +++ b/apps/console/src/pages/organizations/compliance-page/CompliancePageLayout.tsx @@ -13,7 +13,7 @@ export const compliancePageLayoutQuery = graphql` organization: node(id: $organizationId) { __typename ... on Organization { - trustCenter { + compliancePage: trustCenter { active } } @@ -42,8 +42,8 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery - - {organization.trustCenter?.active ? __("Active") : __("Inactive")} + + {organization.compliancePage?.active ? __("Active") : __("Inactive")} @@ -52,9 +52,9 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery {__("Overview")} - + - {__("Trusted by")} + {__("References")} diff --git a/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPage.tsx b/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPage.tsx new file mode 100644 index 000000000..5703f2f83 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPage.tsx @@ -0,0 +1,82 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, IconPlusLarge } from "@probo/ui"; +import { useRef } from "react"; +import { ConnectionHandler, graphql, type PreloadedQuery, usePreloadedQuery } from "react-relay"; + +import type { CompliancePageReferenceListItemFragment$data } from "#/__generated__/core/CompliancePageReferenceListItemFragment.graphql"; +import type { CompliancePageReferencesPageQuery } from "#/__generated__/core/CompliancePageReferencesPageQuery.graphql"; +import { TrustCenterReferenceDialog, type TrustCenterReferenceDialogRef } from "#/components/trustCenter/TrustCenterReferenceDialog"; + +import { CompliancePageReferenceList } from "./_components/CompliancePageReferenceList"; + +export const compliancePageReferencesPageQuery = graphql` + query CompliancePageReferencesPageQuery($organizationId: ID!) { + organization: node(id: $organizationId) { + __typename + ... on Organization { + compliancePage: trustCenter @required(action: THROW) { + id + canCreateReference: permission(action: "core:trust-center-reference:create") + ...CompliancePageReferenceListFragment + } + } + } + } +`; + +export function CompliancePageReferencesPage(props: { queryRef: PreloadedQuery }) { + const { queryRef } = props; + + const { __ } = useTranslate(); + const dialogRef = useRef(null); + + const { organization } = usePreloadedQuery( + compliancePageReferencesPageQuery, + queryRef, + ); + if (organization.__typename !== "Organization") { + throw new Error("invalid type for node"); + } + + const referencesConnectionId = ConnectionHandler.getConnectionID( + organization.compliancePage.id, + "CompliancePageReferenceList_references", + { orderBy: { field: "RANK", direction: "ASC" } }, + ); + + const handleCreate = () => { + if (referencesConnectionId) { + dialogRef.current?.openCreate(organization.compliancePage.id, referencesConnectionId); + } + }; + + const handleEdit = (reference: CompliancePageReferenceListItemFragment$data) => { + dialogRef.current?.openEdit(reference); + }; + + return ( +
+ {organization.compliancePage?.id && ( +
+
+
+

{__("References")}

+

+ {__("Showcase your customers and partners on your compliance page")} +

+
+ {organization.compliancePage?.canCreateReference && ( + + )} +
+ + + + +
+ )} +
+ ); +}; diff --git a/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader.tsx b/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader.tsx new file mode 100644 index 000000000..5c5d39ea7 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader.tsx @@ -0,0 +1,32 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import type { CompliancePageReferencesPageQuery } from "#/__generated__/core/CompliancePageReferencesPageQuery.graphql"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; + +import { CompliancePageReferencesPage, compliancePageReferencesPageQuery } from "./CompliancePageReferencesPage"; + +function CompliancePageReferencesPageQueryLoader() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] = useQueryLoader(compliancePageReferencesPageQuery); + + useEffect(() => { + if (!queryRef) { + loadQuery({ organizationId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function CompliancePageReferencesPageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceList.tsx b/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceList.tsx new file mode 100644 index 000000000..f05a91788 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceList.tsx @@ -0,0 +1,134 @@ +import { useTranslate } from "@probo/i18n"; +import { Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui"; +import { useState } from "react"; +import { useRefetchableFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { CompliancePageReferenceListFragment$key } from "#/__generated__/core/CompliancePageReferenceListFragment.graphql"; +import type { CompliancePageReferenceListItemFragment$data } from "#/__generated__/core/CompliancePageReferenceListItemFragment.graphql"; +import type { CompliancePageReferenceListQuery } from "#/__generated__/core/CompliancePageReferenceListQuery.graphql"; +import { useUpdateTrustCenterReferenceRankMutation } from "#/hooks/graph/TrustCenterReferenceGraph"; + +import { CompliancePageReferenceListItem } from "./CompliancePageReferenceListItem"; + +const fragment = graphql` + fragment CompliancePageReferenceListFragment on TrustCenter + @refetchable(queryName: "CompliancePageReferenceListQuery") + @argumentDefinitions ( + first: { type: Int defaultValue: 100 } + after: { type: CursorKey defaultValue: null } + order: { type: TrustCenterReferenceOrder, defaultValue: { field: RANK, direction: ASC } } + ) { + references(first: $first, after: $after, orderBy: $order) + @connection(key: "CompliancePageReferenceList_references", filters: ["orderBy"]) { + __id + edges { + node { + id + rank + ...CompliancePageReferenceListItemFragment + } + } + } + } +`; + +export function CompliancePageReferenceList(props: { + fragmentRef: CompliancePageReferenceListFragment$key; + onEdit: (r: CompliancePageReferenceListItemFragment$data) => void; +}) { + const { fragmentRef, onEdit } = props; + + const { __ } = useTranslate(); + + const [{ references }, refetch] = useRefetchableFragment< + CompliancePageReferenceListQuery, + CompliancePageReferenceListFragment$key + >(fragment, fragmentRef); + const [updateRank] = useUpdateTrustCenterReferenceRankMutation(); + + const [draggedIndex, setDraggedIndex] = useState(null); + const [dragOverIndex, setDragOverIndex] = useState(null); + + const handleDragStart = (index: number) => { + setDraggedIndex(index); + }; + + const handleDragOver = (e: React.DragEvent, index: number) => { + e.preventDefault(); + if (draggedIndex !== index) { + setDragOverIndex(index); + } + }; + + const handleDrop = async (targetIndex: number) => { + if (draggedIndex === null || draggedIndex === targetIndex) { + setDraggedIndex(null); + setDragOverIndex(null); + return; + } + + const draggedRef = references.edges[draggedIndex]; + const targetRank = references.edges[targetIndex].node.rank; + + await updateRank({ + variables: { + input: { + id: draggedRef.node.id, + rank: targetRank, + }, + }, + onCompleted: (_, errors) => { + if (errors?.length) { + return; + } + + refetch({}); + }, + }); + + setDraggedIndex(null); + setDragOverIndex(null); + }; + + return ( + <> + + + + + + + + + + {references.edges.length === 0 && ( + + + + )} + {references.edges.map(({ node: reference }, index: number) => ( + onEdit(r)} + connectionId={references.__id} + onDragStart={() => handleDragStart(index)} + onDragOver={e => handleDragOver(e, index)} + onDrop={() => void handleDrop(index)} + /> + ))} + +
{__("Name")}{__("Description")}
+ {__("No references available")} +
+ +

+ {__("Drag and drop references to change their displayed order")} +

+ + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceListItem.tsx b/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceListItem.tsx new file mode 100644 index 000000000..f95e8fc46 --- /dev/null +++ b/apps/console/src/pages/organizations/compliance-page/references/_components/CompliancePageReferenceListItem.tsx @@ -0,0 +1,103 @@ +import { safeOpenUrl } from "@probo/helpers"; +import { Avatar, Button, IconArrowLink, IconPencil, IconTrashCan, Td, Tr } from "@probo/ui"; +import { useState } from "react"; +import { useFragment } from "react-relay"; +import { type DataID, graphql } from "relay-runtime"; + +import type { CompliancePageReferenceListItemFragment$data, CompliancePageReferenceListItemFragment$key } from "#/__generated__/core/CompliancePageReferenceListItemFragment.graphql"; +import { DeleteTrustCenterReferenceDialog } from "#/components/trustCenter/DeleteTrustCenterReferenceDialog"; + +const fragment = graphql` + fragment CompliancePageReferenceListItemFragment on TrustCenterReference { + id + logoUrl + name + description + rank + websiteUrl + canUpdate: permission(action: "core:trust-center-reference:update") + canDelete: permission(action: "core:trust-center-reference:delete") + } +`; + +export function CompliancePageReferenceListItem(props: { + fragmentRef: CompliancePageReferenceListItemFragment$key; + index: number; + isDragging: boolean; + isDropTarget: boolean; + onEdit: (r: CompliancePageReferenceListItemFragment$data) => void; + connectionId: DataID; + onDragStart: () => void; + onDragOver: (e: React.DragEvent) => void; + onDrop: () => void; +}) { + const { + connectionId, + fragmentRef, + isDragging, + isDropTarget, + onEdit, + onDragStart, + onDragOver, + onDrop, + } = props; + + const reference = useFragment(fragment, fragmentRef); + + const [isMouseDown, setIsMouseDown] = useState(false); + + const className = [ + isDragging && "opacity-50 cursor-grabbing", + !isDragging && !isMouseDown && "cursor-grab", + !isDragging && isMouseDown && "cursor-grabbing", + isDropTarget && "!bg-primary-50 border-y-2 border-primary-500", + ] + .filter(Boolean) + .join(" "); + + return ( + setIsMouseDown(true)} + onMouseUp={() => setIsMouseDown(false)} + onMouseLeave={() => setIsMouseDown(false)} + className={className} + > + +
+ + {reference.name} +
+ + + + {reference.description} + + + +
+
+ + + ); +} diff --git a/apps/console/src/pages/organizations/compliance-page/routes.ts b/apps/console/src/pages/organizations/compliance-page/routes.ts index 339ebad48..4baaa10df 100644 --- a/apps/console/src/pages/organizations/compliance-page/routes.ts +++ b/apps/console/src/pages/organizations/compliance-page/routes.ts @@ -14,6 +14,11 @@ export const compliancePageRoutes = [ Fallback: LinkCardSkeleton, Component: lazy(() => import("#/pages/organizations/compliance-page/overview/CompliancePageOverviewPageLoader")), }, + { + path: "references", + Fallback: LinkCardSkeleton, + Component: lazy(() => import("#/pages/organizations/compliance-page/references/CompliancePageReferencesPageLoader")), + }, ], }, ];