From f033338cdafc4365be48890ce582f91d29b6731d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 11 Feb 2026 16:39:22 +0400 Subject: [PATCH] Keep both people and members pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Émile Ré --- .../iam/PeopleListFragment.graphql.ts | 244 ++++++++++ ...PeopleListFragment_RefetchQuery.graphql.ts | 432 ++++++++++++++++++ .../iam/PeopleListItemFragment.graphql.ts | 177 +++++++ .../PeopleListItem_removeMutation.graphql.ts | 133 ++++++ .../iam/PeoplePageQuery.graphql.ts | 370 +++++++++++++++ ...aphql.ts => PersonFormFragment.graphql.ts} | 16 +- ...aphql.ts => PersonFormMutation.graphql.ts} | 24 +- ....graphql.ts => PersonPageQuery.graphql.ts} | 40 +- ...s => PersonPage_removeMutation.graphql.ts} | 24 +- .../src/components/tasks/TasksCard.tsx | 2 +- .../iam/organizations/_components/Sidebar.tsx | 2 +- .../iam/organizations/people/PeoplePage.tsx | 44 ++ .../organizations/people/PeoplePageLoader.tsx | 34 ++ .../UserPage.tsx => people/PersonPage.tsx} | 40 +- .../organizations/people/PersonPageLoader.tsx | 35 ++ .../people/_components/PeopleList.tsx | 101 ++++ .../people/_components/PeopleListItem.tsx | 172 +++++++ .../_components/PersonForm.tsx} | 42 +- .../organizations/{users => people}/routes.ts | 10 +- .../{users => settings}/MembersPage.tsx | 2 +- .../{users => settings}/MembersPageLoader.tsx | 0 .../organizations/settings/SettingsLayout.tsx | 5 + .../settings/_components/EditMemberDialog.tsx | 5 +- .../_components/InvitationList.tsx | 0 .../_components/InvitationListItem.tsx | 0 .../_components/InviteUserDialog.tsx | 0 .../_components/MemberList.tsx | 0 .../_components/MemberListItem.tsx | 4 +- .../organizations/users/UserPageLoader.tsx | 35 -- .../meetings/MeetingDetailPage.tsx | 2 +- .../organizations/meetings/MeetingsPage.tsx | 2 +- apps/console/src/routes.tsx | 11 +- 32 files changed, 1864 insertions(+), 144 deletions(-) create mode 100644 apps/console/src/__generated__/iam/PeopleListFragment.graphql.ts create mode 100644 apps/console/src/__generated__/iam/PeopleListFragment_RefetchQuery.graphql.ts create mode 100644 apps/console/src/__generated__/iam/PeopleListItemFragment.graphql.ts create mode 100644 apps/console/src/__generated__/iam/PeopleListItem_removeMutation.graphql.ts create mode 100644 apps/console/src/__generated__/iam/PeoplePageQuery.graphql.ts rename apps/console/src/__generated__/iam/{UserFormFragment.graphql.ts => PersonFormFragment.graphql.ts} (84%) rename apps/console/src/__generated__/iam/{UserFormMutation.graphql.ts => PersonFormMutation.graphql.ts} (76%) rename apps/console/src/__generated__/iam/{UserPageQuery.graphql.ts => PersonPageQuery.graphql.ts} (81%) rename apps/console/src/__generated__/iam/{UserPage_removeMutation.graphql.ts => PersonPage_removeMutation.graphql.ts} (68%) create mode 100644 apps/console/src/pages/iam/organizations/people/PeoplePage.tsx create mode 100644 apps/console/src/pages/iam/organizations/people/PeoplePageLoader.tsx rename apps/console/src/pages/iam/organizations/{users/UserPage.tsx => people/PersonPage.tsx} (68%) create mode 100644 apps/console/src/pages/iam/organizations/people/PersonPageLoader.tsx create mode 100644 apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx create mode 100644 apps/console/src/pages/iam/organizations/people/_components/PeopleListItem.tsx rename apps/console/src/pages/iam/organizations/{users/_components/UserForm.tsx => people/_components/PersonForm.tsx} (74%) rename apps/console/src/pages/iam/organizations/{users => people}/routes.ts (75%) rename apps/console/src/pages/iam/organizations/{users => settings}/MembersPage.tsx (98%) rename apps/console/src/pages/iam/organizations/{users => settings}/MembersPageLoader.tsx (100%) rename apps/console/src/pages/iam/organizations/{users => settings}/_components/InvitationList.tsx (100%) rename apps/console/src/pages/iam/organizations/{users => settings}/_components/InvitationListItem.tsx (100%) rename apps/console/src/pages/iam/organizations/{users => settings}/_components/InviteUserDialog.tsx (100%) rename apps/console/src/pages/iam/organizations/{users => settings}/_components/MemberList.tsx (100%) rename apps/console/src/pages/iam/organizations/{users => settings}/_components/MemberListItem.tsx (96%) delete mode 100644 apps/console/src/pages/iam/organizations/users/UserPageLoader.tsx diff --git a/apps/console/src/__generated__/iam/PeopleListFragment.graphql.ts b/apps/console/src/__generated__/iam/PeopleListFragment.graphql.ts new file mode 100644 index 000000000..c2c83f9db --- /dev/null +++ b/apps/console/src/__generated__/iam/PeopleListFragment.graphql.ts @@ -0,0 +1,244 @@ +/** + * @generated SignedSource<<1eea7fe2bc0c652d4b8aa50f263a0abe>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type PeopleListFragment$data = { + readonly id: string; + readonly members: { + readonly __id: string; + readonly edges: ReadonlyArray<{ + readonly node: { + readonly id: string; + readonly " $fragmentSpreads": FragmentRefs<"PeopleListItemFragment">; + }; + }>; + readonly totalCount: number | null | undefined; + }; + readonly " $fragmentType": "PeopleListFragment"; +}; +export type PeopleListFragment$key = { + readonly " $data"?: PeopleListFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"PeopleListFragment">; +}; + +import PeopleListFragment_RefetchQuery_graphql from './PeopleListFragment_RefetchQuery.graphql'; + +const node: ReaderFragment = (function(){ +var v0 = [ + "members" +], +v1 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" + }, + { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" + }, + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" + }, + { + "defaultValue": { + "direction": "ASC", + "field": "FULL_NAME" + }, + "kind": "LocalArgument", + "name": "order" + } + ], + "kind": "Fragment", + "metadata": { + "connection": [ + { + "count": null, + "cursor": null, + "direction": "bidirectional", + "path": (v0/*: any*/) + } + ], + "refetch": { + "connection": { + "forward": { + "count": "first", + "cursor": "after" + }, + "backward": { + "count": "last", + "cursor": "before" + }, + "path": (v0/*: any*/) + }, + "fragmentPathInResult": [ + "node" + ], + "operation": PeopleListFragment_RefetchQuery_graphql, + "identifierInfo": { + "identifierField": "id", + "identifierQueryVariableName": "id" + } + } + }, + "name": "PeopleListFragment", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": "members", + "args": [ + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } + ], + "concreteType": "MembershipConnection", + "kind": "LinkedField", + "name": "__PeopleListFragment_members_connection", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "MembershipEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Membership", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v1/*: any*/), + { + "args": null, + "kind": "FragmentSpread", + "name": "PeopleListItemFragment" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "cursor", + "storageKey": null + } + ], + "storageKey": null + }, + "action": "THROW" + }, + { + "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 + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + "action": "THROW" + }, + (v1/*: any*/) + ], + "type": "Organization", + "abstractKey": null +}; +})(); + +(node as any).hash = "b081725658cd706a0b108eb1d058de32"; + +export default node; diff --git a/apps/console/src/__generated__/iam/PeopleListFragment_RefetchQuery.graphql.ts b/apps/console/src/__generated__/iam/PeopleListFragment_RefetchQuery.graphql.ts new file mode 100644 index 000000000..ca33e0bcd --- /dev/null +++ b/apps/console/src/__generated__/iam/PeopleListFragment_RefetchQuery.graphql.ts @@ -0,0 +1,432 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type MembershipOrderField = "CREATED_AT" | "EMAIL_ADDRESS" | "FULL_NAME" | "ORGANIZATION_NAME" | "ROLE"; +export type OrderDirection = "ASC" | "DESC"; +export type MembershipOrder = { + direction: OrderDirection; + field: MembershipOrderField; +}; +export type PeopleListFragment_RefetchQuery$variables = { + after?: string | null | undefined; + before?: string | null | undefined; + first?: number | null | undefined; + id: string; + last?: number | null | undefined; + order?: MembershipOrder | null | undefined; +}; +export type PeopleListFragment_RefetchQuery$data = { + readonly node: { + readonly " $fragmentSpreads": FragmentRefs<"PeopleListFragment">; + } | null | undefined; +}; +export type PeopleListFragment_RefetchQuery = { + response: PeopleListFragment_RefetchQuery$data; + variables: PeopleListFragment_RefetchQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "before" +}, +v2 = { + "defaultValue": 20, + "kind": "LocalArgument", + "name": "first" +}, +v3 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "id" +}, +v4 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "last" +}, +v5 = { + "defaultValue": { + "direction": "ASC", + "field": "FULL_NAME" + }, + "kind": "LocalArgument", + "name": "order" +}, +v6 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "id" + } +], +v7 = { + "kind": "Variable", + "name": "after", + "variableName": "after" +}, +v8 = { + "kind": "Variable", + "name": "before", + "variableName": "before" +}, +v9 = { + "kind": "Variable", + "name": "first", + "variableName": "first" +}, +v10 = { + "kind": "Variable", + "name": "last", + "variableName": "last" +}, +v11 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v12 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v13 = [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "orderBy", + "variableName": "order" + } +]; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v3/*: any*/), + (v4/*: any*/), + (v5/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "PeopleListFragment_RefetchQuery", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "args": [ + (v7/*: any*/), + (v8/*: any*/), + (v9/*: any*/), + (v10/*: any*/), + { + "kind": "Variable", + "name": "order", + "variableName": "order" + } + ], + "kind": "FragmentSpread", + "name": "PeopleListFragment" + } + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/), + (v4/*: any*/), + (v5/*: any*/), + (v3/*: any*/) + ], + "kind": "Operation", + "name": "PeopleListFragment_RefetchQuery", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v11/*: any*/), + (v12/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v13/*: any*/), + "concreteType": "MembershipConnection", + "kind": "LinkedField", + "name": "members", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MembershipEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Membership", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v12/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "role", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MembershipProfile", + "kind": "LinkedField", + "name": "profile", + "plural": false, + "selections": [ + (v12/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "kind", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "position", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "identity", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + (v12/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership:delete\")" + }, + (v11/*: 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 + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v13/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "PeopleListFragment_members", + "kind": "LinkedHandle", + "name": "members" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "7b0c4eff8642cc6d595afdd01f4a643e", + "id": null, + "metadata": {}, + "name": "PeopleListFragment_RefetchQuery", + "operationKind": "query", + "text": "query PeopleListFragment_RefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: FULL_NAME}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...PeopleListFragment_16fISc\n id\n }\n}\n\nfragment PeopleListFragment_16fISc on Organization {\n members(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n id\n fullName\n kind\n position\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n" + } +}; +})(); + +(node as any).hash = "b081725658cd706a0b108eb1d058de32"; + +export default node; diff --git a/apps/console/src/__generated__/iam/PeopleListItemFragment.graphql.ts b/apps/console/src/__generated__/iam/PeopleListItemFragment.graphql.ts new file mode 100644 index 000000000..2327cfef0 --- /dev/null +++ b/apps/console/src/__generated__/iam/PeopleListItemFragment.graphql.ts @@ -0,0 +1,177 @@ +/** + * @generated SignedSource<<6739397aaeb3d7890fd697ab4323736a>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ReaderFragment } from 'relay-runtime'; +export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER"; +export type MembershipSource = "MANUAL" | "SAML" | "SCIM"; +export type MembershipState = "ACTIVE" | "INACTIVE"; +export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT"; +import { FragmentRefs } from "relay-runtime"; +export type PeopleListItemFragment$data = { + readonly canDelete: boolean; + readonly canUpdate: boolean; + readonly createdAt: string; + readonly id: string; + readonly identity: { + readonly email: string; + }; + readonly profile: { + readonly fullName: string; + readonly id: string; + readonly kind: ProfileKind; + readonly position: string | null | undefined; + }; + readonly role: MembershipRole; + readonly source: MembershipSource; + readonly state: MembershipState; + readonly " $fragmentType": "PeopleListItemFragment"; +}; +export type PeopleListItemFragment$key = { + readonly " $data"?: PeopleListItemFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"PeopleListItemFragment">; +}; + +const node: ReaderFragment = (function(){ +var v0 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}; +return { + "argumentDefinitions": [], + "kind": "Fragment", + "metadata": null, + "name": "PeopleListItemFragment", + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "role", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "MembershipProfile", + "kind": "LinkedField", + "name": "profile", + "plural": false, + "selections": [ + (v0/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "kind", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "position", + "storageKey": null + } + ], + "storageKey": null + }, + "action": "THROW" + }, + { + "kind": "RequiredField", + "field": { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "identity", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + } + ], + "storageKey": null + }, + "action": "THROW" + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership:delete\")" + } + ], + "type": "Membership", + "abstractKey": null +}; +})(); + +(node as any).hash = "cf1683d15b13975d8f739c3a7aa38d7e"; + +export default node; diff --git a/apps/console/src/__generated__/iam/PeopleListItem_removeMutation.graphql.ts b/apps/console/src/__generated__/iam/PeopleListItem_removeMutation.graphql.ts new file mode 100644 index 000000000..9383ba305 --- /dev/null +++ b/apps/console/src/__generated__/iam/PeopleListItem_removeMutation.graphql.ts @@ -0,0 +1,133 @@ +/** + * @generated SignedSource<<8c0cc8f8d9d0418ea1d817412793f2dc>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type RemoveMemberInput = { + membershipId: string; + organizationId: string; +}; +export type PeopleListItem_removeMutation$variables = { + connections: ReadonlyArray; + input: RemoveMemberInput; +}; +export type PeopleListItem_removeMutation$data = { + readonly removeMember: { + readonly deletedMembershipId: string; + } | null | undefined; +}; +export type PeopleListItem_removeMutation = { + response: PeopleListItem_removeMutation$data; + variables: PeopleListItem_removeMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "connections" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" +}, +v2 = [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } +], +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "deletedMembershipId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "PeopleListItem_removeMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "RemoveMemberPayload", + "kind": "LinkedField", + "name": "removeMember", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "PeopleListItem_removeMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "RemoveMemberPayload", + "kind": "LinkedField", + "name": "removeMember", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedMembershipId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "d5a0e195ee72906380f566bcecb45a3f", + "id": null, + "metadata": {}, + "name": "PeopleListItem_removeMutation", + "operationKind": "mutation", + "text": "mutation PeopleListItem_removeMutation(\n $input: RemoveMemberInput!\n) {\n removeMember(input: $input) {\n deletedMembershipId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "c1bceb22b556aff77543ec1956445187"; + +export default node; diff --git a/apps/console/src/__generated__/iam/PeoplePageQuery.graphql.ts b/apps/console/src/__generated__/iam/PeoplePageQuery.graphql.ts new file mode 100644 index 000000000..cddc0db3e --- /dev/null +++ b/apps/console/src/__generated__/iam/PeoplePageQuery.graphql.ts @@ -0,0 +1,370 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type PeoplePageQuery$variables = { + organizationId: string; +}; +export type PeoplePageQuery$data = { + readonly organization: { + readonly __typename: string; + readonly " $fragmentSpreads": FragmentRefs<"PeopleListFragment">; + }; +}; +export type PeoplePageQuery = { + response: PeoplePageQuery$data; + variables: PeoplePageQuery$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 = { + "kind": "Literal", + "name": "first", + "value": 20 +}, +v4 = { + "direction": "ASC", + "field": "FULL_NAME" +}, +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v6 = [ + (v3/*: any*/), + { + "kind": "Literal", + "name": "orderBy", + "value": (v4/*: any*/) + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "PeoplePageQuery", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "args": [ + (v3/*: any*/), + { + "kind": "Literal", + "name": "order", + "value": (v4/*: any*/) + } + ], + "kind": "FragmentSpread", + "name": "PeopleListFragment" + } + ], + "storageKey": null + }, + "action": "THROW" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "PeoplePageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v5/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": (v6/*: any*/), + "concreteType": "MembershipConnection", + "kind": "LinkedField", + "name": "members", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MembershipEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "Membership", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v5/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "role", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "source", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "state", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "MembershipProfile", + "kind": "LinkedField", + "name": "profile", + "plural": false, + "selections": [ + (v5/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "fullName", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "kind", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "position", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "Identity", + "kind": "LinkedField", + "name": "identity", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "email", + "storageKey": null + }, + (v5/*: any*/) + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": "canUpdate", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:update" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership:update\")" + }, + { + "alias": "canDelete", + "args": [ + { + "kind": "Literal", + "name": "action", + "value": "iam:membership:delete" + } + ], + "kind": "ScalarField", + "name": "permission", + "storageKey": "permission(action:\"iam:membership: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 + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasPreviousPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "startCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "kind": "ClientExtension", + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__id", + "storageKey": null + } + ] + } + ], + "storageKey": "members(first:20,orderBy:{\"direction\":\"ASC\",\"field\":\"FULL_NAME\"})" + }, + { + "alias": null, + "args": (v6/*: any*/), + "filters": [ + "orderBy" + ], + "handle": "connection", + "key": "PeopleListFragment_members", + "kind": "LinkedHandle", + "name": "members" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "e053ac08939a9072b8824317b327687b", + "id": null, + "metadata": {}, + "name": "PeoplePageQuery", + "operationKind": "query", + "text": "query PeoplePageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ...PeopleListFragment_8lnpd\n id\n }\n}\n\nfragment PeopleListFragment_8lnpd on Organization {\n members(first: 20, orderBy: {direction: ASC, field: FULL_NAME}) {\n totalCount\n edges {\n node {\n id\n ...PeopleListItemFragment\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n\nfragment PeopleListItemFragment on Membership {\n id\n role\n source\n state\n profile {\n id\n fullName\n kind\n position\n }\n identity {\n email\n id\n }\n createdAt\n canUpdate: permission(action: \"iam:membership:update\")\n canDelete: permission(action: \"iam:membership:delete\")\n}\n" + } +}; +})(); + +(node as any).hash = "cf98b65e0392a5c2d48254d89b114c04"; + +export default node; diff --git a/apps/console/src/__generated__/iam/UserFormFragment.graphql.ts b/apps/console/src/__generated__/iam/PersonFormFragment.graphql.ts similarity index 84% rename from apps/console/src/__generated__/iam/UserFormFragment.graphql.ts rename to apps/console/src/__generated__/iam/PersonFormFragment.graphql.ts index 96e595d4b..cac3bf24f 100644 --- a/apps/console/src/__generated__/iam/UserFormFragment.graphql.ts +++ b/apps/console/src/__generated__/iam/PersonFormFragment.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<<1deba62d68487df7458534fef4bed9ff>> * @lightSyntaxTransform * @nogrep */ @@ -11,7 +11,7 @@ import { ReaderFragment } from 'relay-runtime'; export type ProfileKind = "CONTRACTOR" | "EMPLOYEE" | "SERVICE_ACCOUNT"; import { FragmentRefs } from "relay-runtime"; -export type UserFormFragment$data = { +export type PersonFormFragment$data = { readonly additionalEmailAddresses: ReadonlyArray; readonly canUpdate: boolean; readonly contractEndDate: string | null | undefined; @@ -20,18 +20,18 @@ export type UserFormFragment$data = { readonly id: string; readonly kind: ProfileKind; readonly position: string | null | undefined; - readonly " $fragmentType": "UserFormFragment"; + readonly " $fragmentType": "PersonFormFragment"; }; -export type UserFormFragment$key = { - readonly " $data"?: UserFormFragment$data; - readonly " $fragmentSpreads": FragmentRefs<"UserFormFragment">; +export type PersonFormFragment$key = { + readonly " $data"?: PersonFormFragment$data; + readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">; }; const node: ReaderFragment = { "argumentDefinitions": [], "kind": "Fragment", "metadata": null, - "name": "UserFormFragment", + "name": "PersonFormFragment", "selections": [ { "alias": null, @@ -100,6 +100,6 @@ const node: ReaderFragment = { "abstractKey": null }; -(node as any).hash = "b7c1eb9f3703338d5d0fb4c5411bf2eb"; +(node as any).hash = "ba9398e81e237b983ce37f05436439ee"; export default node; diff --git a/apps/console/src/__generated__/iam/UserFormMutation.graphql.ts b/apps/console/src/__generated__/iam/PersonFormMutation.graphql.ts similarity index 76% rename from apps/console/src/__generated__/iam/UserFormMutation.graphql.ts rename to apps/console/src/__generated__/iam/PersonFormMutation.graphql.ts index c076dd2a9..6721164e7 100644 --- a/apps/console/src/__generated__/iam/UserFormMutation.graphql.ts +++ b/apps/console/src/__generated__/iam/PersonFormMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<7a6d038560759279a60a293bc4be5bb8>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -19,19 +19,19 @@ export type UpdateProfileInput = { kind: ProfileKind; position?: string | null | undefined; }; -export type UserFormMutation$variables = { +export type PersonFormMutation$variables = { input: UpdateProfileInput; }; -export type UserFormMutation$data = { +export type PersonFormMutation$data = { readonly updateProfile: { readonly profile: { readonly id: string; }; }; }; -export type UserFormMutation = { - response: UserFormMutation$data; - variables: UserFormMutation$variables; +export type PersonFormMutation = { + response: PersonFormMutation$data; + variables: PersonFormMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -84,7 +84,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "UserFormMutation", + "name": "PersonFormMutation", "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null @@ -93,20 +93,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "UserFormMutation", + "name": "PersonFormMutation", "selections": (v1/*: any*/) }, "params": { - "cacheID": "edb3d0ac8f6a87857c470114ff6ad7f4", + "cacheID": "fe068a78bed066f0d50cab1dde6db292", "id": null, "metadata": {}, - "name": "UserFormMutation", + "name": "PersonFormMutation", "operationKind": "mutation", - "text": "mutation UserFormMutation(\n $input: UpdateProfileInput!\n) {\n updateProfile(input: $input) {\n profile {\n id\n }\n }\n}\n" + "text": "mutation PersonFormMutation(\n $input: UpdateProfileInput!\n) {\n updateProfile(input: $input) {\n profile {\n id\n }\n }\n}\n" } }; })(); -(node as any).hash = "6ae1b8fd927611dd3b3b530ccd1de7d1"; +(node as any).hash = "eb43fe5003e9761924aad474bf8296ee"; export default node; diff --git a/apps/console/src/__generated__/iam/UserPageQuery.graphql.ts b/apps/console/src/__generated__/iam/PersonPageQuery.graphql.ts similarity index 81% rename from apps/console/src/__generated__/iam/UserPageQuery.graphql.ts rename to apps/console/src/__generated__/iam/PersonPageQuery.graphql.ts index aad37e8c1..a4d3c37c4 100644 --- a/apps/console/src/__generated__/iam/UserPageQuery.graphql.ts +++ b/apps/console/src/__generated__/iam/PersonPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<10ec7c14d725880998dcd5ba2b99004e>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -10,11 +10,11 @@ import { ConcreteRequest } from 'relay-runtime'; import { FragmentRefs } from "relay-runtime"; -export type UserPageQuery$variables = { - userId: string; +export type PersonPageQuery$variables = { + personId: string; }; -export type UserPageQuery$data = { - readonly user: { +export type PersonPageQuery$data = { + readonly person: { readonly __typename: "MembershipProfile"; readonly canDelete: boolean; readonly fullName: string; @@ -22,16 +22,16 @@ export type UserPageQuery$data = { readonly email: string; }; readonly membershipId: string; - readonly " $fragmentSpreads": FragmentRefs<"UserFormFragment">; + readonly " $fragmentSpreads": FragmentRefs<"PersonFormFragment">; } | { // This will never be '%other', but we need some // value in case none of the concrete values match. readonly __typename: "%other"; }; }; -export type UserPageQuery = { - response: UserPageQuery$data; - variables: UserPageQuery$variables; +export type PersonPageQuery = { + response: PersonPageQuery$data; + variables: PersonPageQuery$variables; }; const node: ConcreteRequest = (function(){ @@ -39,14 +39,14 @@ var v0 = [ { "defaultValue": null, "kind": "LocalArgument", - "name": "userId" + "name": "personId" } ], v1 = [ { "kind": "Variable", "name": "id", - "variableName": "userId" + "variableName": "personId" } ], v2 = { @@ -102,12 +102,12 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "UserPageQuery", + "name": "PersonPageQuery", "selections": [ { "kind": "RequiredField", "field": { - "alias": "user", + "alias": "person", "args": (v1/*: any*/), "concreteType": null, "kind": "LinkedField", @@ -140,7 +140,7 @@ return { { "args": null, "kind": "FragmentSpread", - "name": "UserFormFragment" + "name": "PersonFormFragment" } ], "type": "MembershipProfile", @@ -159,10 +159,10 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "UserPageQuery", + "name": "PersonPageQuery", "selections": [ { - "alias": "user", + "alias": "person", "args": (v1/*: any*/), "concreteType": null, "kind": "LinkedField", @@ -248,16 +248,16 @@ return { ] }, "params": { - "cacheID": "9d437c761ed1795dde80f30a63e400fc", + "cacheID": "f754630a84c19798f2b0611dba4c6603", "id": null, "metadata": {}, - "name": "UserPageQuery", + "name": "PersonPageQuery", "operationKind": "query", - "text": "query UserPageQuery(\n $userId: ID!\n) {\n user: node(id: $userId) {\n __typename\n ... on MembershipProfile {\n fullName\n membershipId\n identity {\n email\n id\n }\n canDelete: permission(action: \"iam:membership-profile:delete\")\n ...UserFormFragment\n }\n id\n }\n}\n\nfragment UserFormFragment on MembershipProfile {\n id\n fullName\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n" + "text": "query PersonPageQuery(\n $personId: ID!\n) {\n person: node(id: $personId) {\n __typename\n ... on MembershipProfile {\n fullName\n membershipId\n identity {\n email\n id\n }\n canDelete: permission(action: \"iam:membership-profile:delete\")\n ...PersonFormFragment\n }\n id\n }\n}\n\nfragment PersonFormFragment on MembershipProfile {\n id\n fullName\n kind\n position\n additionalEmailAddresses\n contractStartDate\n contractEndDate\n canUpdate: permission(action: \"iam:membership-profile:update\")\n}\n" } }; })(); -(node as any).hash = "7711b4e7a7463f0134a47017f3e65aca"; +(node as any).hash = "d701ae3a51eb1fed62aec260f42b3c41"; export default node; diff --git a/apps/console/src/__generated__/iam/UserPage_removeMutation.graphql.ts b/apps/console/src/__generated__/iam/PersonPage_removeMutation.graphql.ts similarity index 68% rename from apps/console/src/__generated__/iam/UserPage_removeMutation.graphql.ts rename to apps/console/src/__generated__/iam/PersonPage_removeMutation.graphql.ts index ad29a2169..f3cf6401c 100644 --- a/apps/console/src/__generated__/iam/UserPage_removeMutation.graphql.ts +++ b/apps/console/src/__generated__/iam/PersonPage_removeMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<0cb21a9122ca2763aaa5dbe4d33e2b4a>> + * @generated SignedSource<<0c55769185354921be98596538492fab>> * @lightSyntaxTransform * @nogrep */ @@ -13,17 +13,17 @@ export type RemoveMemberInput = { membershipId: string; organizationId: string; }; -export type UserPage_removeMutation$variables = { +export type PersonPage_removeMutation$variables = { input: RemoveMemberInput; }; -export type UserPage_removeMutation$data = { +export type PersonPage_removeMutation$data = { readonly removeMember: { readonly deletedMembershipId: string; } | null | undefined; }; -export type UserPage_removeMutation = { - response: UserPage_removeMutation$data; - variables: UserPage_removeMutation$variables; +export type PersonPage_removeMutation = { + response: PersonPage_removeMutation$data; + variables: PersonPage_removeMutation$variables; }; const node: ConcreteRequest = (function(){ @@ -65,7 +65,7 @@ return { "argumentDefinitions": (v0/*: any*/), "kind": "Fragment", "metadata": null, - "name": "UserPage_removeMutation", + "name": "PersonPage_removeMutation", "selections": (v1/*: any*/), "type": "Mutation", "abstractKey": null @@ -74,20 +74,20 @@ return { "operation": { "argumentDefinitions": (v0/*: any*/), "kind": "Operation", - "name": "UserPage_removeMutation", + "name": "PersonPage_removeMutation", "selections": (v1/*: any*/) }, "params": { - "cacheID": "7aa8db55205ca16403a5919a5151d69f", + "cacheID": "0c220926bf64fda6954b3ab8e23dcd72", "id": null, "metadata": {}, - "name": "UserPage_removeMutation", + "name": "PersonPage_removeMutation", "operationKind": "mutation", - "text": "mutation UserPage_removeMutation(\n $input: RemoveMemberInput!\n) {\n removeMember(input: $input) {\n deletedMembershipId\n }\n}\n" + "text": "mutation PersonPage_removeMutation(\n $input: RemoveMemberInput!\n) {\n removeMember(input: $input) {\n deletedMembershipId\n }\n}\n" } }; })(); -(node as any).hash = "3c9804054834be923048845b796a2dfd"; +(node as any).hash = "60608384ce1ea37ec7b347ec4fdb4a61"; export default node; diff --git a/apps/console/src/components/tasks/TasksCard.tsx b/apps/console/src/components/tasks/TasksCard.tsx index 2aba73b76..b6296cdaa 100644 --- a/apps/console/src/components/tasks/TasksCard.tsx +++ b/apps/console/src/components/tasks/TasksCard.tsx @@ -262,7 +262,7 @@ function TaskRow(props: TaskRowProps) {
{task.assignedTo.fullName} diff --git a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx index 1513ac8ae..a5f280c17 100644 --- a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx +++ b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx @@ -110,7 +110,7 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) { )} {organization.canListVendors && ( diff --git a/apps/console/src/pages/iam/organizations/people/PeoplePage.tsx b/apps/console/src/pages/iam/organizations/people/PeoplePage.tsx new file mode 100644 index 000000000..9a166329a --- /dev/null +++ b/apps/console/src/pages/iam/organizations/people/PeoplePage.tsx @@ -0,0 +1,44 @@ +import { useTranslate } from "@probo/i18n"; +import { PageHeader } from "@probo/ui"; +import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { PeoplePageQuery } from "#/__generated__/iam/PeoplePageQuery.graphql"; + +import { PeopleList } from "./_components/PeopleList"; + +export const peoplePageQuery = graphql` + query PeoplePageQuery($organizationId: ID!) { + organization: node(id: $organizationId) @required(action: THROW) { + __typename + ...PeopleListFragment + @arguments(first: 20, order: { direction: ASC, field: FULL_NAME }) + } + } +`; + +export function PeoplePage(props: { + queryRef: PreloadedQuery; +}) { + const { queryRef } = props; + + const { __ } = useTranslate(); + + const { organization } = usePreloadedQuery( + peoplePageQuery, + queryRef, + ); + if (organization.__typename !== "Organization") { + throw new Error("node is of invalid type"); + } + + return ( +
+ + +
+ +
+
+ ); +} diff --git a/apps/console/src/pages/iam/organizations/people/PeoplePageLoader.tsx b/apps/console/src/pages/iam/organizations/people/PeoplePageLoader.tsx new file mode 100644 index 000000000..e8d0849e7 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/people/PeoplePageLoader.tsx @@ -0,0 +1,34 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; + +import type { PeoplePageQuery } from "#/__generated__/iam/PeoplePageQuery.graphql"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; +import { IAMRelayProvider } from "#/providers/IAMRelayProvider"; + +import { PeoplePage, peoplePageQuery } from "./PeoplePage"; + +function PeoplePageQueryLoader() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] + = useQueryLoader(peoplePageQuery); + + useEffect(() => { + loadQuery({ + organizationId, + }); + }, [loadQuery, organizationId]); + + if (!queryRef) { + return null; + } + + return ; +} + +export default function PeoplePageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/iam/organizations/users/UserPage.tsx b/apps/console/src/pages/iam/organizations/people/PersonPage.tsx similarity index 68% rename from apps/console/src/pages/iam/organizations/users/UserPage.tsx rename to apps/console/src/pages/iam/organizations/people/PersonPage.tsx index 3accdfeea..4356fa18c 100644 --- a/apps/console/src/pages/iam/organizations/users/UserPage.tsx +++ b/apps/console/src/pages/iam/organizations/people/PersonPage.tsx @@ -5,15 +5,15 @@ import { type PreloadedQuery, usePreloadedQuery } from "react-relay"; import { useNavigate } from "react-router"; import { graphql } from "relay-runtime"; -import type { UserPageQuery } from "#/__generated__/iam/UserPageQuery.graphql"; +import type { PersonPageQuery } from "#/__generated__/iam/PersonPageQuery.graphql"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; import { useOrganizationId } from "#/hooks/useOrganizationId"; -import { UserForm } from "./_components/UserForm"; +import { PersonForm } from "./_components/PersonForm"; -export const userPageQuery = graphql` - query UserPageQuery($userId: ID!) { - user: node(id: $userId) @required(action: THROW) { +export const personPageQuery = graphql` + query PersonPageQuery($personId: ID!) { + person: node(id: $personId) @required(action: THROW) { __typename ... on MembershipProfile { fullName @@ -22,14 +22,14 @@ export const userPageQuery = graphql` email } canDelete: permission(action: "iam:membership-profile:delete") - ...UserFormFragment + ...PersonFormFragment } } } `; const removeMemberMutation = graphql` - mutation UserPage_removeMutation( + mutation PersonPage_removeMutation( $input: RemoveMemberInput! ) { removeMember(input: $input) { @@ -38,7 +38,7 @@ const removeMemberMutation = graphql` } `; -export function UserPage(props: { queryRef: PreloadedQuery }) { +export function PersonPage(props: { queryRef: PreloadedQuery }) { const { queryRef } = props; const organizationId = useOrganizationId(); @@ -46,8 +46,8 @@ export function UserPage(props: { queryRef: PreloadedQuery }) { const confirm = useConfirm(); const navigate = useNavigate(); - const { user } = usePreloadedQuery(userPageQuery, queryRef); - if (user.__typename !== "MembershipProfile") { + const { person } = usePreloadedQuery(personPageQuery, queryRef); + if (person.__typename !== "MembershipProfile") { throw new Error("invalid type for node"); } @@ -65,19 +65,19 @@ export function UserPage(props: { queryRef: PreloadedQuery }) { return removeMembership({ variables: { input: { - membershipId: user.membershipId, + membershipId: person.membershipId, organizationId: organizationId, }, }, onCompleted: () => { - void navigate(`/organizations/${organizationId}/users`); + void navigate(`/organizations/${organizationId}/people`); }, }); }, { message: sprintf( __("Are you sure you want to remove %s?"), - user.fullName, + person.fullName, ), }, ); @@ -89,23 +89,23 @@ export function UserPage(props: { queryRef: PreloadedQuery }) { items={[ { label: __("People"), - to: `/organizations/${organizationId}/users`, + to: `/organizations/${organizationId}/people`, }, { - label: user.fullName, + label: person.fullName, }, ]} />
- +
-
{user.fullName}
-
{user.identity.email}
+
{person.fullName}
+
{person.identity.email}
- {user.canDelete && ( + {person.canDelete && ( }) { )}
- +
); }; diff --git a/apps/console/src/pages/iam/organizations/people/PersonPageLoader.tsx b/apps/console/src/pages/iam/organizations/people/PersonPageLoader.tsx new file mode 100644 index 000000000..bff8eef21 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/people/PersonPageLoader.tsx @@ -0,0 +1,35 @@ +import { useEffect } from "react"; +import { useQueryLoader } from "react-relay"; +import { useParams } from "react-router"; + +import type { PersonPageQuery } from "#/__generated__/iam/PersonPageQuery.graphql"; +import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; +import { IAMRelayProvider } from "#/providers/IAMRelayProvider"; + +import { PersonPage, personPageQuery } from "./PersonPage"; + +function PersonPageQueryLoader() { + const { personId } = useParams(); + if (!personId) { + throw new Error(":personId missing in route params"); + } + const [queryRef, loadQuery] = useQueryLoader(personPageQuery); + + useEffect(() => { + if (!queryRef) { + loadQuery({ personId }); + } + }); + + if (!queryRef) return ; + + return ; +} + +export default function PersonPageLoader() { + return ( + + + + ); +} diff --git a/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx b/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx new file mode 100644 index 000000000..db34291b7 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/people/_components/PeopleList.tsx @@ -0,0 +1,101 @@ +import { useTranslate } from "@probo/i18n"; +import { Tbody, Td, Th, Thead, Tr } from "@probo/ui"; +import type { ComponentProps } from "react"; +import { graphql, usePaginationFragment } from "react-relay"; + +import type { PeopleListFragment$key } from "#/__generated__/iam/PeopleListFragment.graphql"; +import type { PeopleListFragment_RefetchQuery } from "#/__generated__/iam/PeopleListFragment_RefetchQuery.graphql"; +import { SortableTable, SortableTh } from "#/components/SortableTable"; + +import { MemberListItem } from "./PeopleListItem"; + +const fragment = graphql` + fragment PeopleListFragment on Organization + @refetchable(queryName: "PeopleListFragment_RefetchQuery") + @argumentDefinitions( + first: { type: "Int", defaultValue: 20 } + order: { + type: "MembershipOrder" + defaultValue: { direction: ASC, field: FULL_NAME } + } + after: { type: "CursorKey", defaultValue: null } + before: { type: "CursorKey", defaultValue: null } + last: { type: "Int", defaultValue: null } + ) { + members( + first: $first + after: $after + last: $last + before: $before + orderBy: $order + ) @connection(key: "PeopleListFragment_members") @required(action: THROW) { + __id + totalCount + edges @required(action: THROW) { + node { + id + ...PeopleListItemFragment + } + } + } + } +`; + +export function PeopleList(props: { fKey: PeopleListFragment$key }) { + const { fKey } = props; + + const { __ } = useTranslate(); + + const peoplePagination = usePaginationFragment< + PeopleListFragment_RefetchQuery, + PeopleListFragment$key + >(fragment, fKey); + + const refetchMemberships = () => { + peoplePagination.refetch({}, { fetchPolicy: "network-only" }); + }; + + return ( + ["refetch"] + } + pageSize={20} + > + + + {__("Name")} + {__("Email")} + {__("Type")} + {__("Role")} + {__("Joined")} + {__("Position")} + + + + + {peoplePagination.data.members.totalCount === 0 + ? ( + + + {__("No members")} + + + ) + : ( + peoplePagination.data.members.edges.map(({ node: membership }) => ( + + )) + )} + + + ); +} diff --git a/apps/console/src/pages/iam/organizations/people/_components/PeopleListItem.tsx b/apps/console/src/pages/iam/organizations/people/_components/PeopleListItem.tsx new file mode 100644 index 000000000..2cde9a4cd --- /dev/null +++ b/apps/console/src/pages/iam/organizations/people/_components/PeopleListItem.tsx @@ -0,0 +1,172 @@ +import { sprintf } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { + Badge, + Button, + IconPencil, + IconTrashCan, + Spinner, + Td, + Tr, + useConfirm, +} from "@probo/ui"; +import { clsx } from "clsx"; +import { useState } from "react"; +import { useFragment } from "react-relay"; +import { graphql } from "relay-runtime"; + +import type { PeopleListItemFragment$key } from "#/__generated__/iam/PeopleListItemFragment.graphql"; +import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import { useOrganizationId } from "#/hooks/useOrganizationId"; + +import { EditMemberDialog } from "../../settings/_components/EditMemberDialog"; + +const fragment = graphql` + fragment PeopleListItemFragment on Membership { + id + role + source + state + profile @required(action: THROW) { + id + fullName + kind + position + } + identity @required(action: THROW) { + email + } + createdAt + canUpdate: permission(action: "iam:membership:update") + canDelete: permission(action: "iam:membership:delete") + } +`; + +const removeMemberMutation = graphql` + mutation PeopleListItem_removeMutation( + $input: RemoveMemberInput! + $connections: [ID!]! + ) { + removeMember(input: $input) { + deletedMembershipId @deleteEdge(connections: $connections) + } + } +`; + +export function MemberListItem(props: { + connectionId: string; + fKey: PeopleListItemFragment$key; + onRefetch: () => void; +}) { + const { fKey, connectionId } = props; + + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + const confirm = useConfirm(); + const [dialogOpen, setDialogOpen] = useState(false); + + const membership = useFragment(fragment, fKey); + + const isInactive = membership.state === "INACTIVE"; + + const [removeMembership, isRemoving] = useMutationWithToasts( + removeMemberMutation, + { + successMessage: __("Member removed successfully"), + errorMessage: __("Failed to remove member"), + }, + ); + + const handleRemove = () => { + confirm( + () => { + return removeMembership({ + variables: { + input: { + membershipId: membership.id, + organizationId: organizationId, + }, + connections: [connectionId], + }, + }); + }, + { + message: sprintf( + __("Are you sure you want to remove %s?"), + membership.profile.fullName, + ), + }, + ); + }; + + return ( + <> + + +
+ {membership.profile.fullName} + {isInactive && {__("Inactive")}} +
+ + +
+ {membership.identity.email} + {membership.source} +
+ + {membership.profile.kind} + + {membership.role} + + {new Date(membership.createdAt).toLocaleDateString()} + {membership.profile.position} + + {!isInactive && ( +
e.stopPropagation()} + > + {membership.canUpdate && ( +
+ )} + + + + {dialogOpen && ( + setDialogOpen(false)} + membership={membership} + /> + )} + + ); +} diff --git a/apps/console/src/pages/iam/organizations/users/_components/UserForm.tsx b/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx similarity index 74% rename from apps/console/src/pages/iam/organizations/users/_components/UserForm.tsx rename to apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx index 8e08fb66d..83571d628 100644 --- a/apps/console/src/pages/iam/organizations/users/_components/UserForm.tsx +++ b/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx @@ -5,15 +5,15 @@ import { useFragment } from "react-relay"; import { graphql } from "relay-runtime"; import { z } from "zod"; -import type { UserFormFragment$key } from "#/__generated__/iam/UserFormFragment.graphql"; -import type { UserFormMutation } from "#/__generated__/iam/UserFormMutation.graphql"; +import type { PersonFormFragment$key } from "#/__generated__/iam/PersonFormFragment.graphql"; +import type { PersonFormMutation } from "#/__generated__/iam/PersonFormMutation.graphql"; import { ControlledField } from "#/components/form/ControlledField"; import { EmailsField } from "#/components/form/EmailsField"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; const fragment = graphql` - fragment UserFormFragment on MembershipProfile { + fragment PersonFormFragment on MembershipProfile { id fullName kind @@ -25,8 +25,8 @@ const fragment = graphql` } `; -const updateUserMutation = graphql` - mutation UserFormMutation($input: UpdateProfileInput!) { +const updatePersonMutation = graphql` + mutation PersonFormMutation($input: UpdateProfileInput!) { updateProfile(input: $input) { profile { id @@ -48,26 +48,26 @@ const schema = z.object({ contractEndDate: z.string().optional().nullable(), }); -export function UserForm(props: { fragmentRef: UserFormFragment$key }) { +export function PersonForm(props: { fragmentRef: PersonFormFragment$key }) { const { fragmentRef } = props; const { __ } = useTranslate(); - const user = useFragment(fragment, fragmentRef); + const person = useFragment(fragment, fragmentRef); const { control, formState, handleSubmit, register, reset } = useFormWithSchema(schema, { defaultValues: { - kind: user.kind, - fullName: user.fullName, - position: user.position, - additionalEmailAddresses: [...user.additionalEmailAddresses], - contractStartDate: user.contractStartDate?.split("T")[0] || "", - contractEndDate: user.contractEndDate?.split("T")[0] || "", + kind: person.kind, + fullName: person.fullName, + position: person.position, + additionalEmailAddresses: [...person.additionalEmailAddresses], + contractStartDate: person.contractStartDate?.split("T")[0] || "", + contractEndDate: person.contractEndDate?.split("T")[0] || "", }, }); - const [mutate, isMutating] = useMutationWithToasts( - updateUserMutation, + const [mutate, isMutating] = useMutationWithToasts( + updatePersonMutation, { successMessage: __("Member updated successfully."), errorMessage: __("Failed to update member"), @@ -75,7 +75,7 @@ export function UserForm(props: { fragmentRef: UserFormFragment$key }) { ); const onSubmit = handleSubmit(async (data: z.infer) => { const input = { - id: user.id, + id: person.id, fullName: data.fullName, additionalEmailAddresses: data.additionalEmailAddresses, kind: data.kind, @@ -101,7 +101,7 @@ export function UserForm(props: { fragmentRef: UserFormFragment$key }) { name="kind" type="select" label={__("Type")} - disabled={!user.canUpdate} + disabled={!person.canUpdate} > {getRoles(__).map(role => (