From 5e602d744bbc9f3aa5c346010771ead99923414c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 7 Jan 2026 11:25:36 +0100 Subject: [PATCH] Rename MembershipLayout and create ViewerLayout for identity context pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../iam/ViewerDropdownFragment.graphql.ts | 64 ++++++++++ ... ViewerDropdownSignOutMutation.graphql.ts} | 24 ++-- .../iam/ViewerLayoutQuery.graphql.ts | 120 ++++++++++++++++++ ...ewerMembershipDropdownFragment.graphql.ts} | 44 +++---- ...mbershipDropdownSignOutMutation.graphql.ts | 74 +++++++++++ ...=> ViewerMembershipLayoutQuery.graphql.ts} | 54 ++++---- .../pages/iam/memberships/ViewerLayout.tsx | 42 ++++++ .../iam/memberships/ViewerLayoutLoader.tsx | 33 +++++ .../_components/ViewerDropdown.tsx | 92 ++++++++++++++ .../ViewerMembershipLayout.tsx} | 74 ++++++----- .../ViewerMembershipLayoutLoader.tsx} | 13 +- .../_components/ViewerMembershipDropdown.tsx} | 19 +-- .../employee/EmployeeLayoutLoader.tsx | 14 +- apps/console/src/routes.tsx | 6 +- packages/ui/src/Layouts/CenteredLayout.tsx | 30 ++--- packages/ui/src/Layouts/Layout.tsx | 44 ++++--- pkg/server/api/connect/v1/v1_resolver.go | 13 +- 17 files changed, 604 insertions(+), 156 deletions(-) create mode 100644 apps/console/src/__generated__/iam/ViewerDropdownFragment.graphql.ts rename apps/console/src/__generated__/iam/{SessionDropdownSignOutMutation.graphql.ts => ViewerDropdownSignOutMutation.graphql.ts} (61%) create mode 100644 apps/console/src/__generated__/iam/ViewerLayoutQuery.graphql.ts rename apps/console/src/__generated__/iam/{SessionDropdownFragment.graphql.ts => ViewerMembershipDropdownFragment.graphql.ts} (68%) create mode 100644 apps/console/src/__generated__/iam/ViewerMembershipDropdownSignOutMutation.graphql.ts rename apps/console/src/__generated__/iam/{MembershipLayoutQuery.graphql.ts => ViewerMembershipLayoutQuery.graphql.ts} (83%) create mode 100644 apps/console/src/pages/iam/memberships/ViewerLayout.tsx create mode 100644 apps/console/src/pages/iam/memberships/ViewerLayoutLoader.tsx create mode 100644 apps/console/src/pages/iam/memberships/_components/ViewerDropdown.tsx rename apps/console/src/pages/iam/{memberships/MembershipLayout.tsx => organizations/ViewerMembershipLayout.tsx} (52%) rename apps/console/src/pages/iam/{memberships/MembershipLayoutLoader.tsx => organizations/ViewerMembershipLayoutLoader.tsx} (67%) rename apps/console/src/pages/iam/{memberships/_components/SessionDropdown.tsx => organizations/_components/ViewerMembershipDropdown.tsx} (80%) diff --git a/apps/console/src/__generated__/iam/ViewerDropdownFragment.graphql.ts b/apps/console/src/__generated__/iam/ViewerDropdownFragment.graphql.ts new file mode 100644 index 000000000..b9419ec90 --- /dev/null +++ b/apps/console/src/__generated__/iam/ViewerDropdownFragment.graphql.ts @@ -0,0 +1,64 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type ViewerDropdownFragment$data = { + readonly canListAPIKeys: boolean; + readonly email: string; + readonly fullName: string; + readonly " $fragmentType": "ViewerDropdownFragment"; +}; +export type ViewerDropdownFragment$key = { + readonly " $data"?: ViewerDropdownFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"ViewerDropdownFragment">; +}; + +const node: ReaderFragment = { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "ViewerDropdownFragment", + "selections": [ + { + "alias": "canListAPIKeys", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:personal-api-key:list" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:personal-api-key:list\")" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + } + ], + "type": "Identity", + "abstractKey": null +}; + +(node as any).hash = "e96fe96007f4d949403890fda7180cba"; + +export default node; diff --git a/apps/console/src/__generated__/iam/SessionDropdownSignOutMutation.graphql.ts b/apps/console/src/__generated__/iam/ViewerDropdownSignOutMutation.graphql.ts similarity index 61% rename from apps/console/src/__generated__/iam/SessionDropdownSignOutMutation.graphql.ts rename to apps/console/src/__generated__/iam/ViewerDropdownSignOutMutation.graphql.ts index 04d693a3a..fbad14306 100644 --- a/apps/console/src/__generated__/iam/SessionDropdownSignOutMutation.graphql.ts +++ b/apps/console/src/__generated__/iam/ViewerDropdownSignOutMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<8cb399eb2f38eaf3059531ea4bc3fa30>> + * @generated SignedSource<<93ee9324d2ee86042f9f46eda06d0770>> * @lightSyntaxTransform * @nogrep */ @@ -9,15 +9,15 @@ // @ts-nocheck import { ConcreteRequest } from 'relay-runtime'; -export type SessionDropdownSignOutMutation$variables = Record; -export type SessionDropdownSignOutMutation$data = { +export type ViewerDropdownSignOutMutation$variables = Record; +export type ViewerDropdownSignOutMutation$data = { readonly signOut: { readonly success: boolean; } | null | undefined; }; -export type SessionDropdownSignOutMutation = { - response: SessionDropdownSignOutMutation$data; - variables: SessionDropdownSignOutMutation$variables; +export type ViewerDropdownSignOutMutation = { + response: ViewerDropdownSignOutMutation$data; + variables: ViewerDropdownSignOutMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -46,7 +46,7 @@ return { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, - "name": "SessionDropdownSignOutMutation", + "name": "ViewerDropdownSignOutMutation", "selections": (v0/*: any*/), "type": "Mutation", "abstractKey": null @@ -55,20 +55,20 @@ return { "operation": { "argumentDefinitions": [], "kind": "Operation", - "name": "SessionDropdownSignOutMutation", + "name": "ViewerDropdownSignOutMutation", "selections": (v0/*: any*/) }, "params": { - "cacheID": "a677190a127d7f75d5c629fab236a7a3", + "cacheID": "9252bcfdab3361ae1690a713e2765de3", "id": null, "metadata": {}, - "name": "SessionDropdownSignOutMutation", + "name": "ViewerDropdownSignOutMutation", "operationKind": "mutation", - "text": "mutation SessionDropdownSignOutMutation {\n signOut {\n success\n }\n}\n" + "text": "mutation ViewerDropdownSignOutMutation {\n signOut {\n success\n }\n}\n" } }; })(); -(node as any).hash = "10c9bdf35ab3961c7488973e052cf978"; +(node as any).hash = "a08b0de590063055e1ccbacd1d443a0f"; export default node; diff --git a/apps/console/src/__generated__/iam/ViewerLayoutQuery.graphql.ts b/apps/console/src/__generated__/iam/ViewerLayoutQuery.graphql.ts new file mode 100644 index 000000000..6c6bacbc8 --- /dev/null +++ b/apps/console/src/__generated__/iam/ViewerLayoutQuery.graphql.ts @@ -0,0 +1,120 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type ViewerLayoutQuery$variables = Record; +export type ViewerLayoutQuery$data = { + readonly viewer: { + readonly " $fragmentSpreads": FragmentRefs<"ViewerDropdownFragment">; + }; +}; +export type ViewerLayoutQuery = { + response: ViewerLayoutQuery$data; + variables: ViewerLayoutQuery$variables; +}; + +const node: ConcreteRequest = { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "ViewerLayoutQuery", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "args": null, + "kind": "FragmentSpread", + "name": "ViewerDropdownFragment" + } + ], + "storageKey": null + }, + "action": "THROW" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "ViewerLayoutQuery", + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "viewer", + "plural": false, + "selections": [ + { + "alias": "canListAPIKeys", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:personal-api-key:list" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:personal-api-key:list\")" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "faf40277113685d45576f1b466123787", + "id": null, + "metadata": {}, + "name": "ViewerLayoutQuery", + "operationKind": "query", + "text": "query ViewerLayoutQuery {\n viewer {\n ...ViewerDropdownFragment\n id\n }\n}\n\nfragment ViewerDropdownFragment on Identity {\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n email\n fullName\n}\n" + } +}; + +(node as any).hash = "48e174d981dbc9198a56aac976a20b21"; + +export default node; diff --git a/apps/console/src/__generated__/iam/SessionDropdownFragment.graphql.ts b/apps/console/src/__generated__/iam/ViewerMembershipDropdownFragment.graphql.ts similarity index 68% rename from apps/console/src/__generated__/iam/SessionDropdownFragment.graphql.ts rename to apps/console/src/__generated__/iam/ViewerMembershipDropdownFragment.graphql.ts index f50ce6d87..fb1c9d06d 100644 --- a/apps/console/src/__generated__/iam/SessionDropdownFragment.graphql.ts +++ b/apps/console/src/__generated__/iam/ViewerMembershipDropdownFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<73d5a0059348fd0c5950ae3a873cc383>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -10,42 +10,29 @@ import { ReaderFragment } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; -export type SessionDropdownFragment$data = { - readonly canDelete: boolean; +export type ViewerMembershipDropdownFragment$data = { readonly viewerMembership: { readonly identity: { + readonly canListAPIKeys: boolean; readonly email: string; }; readonly profile: { readonly fullName: string; }; }; - readonly " $fragmentType": "SessionDropdownFragment"; + readonly " $fragmentType": "ViewerMembershipDropdownFragment"; }; -export type SessionDropdownFragment$key = { - readonly " $data"?: SessionDropdownFragment$data; - readonly " $fragmentSpreads": FragmentRefs<"SessionDropdownFragment">; +export type ViewerMembershipDropdownFragment$key = { + readonly " $data"?: ViewerMembershipDropdownFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"ViewerMembershipDropdownFragment">; }; const node: ReaderFragment = { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, - "name": "SessionDropdownFragment", + "name": "ViewerMembershipDropdownFragment", "selections": [ - { - "alias": "canDelete", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "iam:organization:delete" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"iam:organization:delete\")" - }, { "kind": "RequiredField", "field": { @@ -72,6 +59,19 @@ const node: ReaderFragment = { "kind": "ScalarField", "name": "email", "storageKey": null + }, + { + "alias": "canListAPIKeys", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:personal-api-key:list" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:personal-api-key:list\")" } ], "storageKey": null @@ -110,6 +110,6 @@ const node: ReaderFragment = { "abstractKey": null }; -(node as any).hash = "51810eafbf165e7b5eeec42cb268ae54"; +(node as any).hash = "82c179a1ec2b90ce5f12c0959f97150f"; export default node; diff --git a/apps/console/src/__generated__/iam/ViewerMembershipDropdownSignOutMutation.graphql.ts b/apps/console/src/__generated__/iam/ViewerMembershipDropdownSignOutMutation.graphql.ts new file mode 100644 index 000000000..90cb746e2 --- /dev/null +++ b/apps/console/src/__generated__/iam/ViewerMembershipDropdownSignOutMutation.graphql.ts @@ -0,0 +1,74 @@ +/** + * @generated SignedSource<<5d6fc3e9d931dccebc57b68196c98bed>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type ViewerMembershipDropdownSignOutMutation$variables = Record; +export type ViewerMembershipDropdownSignOutMutation$data = { + readonly signOut: { + readonly success: boolean; + } | null | undefined; +}; +export type ViewerMembershipDropdownSignOutMutation = { + response: ViewerMembershipDropdownSignOutMutation$data; + variables: ViewerMembershipDropdownSignOutMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "alias": null, + "args": null, + "concreteType": "SignOutPayload", + "kind": "LinkedField", + "name": "signOut", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "success", + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "ViewerMembershipDropdownSignOutMutation", + "selections": (v0/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [], + "kind": "Operation", + "name": "ViewerMembershipDropdownSignOutMutation", + "selections": (v0/*: any*/) + }, + "params": { + "cacheID": "da5fc8d24f8fac6617c4e4de8d9a629d", + "id": null, + "metadata": {}, + "name": "ViewerMembershipDropdownSignOutMutation", + "operationKind": "mutation", + "text": "mutation ViewerMembershipDropdownSignOutMutation {\n signOut {\n success\n }\n}\n" + } +}; +})(); + +(node as any).hash = "856ff5d4affd9ad27871ab5f4fef3b1e"; + +export default node; diff --git a/apps/console/src/__generated__/iam/MembershipLayoutQuery.graphql.ts b/apps/console/src/__generated__/iam/ViewerMembershipLayoutQuery.graphql.ts similarity index 83% rename from apps/console/src/__generated__/iam/MembershipLayoutQuery.graphql.ts rename to apps/console/src/__generated__/iam/ViewerMembershipLayoutQuery.graphql.ts index c3fccbdbb..898a35ad1 100644 --- a/apps/console/src/__generated__/iam/MembershipLayoutQuery.graphql.ts +++ b/apps/console/src/__generated__/iam/ViewerMembershipLayoutQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<4268d9e187cf29b47f9438cf48cc3e73>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -11,11 +11,11 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER"; -export type MembershipLayoutQuery$variables = { +export type ViewerMembershipLayoutQuery$variables = { hideSidebar: boolean; organizationId: string; }; -export type MembershipLayoutQuery$data = { +export type ViewerMembershipLayoutQuery$data = { readonly organization: { readonly __typename: "Organization"; readonly viewerMembership: { @@ -24,7 +24,7 @@ export type MembershipLayoutQuery$data = { }; readonly role: MembershipRole; }; - readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SessionDropdownFragment" | "SidebarFragment">; + readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_organizationFragment" | "SidebarFragment" | "ViewerMembershipDropdownFragment">; } | { // This will never be '%other', but we need some // value in case none of the concrete values match. @@ -38,9 +38,9 @@ export type MembershipLayoutQuery$data = { readonly " $fragmentSpreads": FragmentRefs<"MembershipsDropdown_viewerFragment">; }; }; -export type MembershipLayoutQuery = { - response: MembershipLayoutQuery$data; - variables: MembershipLayoutQuery$variables; +export type ViewerMembershipLayoutQuery = { + response: ViewerMembershipLayoutQuery$data; + variables: ViewerMembershipLayoutQuery$variables; }; const node: ConcreteRequest = (function(){ @@ -111,7 +111,7 @@ return { ], "kind": "Fragment", "metadata": null, - "name": "MembershipLayoutQuery", + "name": "ViewerMembershipLayoutQuery", "selections": [ { "kind": "RequiredField", @@ -135,7 +135,7 @@ return { { "args": null, "kind": "FragmentSpread", - "name": "SessionDropdownFragment" + "name": "ViewerMembershipDropdownFragment" }, { "condition": "hideSidebar", @@ -242,7 +242,7 @@ return { (v0/*: any*/) ], "kind": "Operation", - "name": "MembershipLayoutQuery", + "name": "ViewerMembershipLayoutQuery", "selections": [ { "alias": "organization", @@ -263,19 +263,6 @@ return { "name": "name", "storageKey": null }, - { - "alias": "canDelete", - "args": [ - { - "kind": "Literal", - "name": "action", - "value": "iam:organization:delete" - } - ], - "kind": "ScalarField", - "name": "permission", - "storageKey": "permission(action:\"iam:organization:delete\")" - }, { "alias": null, "args": null, @@ -293,6 +280,19 @@ return { "plural": false, "selections": [ (v6/*: any*/), + { + "alias": "canListAPIKeys", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:personal-api-key:list" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:personal-api-key:list\")" + }, (v8/*: any*/) ], "storageKey": null @@ -618,16 +618,16 @@ return { ] }, "params": { - "cacheID": "5f50873fa9c14115e307b43d7c1ae822", + "cacheID": "d7e0b080876472d059c97bf57818c697", "id": null, "metadata": {}, - "name": "MembershipLayoutQuery", + "name": "ViewerMembershipLayoutQuery", "operationKind": "query", - "text": "query MembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...SessionDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SessionDropdownFragment on Organization {\n canDelete: permission(action: \"iam:organization:delete\")\n viewerMembership {\n identity {\n email\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n" + "text": "query ViewerMembershipLayoutQuery(\n $organizationId: ID!\n $hideSidebar: Boolean!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n ...MembershipsDropdown_organizationFragment\n ...ViewerMembershipDropdownFragment\n ...SidebarFragment @skip(if: $hideSidebar)\n viewerMembership {\n role\n profile {\n fullName\n id\n }\n id\n }\n }\n id\n }\n viewer {\n email\n ...MembershipsDropdown_viewerFragment\n pendingInvitations {\n totalCount\n }\n id\n }\n}\n\nfragment MembershipsDropdown_organizationFragment on Organization {\n name\n}\n\nfragment MembershipsDropdown_viewerFragment on Identity {\n pendingInvitations {\n totalCount\n }\n}\n\nfragment SidebarFragment on Organization {\n canListMeetings: permission(action: \"core:meeting:list\")\n canListTasks: permission(action: \"core:task:list\")\n canListMeasures: permission(action: \"core:measures:list\")\n canListRisks: permission(action: \"core:risk:list\")\n canListFrameworks: permission(action: \"core:frameworks:list\")\n canListPeople: permission(action: \"core:people:list\")\n canListVendors: permission(action: \"core:vendor:list\")\n canListDocuments: permission(action: \"core:document:list\")\n canListAssets: permission(action: \"core:asset:list\")\n canListData: permission(action: \"core:datum:list\")\n canListAudits: permission(action: \"core:audit:list\")\n canListNonconformities: permission(action: \"core:nonconformity:list\")\n canListObligations: permission(action: \"core:obligation:list\")\n canListContinualImprovements: permission(action: \"core:continual-improvement:list\")\n canListProcessingActivities: permission(action: \"core:processing-activity:list\")\n canListRightsRequests: permission(action: \"core:rights-request:list\")\n canListSnapshots: permission(action: \"core:snapshot:list\")\n canGetTrustCenter: permission(action: \"core:trust-center:get\")\n canUpdateOrganization: permission(action: \"iam:organization:update\")\n canListStatesOfApplicability: permission(action: \"core:state-of-applicability:list\")\n}\n\nfragment ViewerMembershipDropdownFragment on Organization {\n viewerMembership {\n identity {\n email\n canListAPIKeys: permission(action: \"iam:personal-api-key:list\")\n id\n }\n profile {\n fullName\n id\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "e52fc7540b2908e5b4f2c4cef33b5b81"; +(node as any).hash = "298aba62d7920932f86433919c623d4c"; export default node; diff --git a/apps/console/src/pages/iam/memberships/ViewerLayout.tsx b/apps/console/src/pages/iam/memberships/ViewerLayout.tsx new file mode 100644 index 000000000..b81b6dc31 --- /dev/null +++ b/apps/console/src/pages/iam/memberships/ViewerLayout.tsx @@ -0,0 +1,42 @@ +import { graphql, usePreloadedQuery, type PreloadedQuery } from "react-relay"; +import { Outlet } from "react-router"; +import { CenteredLayout, Layout, Skeleton } from "@probo/ui"; +import type { ViewerLayoutQuery } from "/__generated__/iam/ViewerLayoutQuery.graphql"; +import { Suspense } from "react"; +import { ViewerDropdown } from "./_components/ViewerDropdown"; + +export const viewerLayoutQuery = graphql` + query ViewerLayoutQuery { + viewer @required(action: THROW) { + ...ViewerDropdownFragment + } + } +`; + +export function ViewerLayout(props: { + hideSidebar?: boolean; + queryRef: PreloadedQuery; +}) { + const { queryRef } = props; + + const { viewer } = usePreloadedQuery( + viewerLayoutQuery, + queryRef, + ); + + return ( + + }> + + + + } + > + + + + + ); +} diff --git a/apps/console/src/pages/iam/memberships/ViewerLayoutLoader.tsx b/apps/console/src/pages/iam/memberships/ViewerLayoutLoader.tsx new file mode 100644 index 000000000..9629d5297 --- /dev/null +++ b/apps/console/src/pages/iam/memberships/ViewerLayoutLoader.tsx @@ -0,0 +1,33 @@ +import { useQueryLoader } from "react-relay"; +import { Suspense, useEffect } from "react"; +import { CenteredLayoutSkeleton } from "@probo/ui"; +import type { ViewerLayoutQuery } from "/__generated__/iam/ViewerLayoutQuery.graphql"; +import { ViewerLayout, viewerLayoutQuery } from "./ViewerLayout"; +import { IAMRelayProvider } from "/providers/IAMRelayProvider"; + +function ViewerLayoutLoader() { + const [queryRef, loadQuery] = + useQueryLoader(viewerLayoutQuery); + + useEffect(() => { + loadQuery({}); + }, [loadQuery]); + + if (!queryRef) { + return ; + } + + return ( + }> + + + ); +} + +export default function () { + return ( + + + + ); +} diff --git a/apps/console/src/pages/iam/memberships/_components/ViewerDropdown.tsx b/apps/console/src/pages/iam/memberships/_components/ViewerDropdown.tsx new file mode 100644 index 000000000..6bec1e5f0 --- /dev/null +++ b/apps/console/src/pages/iam/memberships/_components/ViewerDropdown.tsx @@ -0,0 +1,92 @@ +import { + DropdownSeparator, + IconArrowBoxLeft, + IconCircleQuestionmark, + IconKey, + UserDropdown, + UserDropdownItem, + useToast, +} from "@probo/ui"; +import { graphql } from "relay-runtime"; +import { useFragment, useMutation } from "react-relay"; +import { useTranslate } from "@probo/i18n"; +import type { ViewerDropdownFragment$key } from "/__generated__/iam/ViewerDropdownFragment.graphql"; +import { formatError } from "@probo/helpers"; + +export const fragment = graphql` + fragment ViewerDropdownFragment on Identity { + canListAPIKeys: permission(action: "iam:personal-api-key:list") + email + fullName + } +`; + +const signOutMutation = graphql` + mutation ViewerDropdownSignOutMutation { + signOut { + success + } + } +`; + +export function ViewerDropdown(props: { fKey: ViewerDropdownFragment$key }) { + const { fKey } = props; + + const { __ } = useTranslate(); + const { toast } = useToast(); + + const { canListAPIKeys, email, fullName } = + useFragment(fragment, fKey); + const [signOut] = useMutation(signOutMutation); + + const handleLogout: React.MouseEventHandler = (e) => { + e.preventDefault(); + + signOut({ + variables: {}, + onCompleted: (_, e) => { + if (e) { + toast({ + title: __("Request failed"), + description: formatError(__("Cannot sign out"), e), + variant: "error", + }); + return; + } + window.location.reload(); + }, + onError: (e) => { + toast({ + title: __("Error"), + description: e.message, + variant: "error", + }); + }, + }); + }; + + return ( + + {canListAPIKeys && ( + + )} + + + + + ); +} diff --git a/apps/console/src/pages/iam/memberships/MembershipLayout.tsx b/apps/console/src/pages/iam/organizations/ViewerMembershipLayout.tsx similarity index 52% rename from apps/console/src/pages/iam/memberships/MembershipLayout.tsx rename to apps/console/src/pages/iam/organizations/ViewerMembershipLayout.tsx index a5a2ca8ce..914003ac1 100644 --- a/apps/console/src/pages/iam/memberships/MembershipLayout.tsx +++ b/apps/console/src/pages/iam/organizations/ViewerMembershipLayout.tsx @@ -1,22 +1,25 @@ import { graphql, usePreloadedQuery, type PreloadedQuery } from "react-relay"; import { Link, Outlet } from "react-router"; import { Badge, Button, IconPeopleAdd, Layout, Skeleton } from "@probo/ui"; -import { Sidebar } from "./_components/Sidebar"; -import { MembershipsDropdown } from "./MembershipsDropdown"; -import type { MembershipLayoutQuery } from "/__generated__/iam/MembershipLayoutQuery.graphql"; -import { SessionDropdown } from "./_components/SessionDropdown"; +import { Sidebar } from "../memberships/_components/Sidebar"; +import { MembershipsDropdown } from "../memberships/MembershipsDropdown"; +import type { ViewerMembershipLayoutQuery } from "/__generated__/iam/ViewerMembershipLayoutQuery.graphql"; +import { ViewerMembershipDropdown } from "./_components/ViewerMembershipDropdown"; import { Suspense } from "react"; import { useTranslate } from "@probo/i18n"; import { CoreRelayProvider } from "/providers/CoreRelayProvider"; import { CurrentUser } from "/providers/CurrentUser"; -export const membershipLayoutQuery = graphql` - query MembershipLayoutQuery($organizationId: ID!, $hideSidebar: Boolean!) { +export const viewerMembershipLayoutQuery = graphql` + query ViewerMembershipLayoutQuery( + $organizationId: ID! + $hideSidebar: Boolean! + ) { organization: node(id: $organizationId) @required(action: THROW) { __typename ... on Organization { ...MembershipsDropdown_organizationFragment - ...SessionDropdownFragment + ...ViewerMembershipDropdownFragment ...SidebarFragment @skip(if: $hideSidebar) viewerMembership @required(action: THROW) { role @@ -36,49 +39,50 @@ export const membershipLayoutQuery = graphql` } `; -export function MembershipLayout(props: { +export function ViewerMembershipLayout(props: { hideSidebar?: boolean; - queryRef: PreloadedQuery; + queryRef: PreloadedQuery; }) { const { hideSidebar = false, queryRef } = props; const { __ } = useTranslate(); - const { organization, viewer } = usePreloadedQuery( - membershipLayoutQuery, - queryRef, - ); + const { organization, viewer } = + usePreloadedQuery( + viewerMembershipLayoutQuery, + queryRef, + ); if (organization.__typename !== "Organization") { throw new Error("invalid type for organization node"); } return ( -
- - {viewer.pendingInvitations.totalCount > 0 && ( - -
- }> - - + + {viewer.pendingInvitations.totalCount > 0 && ( + +