@thunderid/react 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (188) hide show
  1. package/dist/cjs/components/account/UserProfile/BaseUserProfile.cjs +1 -1
  2. package/dist/cjs/components/admin/AgentDetails/AgentDetails.cjs +13 -2
  3. package/dist/cjs/components/admin/AgentDetails/AgentDetails.cjs.map +1 -1
  4. package/dist/cjs/components/admin/AgentDetails/BaseAgentDetails.cjs +25 -7
  5. package/dist/cjs/components/admin/AgentDetails/BaseAgentDetails.cjs.map +1 -1
  6. package/dist/cjs/components/admin/ApplicationDetails/ApplicationDetails.cjs +15 -2
  7. package/dist/cjs/components/admin/ApplicationDetails/ApplicationDetails.cjs.map +1 -1
  8. package/dist/cjs/components/admin/ApplicationDetails/BaseApplicationDetails.cjs +24 -7
  9. package/dist/cjs/components/admin/ApplicationDetails/BaseApplicationDetails.cjs.map +1 -1
  10. package/dist/cjs/components/admin/GroupMembers/GroupMembers.cjs +2 -1
  11. package/dist/cjs/components/admin/GroupMembers/GroupMembers.cjs.map +1 -1
  12. package/dist/cjs/components/admin/RoleAssignments/RoleAssignments.cjs +2 -1
  13. package/dist/cjs/components/admin/RoleAssignments/RoleAssignments.cjs.map +1 -1
  14. package/dist/cjs/components/admin/UserDetails/BaseUserDetails.cjs +32 -14
  15. package/dist/cjs/components/admin/UserDetails/BaseUserDetails.cjs.map +1 -1
  16. package/dist/cjs/components/admin/UserDetails/UserDetails.cjs +14 -2
  17. package/dist/cjs/components/admin/UserDetails/UserDetails.cjs.map +1 -1
  18. package/dist/cjs/components/admin/shared/AccountLifecycleParts.cjs +105 -0
  19. package/dist/cjs/components/admin/shared/AccountLifecycleParts.cjs.map +1 -0
  20. package/dist/cjs/components/admin/shared/ApplicationFormFields.cjs +1 -1
  21. package/dist/cjs/components/auth/AcceptInvite/BaseAcceptInvite.cjs +4 -5
  22. package/dist/cjs/components/auth/AcceptInvite/BaseAcceptInvite.cjs.map +1 -1
  23. package/dist/cjs/components/auth/SignIn/SignIn.cjs +5 -5
  24. package/dist/cjs/components/auth/SignIn/SignIn.cjs.map +1 -1
  25. package/dist/cjs/components/auth/flow/AuthOptionFactory.cjs +4 -5
  26. package/dist/cjs/components/auth/flow/AuthOptionFactory.cjs.map +1 -1
  27. package/dist/cjs/components/auth/flow/Consent.cjs +53 -28
  28. package/dist/cjs/components/auth/flow/Consent.cjs.map +1 -1
  29. package/dist/cjs/components/auth/flow/ConsentAuthorizationDetail.cjs +104 -0
  30. package/dist/cjs/components/auth/flow/ConsentAuthorizationDetail.cjs.map +1 -0
  31. package/dist/cjs/components/auth/flow/ConsentAuthorizationDetail.styles.cjs +59 -0
  32. package/dist/cjs/components/auth/flow/ConsentAuthorizationDetail.styles.cjs.map +1 -0
  33. package/dist/cjs/components/auth/flow/ConsentDetailField.cjs +171 -0
  34. package/dist/cjs/components/auth/flow/ConsentDetailField.cjs.map +1 -0
  35. package/dist/cjs/components/auth/flow/ConsentDetailField.styles.cjs +85 -0
  36. package/dist/cjs/components/auth/flow/ConsentDetailField.styles.cjs.map +1 -0
  37. package/dist/cjs/components/auth/flow/ConsentDetailText.cjs +63 -0
  38. package/dist/cjs/components/auth/flow/ConsentDetailText.cjs.map +1 -0
  39. package/dist/cjs/components/auth/flow/ConsentDetailText.styles.cjs +83 -0
  40. package/dist/cjs/components/auth/flow/ConsentDetailText.styles.cjs.map +1 -0
  41. package/dist/cjs/components/auth/flow/FieldFactory.cjs +1 -1
  42. package/dist/cjs/components/auth/flow/resolveConsentText.cjs +25 -0
  43. package/dist/cjs/components/auth/flow/resolveConsentText.cjs.map +1 -0
  44. package/dist/cjs/components/primitives/KeyValueInput/KeyValueInput.cjs +1 -1
  45. package/dist/cjs/components/primitives/MultiInput/MultiInput.cjs +1 -1
  46. package/dist/cjs/components/primitives/PasswordField/PasswordField.cjs +1 -1
  47. package/dist/cjs/hooks/auth/useConsentDetailField.cjs +357 -0
  48. package/dist/cjs/hooks/auth/useConsentDetailField.cjs.map +1 -0
  49. package/dist/cjs/hooks/management/useAccountLifecycle.cjs +59 -0
  50. package/dist/cjs/hooks/management/useAccountLifecycle.cjs.map +1 -0
  51. package/dist/cjs/index.cjs +66 -1
  52. package/dist/cjs/utils/formatConsentDetailValue.cjs +26 -0
  53. package/dist/cjs/utils/formatConsentDetailValue.cjs.map +1 -0
  54. package/dist/cjs/utils/getConsentDetailLayout.cjs +44 -0
  55. package/dist/cjs/utils/getConsentDetailLayout.cjs.map +1 -0
  56. package/dist/cjs/utils/getConsentDetailListDisplay.cjs +30 -0
  57. package/dist/cjs/utils/getConsentDetailListDisplay.cjs.map +1 -0
  58. package/dist/cjs/utils/isConsentApprovalBlocked.cjs +41 -0
  59. package/dist/cjs/utils/isConsentApprovalBlocked.cjs.map +1 -0
  60. package/dist/cjs/utils/isConsentDetailPlainList.cjs +24 -0
  61. package/dist/cjs/utils/isConsentDetailPlainList.cjs.map +1 -0
  62. package/dist/cjs/utils/isConsentDetailStructured.cjs +15 -0
  63. package/dist/cjs/utils/isConsentDetailStructured.cjs.map +1 -0
  64. package/dist/cjs/utils/isConsentDetailTextClamped.cjs +25 -0
  65. package/dist/cjs/utils/isConsentDetailTextClamped.cjs.map +1 -0
  66. package/dist/components/account/UserProfile/BaseUserProfile.js +1 -1
  67. package/dist/components/admin/AgentDetails/AgentDetails.d.ts +8 -1
  68. package/dist/components/admin/AgentDetails/AgentDetails.d.ts.map +1 -1
  69. package/dist/components/admin/AgentDetails/AgentDetails.js +13 -2
  70. package/dist/components/admin/AgentDetails/AgentDetails.js.map +1 -1
  71. package/dist/components/admin/AgentDetails/BaseAgentDetails.d.ts +21 -1
  72. package/dist/components/admin/AgentDetails/BaseAgentDetails.d.ts.map +1 -1
  73. package/dist/components/admin/AgentDetails/BaseAgentDetails.js +25 -7
  74. package/dist/components/admin/AgentDetails/BaseAgentDetails.js.map +1 -1
  75. package/dist/components/admin/ApplicationDetails/ApplicationDetails.d.ts +15 -1
  76. package/dist/components/admin/ApplicationDetails/ApplicationDetails.d.ts.map +1 -1
  77. package/dist/components/admin/ApplicationDetails/ApplicationDetails.js +15 -2
  78. package/dist/components/admin/ApplicationDetails/ApplicationDetails.js.map +1 -1
  79. package/dist/components/admin/ApplicationDetails/BaseApplicationDetails.d.ts +20 -1
  80. package/dist/components/admin/ApplicationDetails/BaseApplicationDetails.d.ts.map +1 -1
  81. package/dist/components/admin/ApplicationDetails/BaseApplicationDetails.js +24 -7
  82. package/dist/components/admin/ApplicationDetails/BaseApplicationDetails.js.map +1 -1
  83. package/dist/components/admin/GroupMembers/GroupMembers.d.ts +7 -0
  84. package/dist/components/admin/GroupMembers/GroupMembers.d.ts.map +1 -1
  85. package/dist/components/admin/GroupMembers/GroupMembers.js +2 -1
  86. package/dist/components/admin/GroupMembers/GroupMembers.js.map +1 -1
  87. package/dist/components/admin/RoleAssignments/RoleAssignments.d.ts +7 -0
  88. package/dist/components/admin/RoleAssignments/RoleAssignments.d.ts.map +1 -1
  89. package/dist/components/admin/RoleAssignments/RoleAssignments.js +2 -1
  90. package/dist/components/admin/RoleAssignments/RoleAssignments.js.map +1 -1
  91. package/dist/components/admin/UserDetails/BaseUserDetails.d.ts +22 -1
  92. package/dist/components/admin/UserDetails/BaseUserDetails.d.ts.map +1 -1
  93. package/dist/components/admin/UserDetails/BaseUserDetails.js +32 -14
  94. package/dist/components/admin/UserDetails/BaseUserDetails.js.map +1 -1
  95. package/dist/components/admin/UserDetails/UserDetails.d.ts +15 -1
  96. package/dist/components/admin/UserDetails/UserDetails.d.ts.map +1 -1
  97. package/dist/components/admin/UserDetails/UserDetails.js +14 -2
  98. package/dist/components/admin/UserDetails/UserDetails.js.map +1 -1
  99. package/dist/components/admin/shared/AccountLifecycleParts.d.ts +32 -0
  100. package/dist/components/admin/shared/AccountLifecycleParts.d.ts.map +1 -0
  101. package/dist/components/admin/shared/AccountLifecycleParts.js +101 -0
  102. package/dist/components/admin/shared/AccountLifecycleParts.js.map +1 -0
  103. package/dist/components/admin/shared/ApplicationFormFields.js +1 -1
  104. package/dist/components/auth/AcceptInvite/BaseAcceptInvite.d.ts.map +1 -1
  105. package/dist/components/auth/AcceptInvite/BaseAcceptInvite.js +5 -6
  106. package/dist/components/auth/AcceptInvite/BaseAcceptInvite.js.map +1 -1
  107. package/dist/components/auth/SignIn/SignIn.d.ts.map +1 -1
  108. package/dist/components/auth/SignIn/SignIn.js +6 -6
  109. package/dist/components/auth/SignIn/SignIn.js.map +1 -1
  110. package/dist/components/auth/flow/AuthOptionFactory.d.ts.map +1 -1
  111. package/dist/components/auth/flow/AuthOptionFactory.js +5 -6
  112. package/dist/components/auth/flow/AuthOptionFactory.js.map +1 -1
  113. package/dist/components/auth/flow/Consent.d.ts +6 -0
  114. package/dist/components/auth/flow/Consent.d.ts.map +1 -1
  115. package/dist/components/auth/flow/Consent.js +54 -29
  116. package/dist/components/auth/flow/Consent.js.map +1 -1
  117. package/dist/components/auth/flow/ConsentAuthorizationDetail.d.ts +68 -0
  118. package/dist/components/auth/flow/ConsentAuthorizationDetail.d.ts.map +1 -0
  119. package/dist/components/auth/flow/ConsentAuthorizationDetail.js +100 -0
  120. package/dist/components/auth/flow/ConsentAuthorizationDetail.js.map +1 -0
  121. package/dist/components/auth/flow/ConsentAuthorizationDetail.styles.d.ts +10 -0
  122. package/dist/components/auth/flow/ConsentAuthorizationDetail.styles.d.ts.map +1 -0
  123. package/dist/components/auth/flow/ConsentAuthorizationDetail.styles.js +57 -0
  124. package/dist/components/auth/flow/ConsentAuthorizationDetail.styles.js.map +1 -0
  125. package/dist/components/auth/flow/ConsentDetailField.d.ts +39 -0
  126. package/dist/components/auth/flow/ConsentDetailField.d.ts.map +1 -0
  127. package/dist/components/auth/flow/ConsentDetailField.js +168 -0
  128. package/dist/components/auth/flow/ConsentDetailField.js.map +1 -0
  129. package/dist/components/auth/flow/ConsentDetailField.styles.d.ts +11 -0
  130. package/dist/components/auth/flow/ConsentDetailField.styles.d.ts.map +1 -0
  131. package/dist/components/auth/flow/ConsentDetailField.styles.js +83 -0
  132. package/dist/components/auth/flow/ConsentDetailField.styles.js.map +1 -0
  133. package/dist/components/auth/flow/ConsentDetailText.d.ts +53 -0
  134. package/dist/components/auth/flow/ConsentDetailText.d.ts.map +1 -0
  135. package/dist/components/auth/flow/ConsentDetailText.js +58 -0
  136. package/dist/components/auth/flow/ConsentDetailText.js.map +1 -0
  137. package/dist/components/auth/flow/ConsentDetailText.styles.d.ts +20 -0
  138. package/dist/components/auth/flow/ConsentDetailText.styles.d.ts.map +1 -0
  139. package/dist/components/auth/flow/ConsentDetailText.styles.js +80 -0
  140. package/dist/components/auth/flow/ConsentDetailText.styles.js.map +1 -0
  141. package/dist/components/auth/flow/FieldFactory.js +1 -1
  142. package/dist/components/auth/flow/resolveConsentText.d.ts +13 -0
  143. package/dist/components/auth/flow/resolveConsentText.d.ts.map +1 -0
  144. package/dist/components/auth/flow/resolveConsentText.js +23 -0
  145. package/dist/components/auth/flow/resolveConsentText.js.map +1 -0
  146. package/dist/components/primitives/KeyValueInput/KeyValueInput.js +1 -1
  147. package/dist/components/primitives/MultiInput/MultiInput.js +1 -1
  148. package/dist/components/primitives/PasswordField/PasswordField.js +1 -1
  149. package/dist/hooks/auth/useConsentDetailField.d.ts +130 -0
  150. package/dist/hooks/auth/useConsentDetailField.d.ts.map +1 -0
  151. package/dist/hooks/auth/useConsentDetailField.js +354 -0
  152. package/dist/hooks/auth/useConsentDetailField.js.map +1 -0
  153. package/dist/hooks/management/useAccountLifecycle.d.ts +49 -0
  154. package/dist/hooks/management/useAccountLifecycle.d.ts.map +1 -0
  155. package/dist/hooks/management/useAccountLifecycle.js +56 -0
  156. package/dist/hooks/management/useAccountLifecycle.js.map +1 -0
  157. package/dist/index.d.ts +16 -4
  158. package/dist/index.d.ts.map +1 -1
  159. package/dist/index.js +11 -3
  160. package/dist/utils/formatConsentDetailValue.d.ts +16 -0
  161. package/dist/utils/formatConsentDetailValue.d.ts.map +1 -0
  162. package/dist/utils/formatConsentDetailValue.js +25 -0
  163. package/dist/utils/formatConsentDetailValue.js.map +1 -0
  164. package/dist/utils/getConsentDetailLayout.d.ts +32 -0
  165. package/dist/utils/getConsentDetailLayout.d.ts.map +1 -0
  166. package/dist/utils/getConsentDetailLayout.js +43 -0
  167. package/dist/utils/getConsentDetailLayout.js.map +1 -0
  168. package/dist/utils/getConsentDetailListDisplay.d.ts +30 -0
  169. package/dist/utils/getConsentDetailListDisplay.d.ts.map +1 -0
  170. package/dist/utils/getConsentDetailListDisplay.js +30 -0
  171. package/dist/utils/getConsentDetailListDisplay.js.map +1 -0
  172. package/dist/utils/isConsentApprovalBlocked.d.ts +17 -0
  173. package/dist/utils/isConsentApprovalBlocked.d.ts.map +1 -0
  174. package/dist/utils/isConsentApprovalBlocked.js +39 -0
  175. package/dist/utils/isConsentApprovalBlocked.js.map +1 -0
  176. package/dist/utils/isConsentDetailPlainList.d.ts +15 -0
  177. package/dist/utils/isConsentDetailPlainList.d.ts.map +1 -0
  178. package/dist/utils/isConsentDetailPlainList.js +23 -0
  179. package/dist/utils/isConsentDetailPlainList.js.map +1 -0
  180. package/dist/utils/isConsentDetailStructured.d.ts +10 -0
  181. package/dist/utils/isConsentDetailStructured.d.ts.map +1 -0
  182. package/dist/utils/isConsentDetailStructured.js +14 -0
  183. package/dist/utils/isConsentDetailStructured.js.map +1 -0
  184. package/dist/utils/isConsentDetailTextClamped.d.ts +20 -0
  185. package/dist/utils/isConsentDetailTextClamped.d.ts.map +1 -0
  186. package/dist/utils/isConsentDetailTextClamped.js +24 -0
  187. package/dist/utils/isConsentDetailTextClamped.js.map +1 -0
  188. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"GroupMembers.js","names":["GroupMembers: FC<GroupMembersProps>","useTranslation","groupsEndpoint: ManagementEndpoint","useManagementEndpoint","usersEndpoint: ManagementEndpoint","agentsEndpoint: ManagementEndpoint","members: ResourceQueryResult<GroupMemberListResponse>","useResourceQuery","users: ResourceQueryResult<ManagedUserListResponse>","agents: ResourceQueryResult<AgentListResponse>","add: ResourceMutationResult<Group, GroupMembersVariables>","useResourceMutation","remove: ResourceMutationResult<Group, GroupMembersVariables>","candidates: Partial<Record<'user' | 'agent', IdentityCandidate[]>>","BaseGroupMembers"],"sources":["../../../../src/components/admin/GroupMembers/GroupMembers.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AgentListResponse,\n AgentQueryKeys,\n BasicAgent,\n Group,\n GroupMember,\n GroupMemberListResponse,\n GroupQueryKeys,\n ManagedUser,\n ManagedUserListResponse,\n ResourceQueryKey,\n RoleQueryKeys,\n UserQueryKeys,\n addGroupMembers,\n getAgents,\n getGroupMembers,\n getUsers,\n removeGroupMembers,\n resolveManagedIdentityName,\n toManagementErrorMessage,\n} from '@thunderid/browser';\nimport {FC, ReactElement, useMemo} from 'react';\nimport BaseGroupMembers, {BaseGroupMembersProps} from './BaseGroupMembers';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\nimport {IdentityCandidate} from '../shared/IdentityPicker';\n\ninterface GroupMembersVariables {\n groupId: string;\n members: Pick<GroupMember, 'id' | 'type'>[];\n}\n\n/**\n * Query keys to refetch after a group's membership changes.\n */\nconst getMembershipInvalidatedKeys = ({groupId}: GroupMembersVariables): ResourceQueryKey[] => [\n [GroupQueryKeys.GROUP, groupId],\n [GroupQueryKeys.GROUP_MEMBERS, groupId],\n [GroupQueryKeys.MEMBERSHIPS],\n [RoleQueryKeys.ROLE_ASSIGNMENTS],\n];\n\nexport type GroupMembersProps = Omit<\n BaseGroupMembersProps,\n 'candidates' | 'error' | 'isLoading' | 'isMutating' | 'members' | 'onAdd' | 'onRemove'\n> & {\n /**\n * Whether members can be added and removed from this view.\n * @default false\n */\n editable?: boolean;\n groupId: string;\n};\n\n/**\n * Lists the users and agents in a group, and lets the signed-in user add and remove them.\n *\n * @example\n * ```tsx\n * <GroupMembers groupId={groupId} />\n * ```\n */\nconst GroupMembers: FC<GroupMembersProps> = ({\n editable = false,\n groupId,\n preferences,\n ...rest\n}: GroupMembersProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const groupsEndpoint: ManagementEndpoint = useManagementEndpoint('groups');\n const usersEndpoint: ManagementEndpoint = useManagementEndpoint('users');\n const agentsEndpoint: ManagementEndpoint = useManagementEndpoint('agents');\n const members: ResourceQueryResult<GroupMemberListResponse> = useResourceQuery(\n [GroupQueryKeys.GROUP_MEMBERS, groupId, {limit: 100}],\n (fetcher) => getGroupMembers({...groupsEndpoint, fetcher, groupId, limit: 100}),\n {enabled: Boolean(groupId)},\n );\n const users: ResourceQueryResult<ManagedUserListResponse> = useResourceQuery(\n [UserQueryKeys.USERS, {limit: 100}],\n (fetcher) => getUsers({...usersEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const agents: ResourceQueryResult<AgentListResponse> = useResourceQuery(\n [AgentQueryKeys.AGENTS, {limit: 100}],\n (fetcher) => getAgents({...agentsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const add: ResourceMutationResult<Group, GroupMembersVariables> = useResourceMutation(\n ({groupId: id, members: changes}: GroupMembersVariables, fetcher) =>\n addGroupMembers({...groupsEndpoint, fetcher, groupId: id, members: changes}),\n getMembershipInvalidatedKeys,\n );\n const remove: ResourceMutationResult<Group, GroupMembersVariables> = useResourceMutation(\n ({groupId: id, members: changes}: GroupMembersVariables, fetcher) =>\n removeGroupMembers({...groupsEndpoint, fetcher, groupId: id, members: changes}),\n getMembershipInvalidatedKeys,\n );\n\n const candidates: Partial<Record<'user' | 'agent', IdentityCandidate[]>> = useMemo(\n () => ({\n agent: (agents.data?.agents ?? []).map((agent: BasicAgent) => ({id: agent.id, label: agent.name})),\n user: (users.data?.users ?? []).map((user: ManagedUser) => ({\n id: user.id,\n label: resolveManagedIdentityName(user),\n })),\n }),\n [agents.data, users.data],\n );\n\n return (\n <BaseGroupMembers\n members={members.data?.members ?? []}\n isLoading={members.isLoading}\n isMutating={add.isLoading || remove.isLoading}\n error={\n toManagementErrorMessage(t, members.error, 'admin.groups.members.error') ??\n toManagementErrorMessage(t, add.error ?? remove.error, 'admin.groups.members.update_error')\n }\n candidates={editable ? candidates : undefined}\n onAdd={\n editable\n ? (member: Pick<GroupMember, 'id' | 'type'>): void => void add.mutate({groupId, members: [member]})\n : undefined\n }\n onRemove={\n editable\n ? (member: GroupMember): void => void remove.mutate({groupId, members: [{id: member.id, type: member.type}]})\n : undefined\n }\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default GroupMembers;\n"],"mappings":";;;;;;;;;;;;;AAwCA,MAAM,gCAAgC,EAAC,cAAwD;CAC7F,CAAC,eAAe,OAAO,QAAQ;CAC/B,CAAC,eAAe,eAAe,QAAQ;CACvC,CAAC,eAAe,YAAY;CAC5B,CAAC,cAAc,iBAAiB;CACjC;;;;;;;;;AAsBD,MAAMA,gBAAuC,EAC3C,WAAW,OACX,SACA,YACA,GAAG,WACkC;CACrC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAMC,iBAAqCC,8BAAsB,SAAS;CAC1E,MAAMC,gBAAoCD,8BAAsB,QAAQ;CACxE,MAAME,iBAAqCF,8BAAsB,SAAS;CAC1E,MAAMG,UAAwDC,yBAC5D;EAAC,eAAe;EAAe;EAAS,EAAC,OAAO,KAAI;EAAC,GACpD,YAAY,gBAAgB;EAAC,GAAG;EAAgB;EAAS;EAAS,OAAO;EAAI,CAAC,EAC/E,EAAC,SAAS,QAAQ,QAAQ,EAAC,CAC5B;CACD,MAAMC,QAAsDD,yBAC1D,CAAC,cAAc,OAAO,EAAC,OAAO,KAAI,CAAC,GAClC,YAAY,SAAS;EAAC,GAAG;EAAe;EAAS,OAAO;EAAI,CAAC,EAC9D,EAAC,SAAS,UAAS,CACpB;CACD,MAAME,SAAiDF,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMG,MAA4DC,6BAC/D,EAAC,SAAS,IAAI,SAAS,WAAiC,YACvD,gBAAgB;EAAC,GAAG;EAAgB;EAAS,SAAS;EAAI,SAAS;EAAQ,CAAC,EAC9E,6BACD;CACD,MAAMC,SAA+DD,6BAClE,EAAC,SAAS,IAAI,SAAS,WAAiC,YACvD,mBAAmB;EAAC,GAAG;EAAgB;EAAS,SAAS;EAAI,SAAS;EAAQ,CAAC,EACjF,6BACD;CAED,MAAME,aAAqE,eAClE;EACL,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAuB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAClG,OAAO,MAAM,MAAM,SAAS,EAAE,EAAE,KAAK,UAAuB;GAC1D,IAAI,KAAK;GACT,OAAO,2BAA2B,KAAK;GACxC,EAAE;EACJ,GACD,CAAC,OAAO,MAAM,MAAM,KAAK,CAC1B;AAED,QACE,oBAACC;EACC,SAAS,QAAQ,MAAM,WAAW,EAAE;EACpC,WAAW,QAAQ;EACnB,YAAY,IAAI,aAAa,OAAO;EACpC,OACE,yBAAyB,GAAG,QAAQ,OAAO,6BAA6B,IACxE,yBAAyB,GAAG,IAAI,SAAS,OAAO,OAAO,oCAAoC;EAE7F,YAAY,WAAW,aAAa;EACpC,OACE,YACK,WAAmD,KAAK,IAAI,OAAO;GAAC;GAAS,SAAS,CAAC,OAAO;GAAC,CAAC,GACjG;EAEN,UACE,YACK,WAA8B,KAAK,OAAO,OAAO;GAAC;GAAS,SAAS,CAAC;IAAC,IAAI,OAAO;IAAI,MAAM,OAAO;IAAK,CAAC;GAAC,CAAC,GAC3G;EAEO;EACb,GAAI;GACJ;;AAIN,2BAAe"}
1
+ {"version":3,"file":"GroupMembers.js","names":["GroupMembers: FC<GroupMembersProps>","useTranslation","groupsEndpoint: ManagementEndpoint","useManagementEndpoint","usersEndpoint: ManagementEndpoint","agentsEndpoint: ManagementEndpoint","members: ResourceQueryResult<GroupMemberListResponse>","useResourceQuery","users: ResourceQueryResult<ManagedUserListResponse>","agents: ResourceQueryResult<AgentListResponse>","add: ResourceMutationResult<Group, GroupMembersVariables>","useResourceMutation","remove: ResourceMutationResult<Group, GroupMembersVariables>","candidates: Partial<Record<'user' | 'agent', IdentityCandidate[]>>","BaseGroupMembers"],"sources":["../../../../src/components/admin/GroupMembers/GroupMembers.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AdministrationMode,\n AgentListResponse,\n AgentQueryKeys,\n BasicAgent,\n Group,\n GroupMember,\n GroupMemberListResponse,\n GroupQueryKeys,\n ManagedUser,\n ManagedUserListResponse,\n ResourceQueryKey,\n RoleQueryKeys,\n UserQueryKeys,\n addGroupMembers,\n getAgents,\n getGroupMembers,\n getUsers,\n removeGroupMembers,\n resolveManagedIdentityName,\n toManagementErrorMessage,\n} from '@thunderid/browser';\nimport {FC, ReactElement, useMemo} from 'react';\nimport BaseGroupMembers, {BaseGroupMembersProps} from './BaseGroupMembers';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\nimport {IdentityCandidate} from '../shared/IdentityPicker';\n\ninterface GroupMembersVariables {\n groupId: string;\n members: Pick<GroupMember, 'id' | 'type'>[];\n}\n\n/**\n * Query keys to refetch after a group's membership changes.\n */\nconst getMembershipInvalidatedKeys = ({groupId}: GroupMembersVariables): ResourceQueryKey[] => [\n [GroupQueryKeys.GROUP, groupId],\n [GroupQueryKeys.GROUP_MEMBERS, groupId],\n [GroupQueryKeys.MEMBERSHIPS],\n [RoleQueryKeys.ROLE_ASSIGNMENTS],\n];\n\nexport type GroupMembersProps = Omit<\n BaseGroupMembersProps,\n 'candidates' | 'error' | 'isLoading' | 'isMutating' | 'members' | 'onAdd' | 'onRemove'\n> & {\n /**\n * How a member is removed. `flow` runs the administration flow the server configures for it, which can revoke\n * grants and end sessions first. It needs `baseUrl`.\n * @default 'native'\n */\n administrationMode?: AdministrationMode;\n /**\n * Whether members can be added and removed from this view.\n * @default false\n */\n editable?: boolean;\n groupId: string;\n};\n\n/**\n * Lists the users and agents in a group, and lets the signed-in user add and remove them.\n *\n * @example\n * ```tsx\n * <GroupMembers groupId={groupId} />\n * ```\n */\nconst GroupMembers: FC<GroupMembersProps> = ({\n administrationMode,\n editable = false,\n groupId,\n preferences,\n ...rest\n}: GroupMembersProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const groupsEndpoint: ManagementEndpoint = useManagementEndpoint('groups');\n const usersEndpoint: ManagementEndpoint = useManagementEndpoint('users');\n const agentsEndpoint: ManagementEndpoint = useManagementEndpoint('agents');\n const members: ResourceQueryResult<GroupMemberListResponse> = useResourceQuery(\n [GroupQueryKeys.GROUP_MEMBERS, groupId, {limit: 100}],\n (fetcher) => getGroupMembers({...groupsEndpoint, fetcher, groupId, limit: 100}),\n {enabled: Boolean(groupId)},\n );\n const users: ResourceQueryResult<ManagedUserListResponse> = useResourceQuery(\n [UserQueryKeys.USERS, {limit: 100}],\n (fetcher) => getUsers({...usersEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const agents: ResourceQueryResult<AgentListResponse> = useResourceQuery(\n [AgentQueryKeys.AGENTS, {limit: 100}],\n (fetcher) => getAgents({...agentsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const add: ResourceMutationResult<Group, GroupMembersVariables> = useResourceMutation(\n ({groupId: id, members: changes}: GroupMembersVariables, fetcher) =>\n addGroupMembers({...groupsEndpoint, fetcher, groupId: id, members: changes}),\n getMembershipInvalidatedKeys,\n );\n const remove: ResourceMutationResult<Group, GroupMembersVariables> = useResourceMutation(\n ({groupId: id, members: changes}: GroupMembersVariables, fetcher) =>\n removeGroupMembers({...groupsEndpoint, administrationMode, fetcher, groupId: id, members: changes}),\n getMembershipInvalidatedKeys,\n );\n\n const candidates: Partial<Record<'user' | 'agent', IdentityCandidate[]>> = useMemo(\n () => ({\n agent: (agents.data?.agents ?? []).map((agent: BasicAgent) => ({id: agent.id, label: agent.name})),\n user: (users.data?.users ?? []).map((user: ManagedUser) => ({\n id: user.id,\n label: resolveManagedIdentityName(user),\n })),\n }),\n [agents.data, users.data],\n );\n\n return (\n <BaseGroupMembers\n members={members.data?.members ?? []}\n isLoading={members.isLoading}\n isMutating={add.isLoading || remove.isLoading}\n error={\n toManagementErrorMessage(t, members.error, 'admin.groups.members.error') ??\n toManagementErrorMessage(t, add.error ?? remove.error, 'admin.groups.members.update_error')\n }\n candidates={editable ? candidates : undefined}\n onAdd={\n editable\n ? (member: Pick<GroupMember, 'id' | 'type'>): void => void add.mutate({groupId, members: [member]})\n : undefined\n }\n onRemove={\n editable\n ? (member: GroupMember): void => void remove.mutate({groupId, members: [{id: member.id, type: member.type}]})\n : undefined\n }\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default GroupMembers;\n"],"mappings":";;;;;;;;;;;;;AAyCA,MAAM,gCAAgC,EAAC,cAAwD;CAC7F,CAAC,eAAe,OAAO,QAAQ;CAC/B,CAAC,eAAe,eAAe,QAAQ;CACvC,CAAC,eAAe,YAAY;CAC5B,CAAC,cAAc,iBAAiB;CACjC;;;;;;;;;AA4BD,MAAMA,gBAAuC,EAC3C,oBACA,WAAW,OACX,SACA,YACA,GAAG,WACkC;CACrC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAMC,iBAAqCC,8BAAsB,SAAS;CAC1E,MAAMC,gBAAoCD,8BAAsB,QAAQ;CACxE,MAAME,iBAAqCF,8BAAsB,SAAS;CAC1E,MAAMG,UAAwDC,yBAC5D;EAAC,eAAe;EAAe;EAAS,EAAC,OAAO,KAAI;EAAC,GACpD,YAAY,gBAAgB;EAAC,GAAG;EAAgB;EAAS;EAAS,OAAO;EAAI,CAAC,EAC/E,EAAC,SAAS,QAAQ,QAAQ,EAAC,CAC5B;CACD,MAAMC,QAAsDD,yBAC1D,CAAC,cAAc,OAAO,EAAC,OAAO,KAAI,CAAC,GAClC,YAAY,SAAS;EAAC,GAAG;EAAe;EAAS,OAAO;EAAI,CAAC,EAC9D,EAAC,SAAS,UAAS,CACpB;CACD,MAAME,SAAiDF,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMG,MAA4DC,6BAC/D,EAAC,SAAS,IAAI,SAAS,WAAiC,YACvD,gBAAgB;EAAC,GAAG;EAAgB;EAAS,SAAS;EAAI,SAAS;EAAQ,CAAC,EAC9E,6BACD;CACD,MAAMC,SAA+DD,6BAClE,EAAC,SAAS,IAAI,SAAS,WAAiC,YACvD,mBAAmB;EAAC,GAAG;EAAgB;EAAoB;EAAS,SAAS;EAAI,SAAS;EAAQ,CAAC,EACrG,6BACD;CAED,MAAME,aAAqE,eAClE;EACL,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAuB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAClG,OAAO,MAAM,MAAM,SAAS,EAAE,EAAE,KAAK,UAAuB;GAC1D,IAAI,KAAK;GACT,OAAO,2BAA2B,KAAK;GACxC,EAAE;EACJ,GACD,CAAC,OAAO,MAAM,MAAM,KAAK,CAC1B;AAED,QACE,oBAACC;EACC,SAAS,QAAQ,MAAM,WAAW,EAAE;EACpC,WAAW,QAAQ;EACnB,YAAY,IAAI,aAAa,OAAO;EACpC,OACE,yBAAyB,GAAG,QAAQ,OAAO,6BAA6B,IACxE,yBAAyB,GAAG,IAAI,SAAS,OAAO,OAAO,oCAAoC;EAE7F,YAAY,WAAW,aAAa;EACpC,OACE,YACK,WAAmD,KAAK,IAAI,OAAO;GAAC;GAAS,SAAS,CAAC,OAAO;GAAC,CAAC,GACjG;EAEN,UACE,YACK,WAA8B,KAAK,OAAO,OAAO;GAAC;GAAS,SAAS,CAAC;IAAC,IAAI,OAAO;IAAI,MAAM,OAAO;IAAK,CAAC;GAAC,CAAC,GAC3G;EAEO;EACb,GAAI;GACJ;;AAIN,2BAAe"}
@@ -1,6 +1,13 @@
1
+ import { AdministrationMode } from '@thunderid/browser';
1
2
  import { FC } from 'react';
2
3
  import { BaseRoleAssignmentsProps } from './BaseRoleAssignments';
3
4
  export type RoleAssignmentsProps = Omit<BaseRoleAssignmentsProps, 'assignments' | 'candidates' | 'error' | 'isLoading' | 'isMutating' | 'onAssign' | 'onTypeFilterChange' | 'onUnassign' | 'typeFilter'> & {
5
+ /**
6
+ * How an assignment is removed. `flow` runs the administration flow the server configures for it, which can revoke
7
+ * grants and end sessions first. It needs `baseUrl`.
8
+ * @default 'native'
9
+ */
10
+ administrationMode?: AdministrationMode;
4
11
  /**
5
12
  * Whether assignments can be added and removed from this view.
6
13
  * @default false
@@ -1 +1 @@
1
- {"version":3,"file":"RoleAssignments.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/RoleAssignments/RoleAssignments.tsx"],"names":[],"mappings":"AA2BA,OAAO,EAAC,EAAE,EAAkC,MAAM,OAAO,CAAC;AAC1D,OAA4B,EAAC,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AAYpF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,wBAAwB,EACtB,aAAa,GACb,YAAY,GACZ,OAAO,GACP,WAAW,GACX,YAAY,GACZ,UAAU,GACV,oBAAoB,GACpB,YAAY,GACZ,YAAY,CACf,GAAG;IACF;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAoF7C,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"RoleAssignments.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/RoleAssignments/RoleAssignments.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAuBnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAkC,MAAM,OAAO,CAAC;AAC1D,OAA4B,EAAC,wBAAwB,EAAC,MAAM,uBAAuB,CAAC;AAYpF,MAAM,MAAM,oBAAoB,GAAG,IAAI,CACrC,wBAAwB,EACtB,aAAa,GACb,YAAY,GACZ,OAAO,GACP,WAAW,GACX,YAAY,GACZ,UAAU,GACV,oBAAoB,GACpB,YAAY,GACZ,YAAY,CACf,GAAG;IACF;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;;GAQG;AACH,QAAA,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAqF7C,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -17,7 +17,7 @@ import { jsx } from "react/jsx-runtime";
17
17
  * <RoleAssignments roleId={roleId} />
18
18
  * ```
19
19
  */
20
- const RoleAssignments = ({ editable = false, filterable = true, preferences, roleId,...rest }) => {
20
+ const RoleAssignments = ({ administrationMode, editable = false, filterable = true, preferences, roleId,...rest }) => {
21
21
  const { t } = useTranslation_default(preferences?.i18n);
22
22
  const [typeFilter, setTypeFilter] = useState(null);
23
23
  const rolesEndpoint = useManagementEndpoint_default("roles");
@@ -62,6 +62,7 @@ const RoleAssignments = ({ editable = false, filterable = true, preferences, rol
62
62
  }), () => [[RoleQueryKeys.ROLE_ASSIGNMENTS]]);
63
63
  const unassign = useResourceMutation_default(({ assignments: changes, roleId: id }, fetcher) => removeRoleAssignments({
64
64
  ...rolesEndpoint,
65
+ administrationMode,
65
66
  assignments: changes,
66
67
  fetcher,
67
68
  roleId: id
@@ -1 +1 @@
1
- {"version":3,"file":"RoleAssignments.js","names":["RoleAssignments: FC<RoleAssignmentsProps>","useTranslation","rolesEndpoint: ManagementEndpoint","useManagementEndpoint","usersEndpoint: ManagementEndpoint","groupsEndpoint: ManagementEndpoint","agentsEndpoint: ManagementEndpoint","type: RoleAssigneeType | undefined","assignments: ResourceQueryResult<RoleAssignmentListResponse>","useResourceQuery","users: ResourceQueryResult<ManagedUserListResponse>","groups: ResourceQueryResult<GroupListResponse>","agents: ResourceQueryResult<AgentListResponse>","assign: ResourceMutationResult<void, RoleAssignmentsVariables>","useResourceMutation","unassign: ResourceMutationResult<void, RoleAssignmentsVariables>","candidates: Partial<Record<'user' | 'group' | 'agent', IdentityCandidate[]>>","BaseRoleAssignments"],"sources":["../../../../src/components/admin/RoleAssignments/RoleAssignments.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AgentListResponse,\n AgentQueryKeys,\n BasicAgent,\n Group,\n GroupListResponse,\n GroupQueryKeys,\n ManagedUser,\n ManagedUserListResponse,\n RoleAssigneeType,\n RoleAssignment,\n RoleAssignmentInput,\n RoleAssignmentListResponse,\n RoleQueryKeys,\n UserQueryKeys,\n addRoleAssignments,\n getAgents,\n getGroups,\n getRoleAssignments,\n getUsers,\n removeRoleAssignments,\n resolveManagedIdentityName,\n toManagementErrorMessage,\n} from '@thunderid/browser';\nimport {FC, ReactElement, useMemo, useState} from 'react';\nimport BaseRoleAssignments, {BaseRoleAssignmentsProps} from './BaseRoleAssignments';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\nimport {IdentityCandidate} from '../shared/IdentityPicker';\n\ninterface RoleAssignmentsVariables {\n assignments: RoleAssignmentInput[];\n roleId: string;\n}\n\nexport type RoleAssignmentsProps = Omit<\n BaseRoleAssignmentsProps,\n | 'assignments'\n | 'candidates'\n | 'error'\n | 'isLoading'\n | 'isMutating'\n | 'onAssign'\n | 'onTypeFilterChange'\n | 'onUnassign'\n | 'typeFilter'\n> & {\n /**\n * Whether assignments can be added and removed from this view.\n * @default false\n */\n editable?: boolean;\n /**\n * Whether to show the assignee type filter, which narrows the list on the server.\n * @default true\n */\n filterable?: boolean;\n roleId: string;\n};\n\n/**\n * Lists who holds a role, and lets the signed-in user assign it to users, groups and agents or\n * remove it from them.\n *\n * @example\n * ```tsx\n * <RoleAssignments roleId={roleId} />\n * ```\n */\nconst RoleAssignments: FC<RoleAssignmentsProps> = ({\n editable = false,\n filterable = true,\n preferences,\n roleId,\n ...rest\n}: RoleAssignmentsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const [typeFilter, setTypeFilter] = useState<RoleAssigneeType | null>(null);\n const rolesEndpoint: ManagementEndpoint = useManagementEndpoint('roles');\n const usersEndpoint: ManagementEndpoint = useManagementEndpoint('users');\n const groupsEndpoint: ManagementEndpoint = useManagementEndpoint('groups');\n const agentsEndpoint: ManagementEndpoint = useManagementEndpoint('agents');\n const type: RoleAssigneeType | undefined = typeFilter ?? undefined;\n const assignments: ResourceQueryResult<RoleAssignmentListResponse> = useResourceQuery(\n [RoleQueryKeys.ROLE_ASSIGNMENTS, roleId, {limit: 100, type}],\n (fetcher) => getRoleAssignments({...rolesEndpoint, fetcher, limit: 100, roleId, type}),\n {enabled: Boolean(roleId)},\n );\n const users: ResourceQueryResult<ManagedUserListResponse> = useResourceQuery(\n [UserQueryKeys.USERS, {limit: 100}],\n (fetcher) => getUsers({...usersEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const groups: ResourceQueryResult<GroupListResponse> = useResourceQuery(\n [GroupQueryKeys.GROUPS, {limit: 100}],\n (fetcher) => getGroups({...groupsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const agents: ResourceQueryResult<AgentListResponse> = useResourceQuery(\n [AgentQueryKeys.AGENTS, {limit: 100}],\n (fetcher) => getAgents({...agentsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const assign: ResourceMutationResult<void, RoleAssignmentsVariables> = useResourceMutation(\n ({assignments: changes, roleId: id}: RoleAssignmentsVariables, fetcher) =>\n addRoleAssignments({...rolesEndpoint, assignments: changes, fetcher, roleId: id}),\n () => [[RoleQueryKeys.ROLE_ASSIGNMENTS]],\n );\n const unassign: ResourceMutationResult<void, RoleAssignmentsVariables> = useResourceMutation(\n ({assignments: changes, roleId: id}: RoleAssignmentsVariables, fetcher) =>\n removeRoleAssignments({...rolesEndpoint, assignments: changes, fetcher, roleId: id}),\n () => [[RoleQueryKeys.ROLE_ASSIGNMENTS]],\n );\n\n const candidates: Partial<Record<'user' | 'group' | 'agent', IdentityCandidate[]>> = useMemo(\n () => ({\n agent: (agents.data?.agents ?? []).map((agent: BasicAgent) => ({id: agent.id, label: agent.name})),\n group: (groups.data?.groups ?? []).map((group: Group) => ({id: group.id, label: group.name})),\n user: (users.data?.users ?? []).map((user: ManagedUser) => ({\n id: user.id,\n label: resolveManagedIdentityName(user),\n })),\n }),\n [agents.data, groups.data, users.data],\n );\n\n return (\n <BaseRoleAssignments\n assignments={assignments.data?.assignments ?? []}\n isLoading={assignments.isLoading}\n isMutating={assign.isLoading || unassign.isLoading}\n error={\n toManagementErrorMessage(t, assignments.error, 'admin.roles.assignments.error') ??\n toManagementErrorMessage(t, assign.error ?? unassign.error, 'admin.roles.assignments.update_error')\n }\n candidates={editable ? candidates : undefined}\n onAssign={\n editable\n ? (assignment: RoleAssignmentInput): void => void assign.mutate({assignments: [assignment], roleId})\n : undefined\n }\n onUnassign={\n editable\n ? (assignment: RoleAssignment): void =>\n void unassign.mutate({assignments: [{id: assignment.id, type: assignment.type}], roleId})\n : undefined\n }\n typeFilter={typeFilter}\n onTypeFilterChange={filterable ? setTypeFilter : undefined}\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default RoleAssignments;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA0EA,MAAMA,mBAA6C,EACjD,WAAW,OACX,aAAa,MACb,aACA,OACA,GAAG,WACqC;CACxC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAM,CAAC,YAAY,iBAAiB,SAAkC,KAAK;CAC3E,MAAMC,gBAAoCC,8BAAsB,QAAQ;CACxE,MAAMC,gBAAoCD,8BAAsB,QAAQ;CACxE,MAAME,iBAAqCF,8BAAsB,SAAS;CAC1E,MAAMG,iBAAqCH,8BAAsB,SAAS;CAC1E,MAAMI,OAAqC,cAAc;CACzD,MAAMC,cAA+DC,yBACnE;EAAC,cAAc;EAAkB;EAAQ;GAAC,OAAO;GAAK;GAAK;EAAC,GAC3D,YAAY,mBAAmB;EAAC,GAAG;EAAe;EAAS,OAAO;EAAK;EAAQ;EAAK,CAAC,EACtF,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAMC,QAAsDD,yBAC1D,CAAC,cAAc,OAAO,EAAC,OAAO,KAAI,CAAC,GAClC,YAAY,SAAS;EAAC,GAAG;EAAe;EAAS,OAAO;EAAI,CAAC,EAC9D,EAAC,SAAS,UAAS,CACpB;CACD,MAAME,SAAiDF,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMG,SAAiDH,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMI,SAAiEC,6BACpE,EAAC,aAAa,SAAS,QAAQ,MAA+B,YAC7D,mBAAmB;EAAC,GAAG;EAAe,aAAa;EAAS;EAAS,QAAQ;EAAG,CAAC,QAC7E,CAAC,CAAC,cAAc,iBAAiB,CAAC,CACzC;CACD,MAAMC,WAAmED,6BACtE,EAAC,aAAa,SAAS,QAAQ,MAA+B,YAC7D,sBAAsB;EAAC,GAAG;EAAe,aAAa;EAAS;EAAS,QAAQ;EAAG,CAAC,QAChF,CAAC,CAAC,cAAc,iBAAiB,CAAC,CACzC;CAED,MAAME,aAA+E,eAC5E;EACL,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAuB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAClG,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAkB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAC7F,OAAO,MAAM,MAAM,SAAS,EAAE,EAAE,KAAK,UAAuB;GAC1D,IAAI,KAAK;GACT,OAAO,2BAA2B,KAAK;GACxC,EAAE;EACJ,GACD;EAAC,OAAO;EAAM,OAAO;EAAM,MAAM;EAAK,CACvC;AAED,QACE,oBAACC;EACC,aAAa,YAAY,MAAM,eAAe,EAAE;EAChD,WAAW,YAAY;EACvB,YAAY,OAAO,aAAa,SAAS;EACzC,OACE,yBAAyB,GAAG,YAAY,OAAO,gCAAgC,IAC/E,yBAAyB,GAAG,OAAO,SAAS,SAAS,OAAO,uCAAuC;EAErG,YAAY,WAAW,aAAa;EACpC,UACE,YACK,eAA0C,KAAK,OAAO,OAAO;GAAC,aAAa,CAAC,WAAW;GAAE;GAAO,CAAC,GAClG;EAEN,YACE,YACK,eACC,KAAK,SAAS,OAAO;GAAC,aAAa,CAAC;IAAC,IAAI,WAAW;IAAI,MAAM,WAAW;IAAK,CAAC;GAAE;GAAO,CAAC,GAC3F;EAEM;EACZ,oBAAoB,aAAa,gBAAgB;EACpC;EACb,GAAI;GACJ;;AAIN,8BAAe"}
1
+ {"version":3,"file":"RoleAssignments.js","names":["RoleAssignments: FC<RoleAssignmentsProps>","useTranslation","rolesEndpoint: ManagementEndpoint","useManagementEndpoint","usersEndpoint: ManagementEndpoint","groupsEndpoint: ManagementEndpoint","agentsEndpoint: ManagementEndpoint","type: RoleAssigneeType | undefined","assignments: ResourceQueryResult<RoleAssignmentListResponse>","useResourceQuery","users: ResourceQueryResult<ManagedUserListResponse>","groups: ResourceQueryResult<GroupListResponse>","agents: ResourceQueryResult<AgentListResponse>","assign: ResourceMutationResult<void, RoleAssignmentsVariables>","useResourceMutation","unassign: ResourceMutationResult<void, RoleAssignmentsVariables>","candidates: Partial<Record<'user' | 'group' | 'agent', IdentityCandidate[]>>","BaseRoleAssignments"],"sources":["../../../../src/components/admin/RoleAssignments/RoleAssignments.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AdministrationMode,\n AgentListResponse,\n AgentQueryKeys,\n BasicAgent,\n Group,\n GroupListResponse,\n GroupQueryKeys,\n ManagedUser,\n ManagedUserListResponse,\n RoleAssigneeType,\n RoleAssignment,\n RoleAssignmentInput,\n RoleAssignmentListResponse,\n RoleQueryKeys,\n UserQueryKeys,\n addRoleAssignments,\n getAgents,\n getGroups,\n getRoleAssignments,\n getUsers,\n removeRoleAssignments,\n resolveManagedIdentityName,\n toManagementErrorMessage,\n} from '@thunderid/browser';\nimport {FC, ReactElement, useMemo, useState} from 'react';\nimport BaseRoleAssignments, {BaseRoleAssignmentsProps} from './BaseRoleAssignments';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\nimport {IdentityCandidate} from '../shared/IdentityPicker';\n\ninterface RoleAssignmentsVariables {\n assignments: RoleAssignmentInput[];\n roleId: string;\n}\n\nexport type RoleAssignmentsProps = Omit<\n BaseRoleAssignmentsProps,\n | 'assignments'\n | 'candidates'\n | 'error'\n | 'isLoading'\n | 'isMutating'\n | 'onAssign'\n | 'onTypeFilterChange'\n | 'onUnassign'\n | 'typeFilter'\n> & {\n /**\n * How an assignment is removed. `flow` runs the administration flow the server configures for it, which can revoke\n * grants and end sessions first. It needs `baseUrl`.\n * @default 'native'\n */\n administrationMode?: AdministrationMode;\n /**\n * Whether assignments can be added and removed from this view.\n * @default false\n */\n editable?: boolean;\n /**\n * Whether to show the assignee type filter, which narrows the list on the server.\n * @default true\n */\n filterable?: boolean;\n roleId: string;\n};\n\n/**\n * Lists who holds a role, and lets the signed-in user assign it to users, groups and agents or\n * remove it from them.\n *\n * @example\n * ```tsx\n * <RoleAssignments roleId={roleId} />\n * ```\n */\nconst RoleAssignments: FC<RoleAssignmentsProps> = ({\n administrationMode,\n editable = false,\n filterable = true,\n preferences,\n roleId,\n ...rest\n}: RoleAssignmentsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const [typeFilter, setTypeFilter] = useState<RoleAssigneeType | null>(null);\n const rolesEndpoint: ManagementEndpoint = useManagementEndpoint('roles');\n const usersEndpoint: ManagementEndpoint = useManagementEndpoint('users');\n const groupsEndpoint: ManagementEndpoint = useManagementEndpoint('groups');\n const agentsEndpoint: ManagementEndpoint = useManagementEndpoint('agents');\n const type: RoleAssigneeType | undefined = typeFilter ?? undefined;\n const assignments: ResourceQueryResult<RoleAssignmentListResponse> = useResourceQuery(\n [RoleQueryKeys.ROLE_ASSIGNMENTS, roleId, {limit: 100, type}],\n (fetcher) => getRoleAssignments({...rolesEndpoint, fetcher, limit: 100, roleId, type}),\n {enabled: Boolean(roleId)},\n );\n const users: ResourceQueryResult<ManagedUserListResponse> = useResourceQuery(\n [UserQueryKeys.USERS, {limit: 100}],\n (fetcher) => getUsers({...usersEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const groups: ResourceQueryResult<GroupListResponse> = useResourceQuery(\n [GroupQueryKeys.GROUPS, {limit: 100}],\n (fetcher) => getGroups({...groupsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const agents: ResourceQueryResult<AgentListResponse> = useResourceQuery(\n [AgentQueryKeys.AGENTS, {limit: 100}],\n (fetcher) => getAgents({...agentsEndpoint, fetcher, limit: 100}),\n {enabled: editable},\n );\n const assign: ResourceMutationResult<void, RoleAssignmentsVariables> = useResourceMutation(\n ({assignments: changes, roleId: id}: RoleAssignmentsVariables, fetcher) =>\n addRoleAssignments({...rolesEndpoint, assignments: changes, fetcher, roleId: id}),\n () => [[RoleQueryKeys.ROLE_ASSIGNMENTS]],\n );\n const unassign: ResourceMutationResult<void, RoleAssignmentsVariables> = useResourceMutation(\n ({assignments: changes, roleId: id}: RoleAssignmentsVariables, fetcher) =>\n removeRoleAssignments({...rolesEndpoint, administrationMode, assignments: changes, fetcher, roleId: id}),\n () => [[RoleQueryKeys.ROLE_ASSIGNMENTS]],\n );\n\n const candidates: Partial<Record<'user' | 'group' | 'agent', IdentityCandidate[]>> = useMemo(\n () => ({\n agent: (agents.data?.agents ?? []).map((agent: BasicAgent) => ({id: agent.id, label: agent.name})),\n group: (groups.data?.groups ?? []).map((group: Group) => ({id: group.id, label: group.name})),\n user: (users.data?.users ?? []).map((user: ManagedUser) => ({\n id: user.id,\n label: resolveManagedIdentityName(user),\n })),\n }),\n [agents.data, groups.data, users.data],\n );\n\n return (\n <BaseRoleAssignments\n assignments={assignments.data?.assignments ?? []}\n isLoading={assignments.isLoading}\n isMutating={assign.isLoading || unassign.isLoading}\n error={\n toManagementErrorMessage(t, assignments.error, 'admin.roles.assignments.error') ??\n toManagementErrorMessage(t, assign.error ?? unassign.error, 'admin.roles.assignments.update_error')\n }\n candidates={editable ? candidates : undefined}\n onAssign={\n editable\n ? (assignment: RoleAssignmentInput): void => void assign.mutate({assignments: [assignment], roleId})\n : undefined\n }\n onUnassign={\n editable\n ? (assignment: RoleAssignment): void =>\n void unassign.mutate({assignments: [{id: assignment.id, type: assignment.type}], roleId})\n : undefined\n }\n typeFilter={typeFilter}\n onTypeFilterChange={filterable ? setTypeFilter : undefined}\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default RoleAssignments;\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAiFA,MAAMA,mBAA6C,EACjD,oBACA,WAAW,OACX,aAAa,MACb,aACA,OACA,GAAG,WACqC;CACxC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAM,CAAC,YAAY,iBAAiB,SAAkC,KAAK;CAC3E,MAAMC,gBAAoCC,8BAAsB,QAAQ;CACxE,MAAMC,gBAAoCD,8BAAsB,QAAQ;CACxE,MAAME,iBAAqCF,8BAAsB,SAAS;CAC1E,MAAMG,iBAAqCH,8BAAsB,SAAS;CAC1E,MAAMI,OAAqC,cAAc;CACzD,MAAMC,cAA+DC,yBACnE;EAAC,cAAc;EAAkB;EAAQ;GAAC,OAAO;GAAK;GAAK;EAAC,GAC3D,YAAY,mBAAmB;EAAC,GAAG;EAAe;EAAS,OAAO;EAAK;EAAQ;EAAK,CAAC,EACtF,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAMC,QAAsDD,yBAC1D,CAAC,cAAc,OAAO,EAAC,OAAO,KAAI,CAAC,GAClC,YAAY,SAAS;EAAC,GAAG;EAAe;EAAS,OAAO;EAAI,CAAC,EAC9D,EAAC,SAAS,UAAS,CACpB;CACD,MAAME,SAAiDF,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMG,SAAiDH,yBACrD,CAAC,eAAe,QAAQ,EAAC,OAAO,KAAI,CAAC,GACpC,YAAY,UAAU;EAAC,GAAG;EAAgB;EAAS,OAAO;EAAI,CAAC,EAChE,EAAC,SAAS,UAAS,CACpB;CACD,MAAMI,SAAiEC,6BACpE,EAAC,aAAa,SAAS,QAAQ,MAA+B,YAC7D,mBAAmB;EAAC,GAAG;EAAe,aAAa;EAAS;EAAS,QAAQ;EAAG,CAAC,QAC7E,CAAC,CAAC,cAAc,iBAAiB,CAAC,CACzC;CACD,MAAMC,WAAmED,6BACtE,EAAC,aAAa,SAAS,QAAQ,MAA+B,YAC7D,sBAAsB;EAAC,GAAG;EAAe;EAAoB,aAAa;EAAS;EAAS,QAAQ;EAAG,CAAC,QACpG,CAAC,CAAC,cAAc,iBAAiB,CAAC,CACzC;CAED,MAAME,aAA+E,eAC5E;EACL,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAuB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAClG,QAAQ,OAAO,MAAM,UAAU,EAAE,EAAE,KAAK,WAAkB;GAAC,IAAI,MAAM;GAAI,OAAO,MAAM;GAAK,EAAE;EAC7F,OAAO,MAAM,MAAM,SAAS,EAAE,EAAE,KAAK,UAAuB;GAC1D,IAAI,KAAK;GACT,OAAO,2BAA2B,KAAK;GACxC,EAAE;EACJ,GACD;EAAC,OAAO;EAAM,OAAO;EAAM,MAAM;EAAK,CACvC;AAED,QACE,oBAACC;EACC,aAAa,YAAY,MAAM,eAAe,EAAE;EAChD,WAAW,YAAY;EACvB,YAAY,OAAO,aAAa,SAAS;EACzC,OACE,yBAAyB,GAAG,YAAY,OAAO,gCAAgC,IAC/E,yBAAyB,GAAG,OAAO,SAAS,SAAS,OAAO,uCAAuC;EAErG,YAAY,WAAW,aAAa;EACpC,UACE,YACK,eAA0C,KAAK,OAAO,OAAO;GAAC,aAAa,CAAC,WAAW;GAAE;GAAO,CAAC,GAClG;EAEN,YACE,YACK,eACC,KAAK,SAAS,OAAO;GAAC,aAAa,CAAC;IAAC,IAAI,WAAW;IAAI,MAAM,WAAW;IAAK,CAAC;GAAE;GAAO,CAAC,GAC3F;EAEM;EACZ,oBAAoB,aAAa,gBAAgB;EACpC;EACb,GAAI;GACJ;;AAIN,8BAAe"}
@@ -1,4 +1,4 @@
1
- import { ManagedUser, MembershipGroup, Preferences } from '@thunderid/browser';
1
+ import { AccountLifecycleAction, ManagedUser, MembershipGroup, Preferences } from '@thunderid/browser';
2
2
  import { FC, ReactNode } from 'react';
3
3
  /**
4
4
  * What {@link BaseUserDetails} hands its `children` render function, so the user can be shown with any UI.
@@ -16,11 +16,21 @@ export interface BaseUserDetailsRenderProps {
16
16
  groups: MembershipGroup[];
17
17
  isDeleting: boolean;
18
18
  isLoading: boolean;
19
+ isRunningLifecycleAction: boolean;
19
20
  isUpdating: boolean;
21
+ /**
22
+ * The account lifecycle actions offered for the user. Empty when none is allowed.
23
+ */
24
+ lifecycleActions: AccountLifecycleAction[];
20
25
  /**
21
26
  * Deletes the user. Undefined when deletion is not allowed, including for read-only users.
22
27
  */
23
28
  onDelete?: () => void;
29
+ /**
30
+ * Runs an account lifecycle action, with the operator's note on a suspension. Undefined when none is
31
+ * allowed.
32
+ */
33
+ onLifecycleAction?: (action: AccountLifecycleAction, operatorNote?: string) => void;
24
34
  /**
25
35
  * Saves the given attributes. Undefined when editing is not allowed, including for read-only users.
26
36
  */
@@ -50,11 +60,22 @@ export interface BaseUserDetailsProps {
50
60
  hiddenAttributes?: string[];
51
61
  isDeleting?: boolean;
52
62
  isLoading?: boolean;
63
+ isRunningLifecycleAction?: boolean;
53
64
  isUpdating?: boolean;
65
+ /**
66
+ * The account lifecycle actions to offer (suspend, unsuspend, unlock), shown only with
67
+ * `onLifecycleAction`. They change only runtime state, so they apply to read-only users too.
68
+ */
69
+ lifecycleActions?: AccountLifecycleAction[];
54
70
  /**
55
71
  * Deletes the user. The delete action is hidden when omitted.
56
72
  */
57
73
  onDelete?: () => void;
74
+ /**
75
+ * Runs an account lifecycle action. Suspending and unsuspending ask for confirmation first, and a
76
+ * suspension takes an optional operator note.
77
+ */
78
+ onLifecycleAction?: (action: AccountLifecycleAction, operatorNote?: string) => void;
58
79
  /**
59
80
  * Saves edited attributes. Text attributes become editable only when set. Receives every
60
81
  * attribute, edited or not.
@@ -1 +1 @@
1
- {"version":3,"file":"BaseUserDetails.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/UserDetails/BaseUserDetails.tsx"],"names":[],"mappings":"AAGA,OAAO,EAA6B,WAAW,EAAE,eAAe,EAAE,WAAW,EAAC,MAAM,oBAAoB,CAAC;AACzG,OAAO,EAAc,EAAE,EAAgB,SAAS,EAAW,MAAM,OAAO,CAAC;AAWzE;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;OAEG;IACH,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAUD;;;GAGG;AACH,QAAA,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAqK7C,CAAC;AAEF,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"BaseUserDetails.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/UserDetails/BaseUserDetails.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,sBAAsB,EAEtB,WAAW,EACX,eAAe,EACf,WAAW,EACZ,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAc,EAAE,EAAgB,SAAS,EAAW,MAAM,OAAO,CAAC;AAYzE;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACpC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,eAAe,EAAE,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,wBAAwB,EAAE,OAAO,CAAC;IAClC,UAAU,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,gBAAgB,EAAE,sBAAsB,EAAE,CAAC;IAC3C;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpF;;OAEG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzD,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,oBAAoB;IACnC;;OAEG;IACH,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,KAAK,EAAE,0BAA0B,KAAK,SAAS,CAAC;IAC5D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB;;OAEG;IACH,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wBAAwB,CAAC,EAAE,OAAO,CAAC;IACnC,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC5C;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;IACtB;;;OAGG;IACH,iBAAiB,CAAC,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IACpF;;;OAGG;IACH,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACzD,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,IAAI,CAAC,EAAE,WAAW,CAAC;CACpB;AAUD;;;GAGG;AACH,QAAA,MAAM,eAAe,EAAE,EAAE,CAAC,oBAAoB,CAqL7C,CAAC;AAEF,eAAe,eAAe,CAAC"}
@@ -6,6 +6,7 @@ import TextField_default from "../../primitives/TextField/TextField.js";
6
6
  import { Avatar } from "../../primitives/Avatar/Avatar.js";
7
7
  import useAdminPartStyles_default from "../shared/useAdminPartStyles.js";
8
8
  import AdminSection_default from "../shared/AdminSection.js";
9
+ import { AccessStatusBadge, AccountLifecycleButtons } from "../shared/AccountLifecycleParts.js";
9
10
  import { ChipList, ConfirmButton, DetailList, Panel } from "../shared/AdminParts.js";
10
11
  import { resolveManagedIdentityName } from "@thunderid/browser";
11
12
  import { useState } from "react";
@@ -20,7 +21,7 @@ const stringify = (value) => {
20
21
  * Shows a user's attributes, type, organization unit and groups. Holds no data of its own: pass the
21
22
  * user and handle deletion.
22
23
  */
23
- const BaseUserDetails = ({ actions, children, className, error = null, groups = [], hiddenAttributes = ["password"], isDeleting = false, isLoading = false, isUpdating = false, onDelete, onUpdate, preferences, user }) => {
24
+ const BaseUserDetails = ({ actions, children, className, error = null, groups = [], hiddenAttributes = ["password"], isDeleting = false, isLoading = false, isRunningLifecycleAction = false, isUpdating = false, lifecycleActions = [], onDelete, onLifecycleAction, onUpdate, preferences, user }) => {
24
25
  const { t } = useTranslation_default(preferences?.i18n);
25
26
  const styles = useAdminPartStyles_default();
26
27
  const [draft, setDraft] = useState(null);
@@ -35,8 +36,11 @@ const BaseUserDetails = ({ actions, children, className, error = null, groups =
35
36
  groups,
36
37
  isDeleting,
37
38
  isLoading,
39
+ isRunningLifecycleAction,
38
40
  isUpdating,
41
+ lifecycleActions: onLifecycleAction && user ? lifecycleActions : [],
39
42
  onDelete: isWritable ? onDelete : void 0,
43
+ onLifecycleAction: user ? onLifecycleAction : void 0,
40
44
  onUpdate: isWritable ? onUpdate : void 0,
41
45
  user
42
46
  })
@@ -75,12 +79,22 @@ const BaseUserDetails = ({ actions, children, className, error = null, groups =
75
79
  title: displayName,
76
80
  description: t("admin.users.details.subtitle", { type: user.type }),
77
81
  preferences,
78
- actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [actions, onDelete && !user.isReadOnly && /* @__PURE__ */ jsx(ConfirmButton, {
79
- confirmText: t("admin.users.details.delete.confirm", { name: displayName }),
80
- isLoading: isDeleting,
81
- onConfirm: onDelete,
82
- children: t("admin.users.details.delete.action")
83
- })] }),
82
+ actions: /* @__PURE__ */ jsxs(Fragment$1, { children: [
83
+ actions,
84
+ onLifecycleAction && /* @__PURE__ */ jsx(AccountLifecycleButtons, {
85
+ actions: lifecycleActions,
86
+ collectsOperatorNote: true,
87
+ isRunning: isRunningLifecycleAction,
88
+ name: displayName,
89
+ onAction: onLifecycleAction
90
+ }),
91
+ onDelete && !user.isReadOnly && /* @__PURE__ */ jsx(ConfirmButton, {
92
+ confirmText: t("admin.users.details.delete.confirm", { name: displayName }),
93
+ isLoading: isDeleting,
94
+ onConfirm: onDelete,
95
+ children: t("admin.users.details.delete.action")
96
+ })
97
+ ] }),
84
98
  children: [
85
99
  error && /* @__PURE__ */ jsx(Alert_default, {
86
100
  variant: "error",
@@ -89,13 +103,17 @@ const BaseUserDetails = ({ actions, children, className, error = null, groups =
89
103
  }),
90
104
  /* @__PURE__ */ jsxs("div", {
91
105
  className: styles["row"],
92
- children: [/* @__PURE__ */ jsx(Avatar, {
93
- name: displayName,
94
- size: 48
95
- }), /* @__PURE__ */ jsx("span", {
96
- className: styles["muted"],
97
- children: user.ouHandle ?? user.ouId
98
- })]
106
+ children: [
107
+ /* @__PURE__ */ jsx(Avatar, {
108
+ name: displayName,
109
+ size: 48
110
+ }),
111
+ /* @__PURE__ */ jsx("span", {
112
+ className: styles["muted"],
113
+ children: user.ouHandle ?? user.ouId
114
+ }),
115
+ /* @__PURE__ */ jsx(AccessStatusBadge, { status: user.status?.value })
116
+ ]
99
117
  }),
100
118
  /* @__PURE__ */ jsx(Panel, {
101
119
  title: t("admin.users.details.attributes"),
@@ -1 +1 @@
1
- {"version":3,"file":"BaseUserDetails.js","names":["BaseUserDetails: FC<BaseUserDetailsProps>","useTranslation","styles: Record<string, string>","useAdminPartStyles","isWritable: boolean","AdminSection","Spinner","Alert","displayName: string","attributes: [string, unknown][]","editableAttributes: [string, string][]","Button","TextField"],"sources":["../../../../src/components/admin/UserDetails/BaseUserDetails.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {resolveManagedIdentityName, ManagedUser, MembershipGroup, Preferences} from '@thunderid/browser';\nimport {ChangeEvent, FC, ReactElement, ReactNode, useState} from 'react';\nimport useTranslation from '../../../hooks/useTranslation';\nimport Alert from '../../primitives/Alert/Alert';\nimport {Avatar} from '../../primitives/Avatar/Avatar';\nimport Button from '../../primitives/Button/Button';\nimport Spinner from '../../primitives/Spinner/Spinner';\nimport TextField from '../../primitives/TextField/TextField';\nimport {ChipList, ConfirmButton, DetailList, Panel} from '../shared/AdminParts';\nimport AdminSection from '../shared/AdminSection';\nimport useAdminPartStyles from '../shared/useAdminPartStyles';\n\n/**\n * What {@link BaseUserDetails} hands its `children` render function, so the user can be shown with any UI.\n */\nexport interface BaseUserDetailsRenderProps {\n /**\n * The user's attributes, without the hidden ones.\n */\n attributes: Record<string, unknown>;\n /**\n * The user's display name, or an empty string while there is no user.\n */\n displayName: string;\n error: string | null;\n groups: MembershipGroup[];\n isDeleting: boolean;\n isLoading: boolean;\n isUpdating: boolean;\n /**\n * Deletes the user. Undefined when deletion is not allowed, including for read-only users.\n */\n onDelete?: () => void;\n /**\n * Saves the given attributes. Undefined when editing is not allowed, including for read-only users.\n */\n onUpdate?: (attributes: Record<string, unknown>) => void;\n user?: ManagedUser;\n}\n\nexport interface BaseUserDetailsProps {\n /**\n * Controls rendered next to the user's name.\n */\n actions?: ReactNode;\n /**\n * Renders custom UI in place of the default view. Receives the user, their visible attributes and\n * groups, the loading and error state, and the delete and update handlers.\n */\n children?: (props: BaseUserDetailsRenderProps) => ReactNode;\n className?: string;\n error?: string | null;\n /**\n * The groups the user belongs to.\n */\n groups?: MembershipGroup[];\n /**\n * Attribute names that are never shown, such as credentials.\n * @default ['password']\n */\n hiddenAttributes?: string[];\n isDeleting?: boolean;\n isLoading?: boolean;\n isUpdating?: boolean;\n /**\n * Deletes the user. The delete action is hidden when omitted.\n */\n onDelete?: () => void;\n /**\n * Saves edited attributes. Text attributes become editable only when set. Receives every\n * attribute, edited or not.\n */\n onUpdate?: (attributes: Record<string, unknown>) => void;\n preferences?: Preferences;\n user?: ManagedUser;\n}\n\nconst stringify = (value: unknown): string => {\n if (value === null || value === undefined) {\n return '';\n }\n\n return typeof value === 'object' ? JSON.stringify(value) : String(value as string | number | boolean);\n};\n\n/**\n * Shows a user's attributes, type, organization unit and groups. Holds no data of its own: pass the\n * user and handle deletion.\n */\nconst BaseUserDetails: FC<BaseUserDetailsProps> = ({\n actions,\n children,\n className,\n error = null,\n groups = [],\n hiddenAttributes = ['password'],\n isDeleting = false,\n isLoading = false,\n isUpdating = false,\n onDelete,\n onUpdate,\n preferences,\n user,\n}: BaseUserDetailsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const styles: Record<string, string> = useAdminPartStyles();\n const [draft, setDraft] = useState<Record<string, string> | null>(null);\n\n if (children) {\n const isWritable: boolean = Boolean(user) && !user?.isReadOnly;\n\n return (\n <div className={className}>\n {children({\n attributes: Object.fromEntries(\n Object.entries(user?.attributes ?? {}).filter(\n ([name]: [string, unknown]) => !hiddenAttributes.includes(name),\n ),\n ),\n displayName: user ? resolveManagedIdentityName(user) : '',\n error,\n groups,\n isDeleting,\n isLoading,\n isUpdating,\n onDelete: isWritable ? onDelete : undefined,\n onUpdate: isWritable ? onUpdate : undefined,\n user,\n })}\n </div>\n );\n }\n\n if (isLoading && !user) {\n return (\n <AdminSection block=\"user-details\" className={className} preferences={preferences}>\n <span className={styles['muted']} role=\"status\">\n <Spinner size=\"small\" /> {t('admin.common.loading')}\n </span>\n </AdminSection>\n );\n }\n\n if (!user) {\n return (\n <AdminSection block=\"user-details\" className={className} preferences={preferences}>\n <Alert variant=\"error\" role=\"alert\">\n {error ?? t('admin.common.errors.not_found')}\n </Alert>\n </AdminSection>\n );\n }\n\n const displayName: string = resolveManagedIdentityName(user);\n const attributes: [string, unknown][] = Object.entries(user.attributes ?? {}).filter(\n ([name]: [string, unknown]) => !hiddenAttributes.includes(name),\n );\n const editableAttributes: [string, string][] = attributes.filter(\n (entry: [string, unknown]): entry is [string, string] => typeof entry[1] === 'string',\n );\n\n return (\n <AdminSection\n block=\"user-details\"\n className={className}\n title={displayName}\n description={t('admin.users.details.subtitle', {type: user.type})}\n preferences={preferences}\n actions={\n <>\n {actions}\n {onDelete && !user.isReadOnly && (\n <ConfirmButton\n confirmText={t('admin.users.details.delete.confirm', {name: displayName})}\n isLoading={isDeleting}\n onConfirm={onDelete}\n >\n {t('admin.users.details.delete.action')}\n </ConfirmButton>\n )}\n </>\n }\n >\n {error && (\n <Alert variant=\"error\" role=\"alert\">\n {error}\n </Alert>\n )}\n <div className={styles['row']}>\n <Avatar name={displayName} size={48} />\n <span className={styles['muted']}>{user.ouHandle ?? user.ouId}</span>\n </div>\n <Panel\n title={t('admin.users.details.attributes')}\n actions={\n onUpdate && !user.isReadOnly && !draft && editableAttributes.length > 0 ? (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={(): void => setDraft(Object.fromEntries(editableAttributes))}\n >\n {t('admin.users.details.edit.action')}\n </Button>\n ) : undefined\n }\n >\n {draft ? (\n <form\n className={styles['row']}\n style={{alignItems: 'stretch', flexDirection: 'column'}}\n onSubmit={(event): void => {\n event.preventDefault();\n onUpdate?.({...(user.attributes ?? {}), ...draft});\n setDraft(null);\n }}\n >\n {Object.keys(draft).map((name: string) => (\n <TextField\n key={name}\n name={name}\n label={name}\n aria-label={name}\n value={draft[name]}\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n setDraft((previous: Record<string, string> | null) => ({...previous, [name]: event.target.value}))\n }\n />\n ))}\n <div className={styles['row']} style={{justifyContent: 'flex-end'}}>\n <Button type=\"button\" variant=\"text\" disabled={isUpdating} onClick={(): void => setDraft(null)}>\n {t('admin.common.actions.cancel')}\n </Button>\n <Button type=\"submit\" loading={isUpdating} disabled={isUpdating}>\n {t('admin.users.details.edit.save')}\n </Button>\n </div>\n </form>\n ) : attributes.length > 0 ? (\n <DetailList\n items={attributes.map(([name, value]: [string, unknown]) => ({label: name, value: stringify(value)}))}\n />\n ) : (\n <span className={styles['muted']}>{t('admin.users.details.no_attributes')}</span>\n )}\n </Panel>\n <Panel title={t('admin.users.details.groups')}>\n <ChipList\n label={t('admin.users.details.groups')}\n emptyText={t('admin.users.details.no_groups')}\n items={groups.map((group: MembershipGroup) => ({id: group.id, label: group.name}))}\n />\n </Panel>\n </AdminSection>\n );\n};\n\nexport default BaseUserDetails;\n"],"mappings":";;;;;;;;;;;;;;AAgFA,MAAM,aAAa,UAA2B;AAC5C,KAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAGT,QAAO,OAAO,UAAU,WAAW,KAAK,UAAU,MAAM,GAAG,OAAO,MAAmC;;;;;;AAOvG,MAAMA,mBAA6C,EACjD,SACA,UACA,WACA,QAAQ,MACR,SAAS,EAAE,EACX,mBAAmB,CAAC,WAAW,EAC/B,aAAa,OACb,YAAY,OACZ,aAAa,OACb,UACA,UACA,aACA,WACwC;CACxC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAMC,SAAiCC,4BAAoB;CAC3D,MAAM,CAAC,OAAO,YAAY,SAAwC,KAAK;AAEvE,KAAI,UAAU;EACZ,MAAMC,aAAsB,QAAQ,KAAK,IAAI,CAAC,MAAM;AAEpD,SACE,oBAAC;GAAe;aACb,SAAS;IACR,YAAY,OAAO,YACjB,OAAO,QAAQ,MAAM,cAAc,EAAE,CAAC,CAAC,QACpC,CAAC,UAA6B,CAAC,iBAAiB,SAAS,KAAK,CAChE,CACF;IACD,aAAa,OAAO,2BAA2B,KAAK,GAAG;IACvD;IACA;IACA;IACA;IACA;IACA,UAAU,aAAa,WAAW;IAClC,UAAU,aAAa,WAAW;IAClC;IACD,CAAC;IACE;;AAIV,KAAI,aAAa,CAAC,KAChB,QACE,oBAACC;EAAa,OAAM;EAA0B;EAAwB;YACpE,qBAAC;GAAK,WAAW,OAAO;GAAU,MAAK;;IACrC,oBAACC,mBAAQ,MAAK,UAAU;;IAAE,EAAE,uBAAuB;;IAC9C;GACM;AAInB,KAAI,CAAC,KACH,QACE,oBAACD;EAAa,OAAM;EAA0B;EAAwB;YACpE,oBAACE;GAAM,SAAQ;GAAQ,MAAK;aACzB,SAAS,EAAE,gCAAgC;IACtC;GACK;CAInB,MAAMC,cAAsB,2BAA2B,KAAK;CAC5D,MAAMC,aAAkC,OAAO,QAAQ,KAAK,cAAc,EAAE,CAAC,CAAC,QAC3E,CAAC,UAA6B,CAAC,iBAAiB,SAAS,KAAK,CAChE;CACD,MAAMC,qBAAyC,WAAW,QACvD,UAAwD,OAAO,MAAM,OAAO,SAC9E;AAED,QACE,qBAACL;EACC,OAAM;EACK;EACX,OAAO;EACP,aAAa,EAAE,gCAAgC,EAAC,MAAM,KAAK,MAAK,CAAC;EACpD;EACb,SACE,8CACG,SACA,YAAY,CAAC,KAAK,cACjB,oBAAC;GACC,aAAa,EAAE,sCAAsC,EAAC,MAAM,aAAY,CAAC;GACzE,WAAW;GACX,WAAW;aAEV,EAAE,oCAAoC;IACzB,IAEjB;;GAGJ,SACC,oBAACE;IAAM,SAAQ;IAAQ,MAAK;cACzB;KACK;GAEV,qBAAC;IAAI,WAAW,OAAO;eACrB,oBAAC;KAAO,MAAM;KAAa,MAAM;MAAM,EACvC,oBAAC;KAAK,WAAW,OAAO;eAAW,KAAK,YAAY,KAAK;MAAY;KACjE;GACN,oBAAC;IACC,OAAO,EAAE,iCAAiC;IAC1C,SACE,YAAY,CAAC,KAAK,cAAc,CAAC,SAAS,mBAAmB,SAAS,IACpE,oBAACI;KACC,SAAQ;KACR,MAAK;KACL,eAAqB,SAAS,OAAO,YAAY,mBAAmB,CAAC;eAEpE,EAAE,kCAAkC;MAC9B,GACP;cAGL,QACC,qBAAC;KACC,WAAW,OAAO;KAClB,OAAO;MAAC,YAAY;MAAW,eAAe;MAAS;KACvD,WAAW,UAAgB;AACzB,YAAM,gBAAgB;AACtB,iBAAW;OAAC,GAAI,KAAK,cAAc,EAAE;OAAG,GAAG;OAAM,CAAC;AAClD,eAAS,KAAK;;gBAGf,OAAO,KAAK,MAAM,CAAC,KAAK,SACvB,oBAACC;MAEO;MACN,OAAO;MACP,cAAY;MACZ,OAAO,MAAM;MACb,WAAW,UACT,UAAU,cAA6C;OAAC,GAAG;QAAW,OAAO,MAAM,OAAO;OAAM,EAAE;QAN/F,KAQL,CACF,EACF,qBAAC;MAAI,WAAW,OAAO;MAAQ,OAAO,EAAC,gBAAgB,YAAW;iBAChE,oBAACD;OAAO,MAAK;OAAS,SAAQ;OAAO,UAAU;OAAY,eAAqB,SAAS,KAAK;iBAC3F,EAAE,8BAA8B;QAC1B,EACT,oBAACA;OAAO,MAAK;OAAS,SAAS;OAAY,UAAU;iBAClD,EAAE,gCAAgC;QAC5B;OACL;MACD,GACL,WAAW,SAAS,IACtB,oBAAC,cACC,OAAO,WAAW,KAAK,CAAC,MAAM,YAA+B;KAAC,OAAO;KAAM,OAAO,UAAU,MAAM;KAAC,EAAE,GACrG,GAEF,oBAAC;KAAK,WAAW,OAAO;eAAW,EAAE,oCAAoC;MAAQ;KAE7E;GACR,oBAAC;IAAM,OAAO,EAAE,6BAA6B;cAC3C,oBAAC;KACC,OAAO,EAAE,6BAA6B;KACtC,WAAW,EAAE,gCAAgC;KAC7C,OAAO,OAAO,KAAK,WAA4B;MAAC,IAAI,MAAM;MAAI,OAAO,MAAM;MAAK,EAAE;MAClF;KACI;;GACK;;AAInB,8BAAe"}
1
+ {"version":3,"file":"BaseUserDetails.js","names":["BaseUserDetails: FC<BaseUserDetailsProps>","useTranslation","styles: Record<string, string>","useAdminPartStyles","isWritable: boolean","AdminSection","Spinner","Alert","displayName: string","attributes: [string, unknown][]","editableAttributes: [string, string][]","Button","TextField"],"sources":["../../../../src/components/admin/UserDetails/BaseUserDetails.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AccountLifecycleAction,\n resolveManagedIdentityName,\n ManagedUser,\n MembershipGroup,\n Preferences,\n} from '@thunderid/browser';\nimport {ChangeEvent, FC, ReactElement, ReactNode, useState} from 'react';\nimport useTranslation from '../../../hooks/useTranslation';\nimport Alert from '../../primitives/Alert/Alert';\nimport {Avatar} from '../../primitives/Avatar/Avatar';\nimport Button from '../../primitives/Button/Button';\nimport Spinner from '../../primitives/Spinner/Spinner';\nimport TextField from '../../primitives/TextField/TextField';\nimport {AccessStatusBadge, AccountLifecycleButtons} from '../shared/AccountLifecycleParts';\nimport {ChipList, ConfirmButton, DetailList, Panel} from '../shared/AdminParts';\nimport AdminSection from '../shared/AdminSection';\nimport useAdminPartStyles from '../shared/useAdminPartStyles';\n\n/**\n * What {@link BaseUserDetails} hands its `children` render function, so the user can be shown with any UI.\n */\nexport interface BaseUserDetailsRenderProps {\n /**\n * The user's attributes, without the hidden ones.\n */\n attributes: Record<string, unknown>;\n /**\n * The user's display name, or an empty string while there is no user.\n */\n displayName: string;\n error: string | null;\n groups: MembershipGroup[];\n isDeleting: boolean;\n isLoading: boolean;\n isRunningLifecycleAction: boolean;\n isUpdating: boolean;\n /**\n * The account lifecycle actions offered for the user. Empty when none is allowed.\n */\n lifecycleActions: AccountLifecycleAction[];\n /**\n * Deletes the user. Undefined when deletion is not allowed, including for read-only users.\n */\n onDelete?: () => void;\n /**\n * Runs an account lifecycle action, with the operator's note on a suspension. Undefined when none is\n * allowed.\n */\n onLifecycleAction?: (action: AccountLifecycleAction, operatorNote?: string) => void;\n /**\n * Saves the given attributes. Undefined when editing is not allowed, including for read-only users.\n */\n onUpdate?: (attributes: Record<string, unknown>) => void;\n user?: ManagedUser;\n}\n\nexport interface BaseUserDetailsProps {\n /**\n * Controls rendered next to the user's name.\n */\n actions?: ReactNode;\n /**\n * Renders custom UI in place of the default view. Receives the user, their visible attributes and\n * groups, the loading and error state, and the delete and update handlers.\n */\n children?: (props: BaseUserDetailsRenderProps) => ReactNode;\n className?: string;\n error?: string | null;\n /**\n * The groups the user belongs to.\n */\n groups?: MembershipGroup[];\n /**\n * Attribute names that are never shown, such as credentials.\n * @default ['password']\n */\n hiddenAttributes?: string[];\n isDeleting?: boolean;\n isLoading?: boolean;\n isRunningLifecycleAction?: boolean;\n isUpdating?: boolean;\n /**\n * The account lifecycle actions to offer (suspend, unsuspend, unlock), shown only with\n * `onLifecycleAction`. They change only runtime state, so they apply to read-only users too.\n */\n lifecycleActions?: AccountLifecycleAction[];\n /**\n * Deletes the user. The delete action is hidden when omitted.\n */\n onDelete?: () => void;\n /**\n * Runs an account lifecycle action. Suspending and unsuspending ask for confirmation first, and a\n * suspension takes an optional operator note.\n */\n onLifecycleAction?: (action: AccountLifecycleAction, operatorNote?: string) => void;\n /**\n * Saves edited attributes. Text attributes become editable only when set. Receives every\n * attribute, edited or not.\n */\n onUpdate?: (attributes: Record<string, unknown>) => void;\n preferences?: Preferences;\n user?: ManagedUser;\n}\n\nconst stringify = (value: unknown): string => {\n if (value === null || value === undefined) {\n return '';\n }\n\n return typeof value === 'object' ? JSON.stringify(value) : String(value as string | number | boolean);\n};\n\n/**\n * Shows a user's attributes, type, organization unit and groups. Holds no data of its own: pass the\n * user and handle deletion.\n */\nconst BaseUserDetails: FC<BaseUserDetailsProps> = ({\n actions,\n children,\n className,\n error = null,\n groups = [],\n hiddenAttributes = ['password'],\n isDeleting = false,\n isLoading = false,\n isRunningLifecycleAction = false,\n isUpdating = false,\n lifecycleActions = [],\n onDelete,\n onLifecycleAction,\n onUpdate,\n preferences,\n user,\n}: BaseUserDetailsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const styles: Record<string, string> = useAdminPartStyles();\n const [draft, setDraft] = useState<Record<string, string> | null>(null);\n\n if (children) {\n const isWritable: boolean = Boolean(user) && !user?.isReadOnly;\n\n return (\n <div className={className}>\n {children({\n attributes: Object.fromEntries(\n Object.entries(user?.attributes ?? {}).filter(\n ([name]: [string, unknown]) => !hiddenAttributes.includes(name),\n ),\n ),\n displayName: user ? resolveManagedIdentityName(user) : '',\n error,\n groups,\n isDeleting,\n isLoading,\n isRunningLifecycleAction,\n isUpdating,\n lifecycleActions: onLifecycleAction && user ? lifecycleActions : [],\n onDelete: isWritable ? onDelete : undefined,\n onLifecycleAction: user ? onLifecycleAction : undefined,\n onUpdate: isWritable ? onUpdate : undefined,\n user,\n })}\n </div>\n );\n }\n\n if (isLoading && !user) {\n return (\n <AdminSection block=\"user-details\" className={className} preferences={preferences}>\n <span className={styles['muted']} role=\"status\">\n <Spinner size=\"small\" /> {t('admin.common.loading')}\n </span>\n </AdminSection>\n );\n }\n\n if (!user) {\n return (\n <AdminSection block=\"user-details\" className={className} preferences={preferences}>\n <Alert variant=\"error\" role=\"alert\">\n {error ?? t('admin.common.errors.not_found')}\n </Alert>\n </AdminSection>\n );\n }\n\n const displayName: string = resolveManagedIdentityName(user);\n const attributes: [string, unknown][] = Object.entries(user.attributes ?? {}).filter(\n ([name]: [string, unknown]) => !hiddenAttributes.includes(name),\n );\n const editableAttributes: [string, string][] = attributes.filter(\n (entry: [string, unknown]): entry is [string, string] => typeof entry[1] === 'string',\n );\n\n return (\n <AdminSection\n block=\"user-details\"\n className={className}\n title={displayName}\n description={t('admin.users.details.subtitle', {type: user.type})}\n preferences={preferences}\n actions={\n <>\n {actions}\n {onLifecycleAction && (\n <AccountLifecycleButtons\n actions={lifecycleActions}\n collectsOperatorNote\n isRunning={isRunningLifecycleAction}\n name={displayName}\n onAction={onLifecycleAction}\n />\n )}\n {onDelete && !user.isReadOnly && (\n <ConfirmButton\n confirmText={t('admin.users.details.delete.confirm', {name: displayName})}\n isLoading={isDeleting}\n onConfirm={onDelete}\n >\n {t('admin.users.details.delete.action')}\n </ConfirmButton>\n )}\n </>\n }\n >\n {error && (\n <Alert variant=\"error\" role=\"alert\">\n {error}\n </Alert>\n )}\n <div className={styles['row']}>\n <Avatar name={displayName} size={48} />\n <span className={styles['muted']}>{user.ouHandle ?? user.ouId}</span>\n <AccessStatusBadge status={user.status?.value} />\n </div>\n <Panel\n title={t('admin.users.details.attributes')}\n actions={\n onUpdate && !user.isReadOnly && !draft && editableAttributes.length > 0 ? (\n <Button\n variant=\"outline\"\n size=\"small\"\n onClick={(): void => setDraft(Object.fromEntries(editableAttributes))}\n >\n {t('admin.users.details.edit.action')}\n </Button>\n ) : undefined\n }\n >\n {draft ? (\n <form\n className={styles['row']}\n style={{alignItems: 'stretch', flexDirection: 'column'}}\n onSubmit={(event): void => {\n event.preventDefault();\n onUpdate?.({...(user.attributes ?? {}), ...draft});\n setDraft(null);\n }}\n >\n {Object.keys(draft).map((name: string) => (\n <TextField\n key={name}\n name={name}\n label={name}\n aria-label={name}\n value={draft[name]}\n onChange={(event: ChangeEvent<HTMLInputElement>): void =>\n setDraft((previous: Record<string, string> | null) => ({...previous, [name]: event.target.value}))\n }\n />\n ))}\n <div className={styles['row']} style={{justifyContent: 'flex-end'}}>\n <Button type=\"button\" variant=\"text\" disabled={isUpdating} onClick={(): void => setDraft(null)}>\n {t('admin.common.actions.cancel')}\n </Button>\n <Button type=\"submit\" loading={isUpdating} disabled={isUpdating}>\n {t('admin.users.details.edit.save')}\n </Button>\n </div>\n </form>\n ) : attributes.length > 0 ? (\n <DetailList\n items={attributes.map(([name, value]: [string, unknown]) => ({label: name, value: stringify(value)}))}\n />\n ) : (\n <span className={styles['muted']}>{t('admin.users.details.no_attributes')}</span>\n )}\n </Panel>\n <Panel title={t('admin.users.details.groups')}>\n <ChipList\n label={t('admin.users.details.groups')}\n emptyText={t('admin.users.details.no_groups')}\n items={groups.map((group: MembershipGroup) => ({id: group.id, label: group.name}))}\n />\n </Panel>\n </AdminSection>\n );\n};\n\nexport default BaseUserDetails;\n"],"mappings":"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"}
@@ -1,6 +1,13 @@
1
+ import { AdministrationMode } from '@thunderid/browser';
1
2
  import { FC } from 'react';
2
3
  import { BaseUserDetailsProps } from './BaseUserDetails';
3
- export type UserDetailsProps = Omit<BaseUserDetailsProps, 'error' | 'groups' | 'isDeleting' | 'isLoading' | 'isUpdating' | 'onDelete' | 'onUpdate' | 'user'> & {
4
+ export type UserDetailsProps = Omit<BaseUserDetailsProps, 'error' | 'groups' | 'isDeleting' | 'isLoading' | 'isRunningLifecycleAction' | 'isUpdating' | 'lifecycleActions' | 'onDelete' | 'onLifecycleAction' | 'onUpdate' | 'user'> & {
5
+ /**
6
+ * How the delete runs. `flow` runs the user deletion flow the server configures, which revokes the
7
+ * user's grants and ends their sessions before the record goes. It needs `baseUrl`.
8
+ * @default 'native'
9
+ */
10
+ administrationMode?: AdministrationMode;
4
11
  /**
5
12
  * Whether the user can be deleted from this view.
6
13
  * @default false
@@ -11,6 +18,13 @@ export type UserDetailsProps = Omit<BaseUserDetailsProps, 'error' | 'groups' | '
11
18
  * @default false
12
19
  */
13
20
  editable?: boolean;
21
+ /**
22
+ * Whether the user can be suspended, unsuspended and unlocked from this view. Each action runs
23
+ * through its administration flow and is offered only when the server configures that flow. Reading
24
+ * the configured flows needs a token that may read the server configuration.
25
+ * @default false
26
+ */
27
+ lifecycleActions?: boolean;
14
28
  /**
15
29
  * Called after the user has been deleted.
16
30
  */
@@ -1 +1 @@
1
- {"version":3,"file":"UserDetails.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/UserDetails/UserDetails.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,EAAE,EAAe,MAAM,OAAO,CAAC;AACvC,OAAwB,EAAC,oBAAoB,EAAC,MAAM,mBAAmB,CAAC;AAWxE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,oBAAoB,EACpB,OAAO,GAAG,QAAQ,GAAG,YAAY,GAAG,WAAW,GAAG,YAAY,GAAG,UAAU,GAAG,UAAU,GAAG,MAAM,CAClG,GAAG;IACF;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAqDrC,CAAC;AAEF,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"UserDetails.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/UserDetails/UserDetails.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,kBAAkB,EAWnB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAe,MAAM,OAAO,CAAC;AACvC,OAAwB,EAAC,oBAAoB,EAAC,MAAM,mBAAmB,CAAC;AAYxE,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,oBAAoB,EAClB,OAAO,GACP,QAAQ,GACR,YAAY,GACZ,WAAW,GACX,0BAA0B,GAC1B,YAAY,GACZ,kBAAkB,GAClB,UAAU,GACV,mBAAmB,GACnB,UAAU,GACV,MAAM,CACT,GAAG;IACF;;;;OAIG;IACH,kBAAkB,CAAC,EAAE,kBAAkB,CAAC;IACxC;;;OAGG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF;;;;;;;GAOG;AACH,QAAA,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAoErC,CAAC;AAEF,eAAe,WAAW,CAAC"}
@@ -3,6 +3,7 @@ import useManagementEndpoint_default from "../../../hooks/management/useManageme
3
3
  import useResourceQuery_default from "../../../hooks/management/useResourceQuery.js";
4
4
  import useResourceMutation_default from "../../../hooks/management/useResourceMutation.js";
5
5
  import BaseUserDetails_default from "./BaseUserDetails.js";
6
+ import useAccountLifecycle_default from "../../../hooks/management/useAccountLifecycle.js";
6
7
  import { GroupQueryKeys, UserQueryKeys, deleteUser, getUser, getUserGroups, toManagementErrorMessage, updateUser } from "@thunderid/browser";
7
8
  import { jsx } from "react/jsx-runtime";
8
9
 
@@ -15,7 +16,7 @@ import { jsx } from "react/jsx-runtime";
15
16
  * <UserDetails userId={userId} deletable onDeleted={() => navigate('/users')} />
16
17
  * ```
17
18
  */
18
- const UserDetails = ({ deletable = false, editable = false, onDeleted, preferences, userId,...rest }) => {
19
+ const UserDetails = ({ administrationMode, deletable = false, editable = false, lifecycleActions = false, onDeleted, preferences, userId,...rest }) => {
19
20
  const { t } = useTranslation_default(preferences?.i18n);
20
21
  const endpoint = useManagementEndpoint_default("users");
21
22
  const user = useResourceQuery_default([UserQueryKeys.USER, userId], (fetcher) => getUser({
@@ -36,6 +37,7 @@ const UserDetails = ({ deletable = false, editable = false, onDeleted, preferenc
36
37
  }), { enabled: Boolean(userId) });
37
38
  const deletion = useResourceMutation_default((id, fetcher) => deleteUser({
38
39
  ...endpoint,
40
+ administrationMode,
39
41
  fetcher,
40
42
  userId: id
41
43
  }), () => [[UserQueryKeys.USERS]], { onSuccess: onDeleted });
@@ -45,13 +47,23 @@ const UserDetails = ({ deletable = false, editable = false, onDeleted, preferenc
45
47
  payload: data,
46
48
  userId: id
47
49
  }), ({ userId: id }) => [[UserQueryKeys.USER, id], [UserQueryKeys.USERS]]);
50
+ const lifecycle = useAccountLifecycle_default({
51
+ enabled: lifecycleActions,
52
+ invalidatedKeys: [[UserQueryKeys.USER, userId], [UserQueryKeys.USERS]],
53
+ kind: "user",
54
+ status: user.data?.status?.value,
55
+ subject: userId
56
+ });
48
57
  return /* @__PURE__ */ jsx(BaseUserDetails_default, {
49
58
  user: user.data,
50
59
  groups: groups.data?.groups,
51
60
  isLoading: user.isLoading,
52
- error: toManagementErrorMessage(t, user.error, "admin.users.details.error") ?? toManagementErrorMessage(t, deletion.error, "admin.users.details.delete.error") ?? toManagementErrorMessage(t, update.error, "admin.users.details.edit.error"),
61
+ error: toManagementErrorMessage(t, user.error, "admin.users.details.error") ?? toManagementErrorMessage(t, deletion.error, "admin.users.details.delete.error") ?? toManagementErrorMessage(t, update.error, "admin.users.details.edit.error") ?? toManagementErrorMessage(t, lifecycle.error, "admin.common.lifecycle.error") ?? toManagementErrorMessage(t, lifecycle.loadError, "admin.common.lifecycle.load_error"),
53
62
  isDeleting: deletion.isLoading,
54
63
  onDelete: deletable ? () => void deletion.mutate(userId) : void 0,
64
+ lifecycleActions: lifecycle.actions,
65
+ isRunningLifecycleAction: lifecycle.isRunning,
66
+ onLifecycleAction: lifecycleActions ? lifecycle.run : void 0,
55
67
  isUpdating: update.isLoading,
56
68
  onUpdate: editable && user.data ? (attributes) => void update.mutate({
57
69
  data: {
@@ -1 +1 @@
1
- {"version":3,"file":"UserDetails.js","names":["UserDetails: FC<UserDetailsProps>","useTranslation","endpoint: ManagementEndpoint","useManagementEndpoint","user: ResourceQueryResult<ManagedUser>","useResourceQuery","groups: ResourceQueryResult<MembershipGroupListResponse>","deletion: ResourceMutationResult<void, string>","useResourceMutation","update: ResourceMutationResult<ManagedUser, UpdateUserVariables>","BaseUserDetails"],"sources":["../../../../src/components/admin/UserDetails/UserDetails.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n GroupQueryKeys,\n ManagedUser,\n MembershipGroupListResponse,\n UpdateManagedUserRequest,\n UserQueryKeys,\n deleteUser,\n getUser,\n getUserGroups,\n toManagementErrorMessage,\n updateUser,\n} from '@thunderid/browser';\nimport {FC, ReactElement} from 'react';\nimport BaseUserDetails, {BaseUserDetailsProps} from './BaseUserDetails';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\n\ninterface UpdateUserVariables {\n data: UpdateManagedUserRequest;\n userId: string;\n}\n\nexport type UserDetailsProps = Omit<\n BaseUserDetailsProps,\n 'error' | 'groups' | 'isDeleting' | 'isLoading' | 'isUpdating' | 'onDelete' | 'onUpdate' | 'user'\n> & {\n /**\n * Whether the user can be deleted from this view.\n * @default false\n */\n deletable?: boolean;\n /**\n * Whether the user's text attributes can be edited from this view.\n * @default false\n */\n editable?: boolean;\n /**\n * Called after the user has been deleted.\n */\n onDeleted?: () => void;\n userId: string;\n};\n\n/**\n * Shows a user and the groups they belong to, and optionally lets the signed-in user delete them.\n *\n * @example\n * ```tsx\n * <UserDetails userId={userId} deletable onDeleted={() => navigate('/users')} />\n * ```\n */\nconst UserDetails: FC<UserDetailsProps> = ({\n deletable = false,\n editable = false,\n onDeleted,\n preferences,\n userId,\n ...rest\n}: UserDetailsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const endpoint: ManagementEndpoint = useManagementEndpoint('users');\n const user: ResourceQueryResult<ManagedUser> = useResourceQuery(\n [UserQueryKeys.USER, userId],\n (fetcher) => getUser({...endpoint, fetcher, userId}),\n {enabled: Boolean(userId)},\n );\n const groups: ResourceQueryResult<MembershipGroupListResponse> = useResourceQuery(\n [GroupQueryKeys.MEMBERSHIPS, 'user', userId, {limit: 100}],\n (fetcher) => getUserGroups({...endpoint, fetcher, limit: 100, userId}),\n {enabled: Boolean(userId)},\n );\n const deletion: ResourceMutationResult<void, string> = useResourceMutation(\n (id: string, fetcher) => deleteUser({...endpoint, fetcher, userId: id}),\n () => [[UserQueryKeys.USERS]],\n {onSuccess: onDeleted},\n );\n const update: ResourceMutationResult<ManagedUser, UpdateUserVariables> = useResourceMutation(\n ({data, userId: id}: UpdateUserVariables, fetcher) => updateUser({...endpoint, fetcher, payload: data, userId: id}),\n ({userId: id}: UpdateUserVariables) => [[UserQueryKeys.USER, id], [UserQueryKeys.USERS]],\n );\n\n return (\n <BaseUserDetails\n user={user.data}\n groups={groups.data?.groups}\n isLoading={user.isLoading}\n error={\n toManagementErrorMessage(t, user.error, 'admin.users.details.error') ??\n toManagementErrorMessage(t, deletion.error, 'admin.users.details.delete.error') ??\n toManagementErrorMessage(t, update.error, 'admin.users.details.edit.error')\n }\n isDeleting={deletion.isLoading}\n onDelete={deletable ? (): void => void deletion.mutate(userId) : undefined}\n isUpdating={update.isLoading}\n onUpdate={\n editable && user.data\n ? (attributes: Record<string, unknown>): void =>\n void update.mutate({data: {attributes, ouId: user.data!.ouId, type: user.data!.type}, userId})\n : undefined\n }\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default UserDetails;\n"],"mappings":";;;;;;;;;;;;;;;;;AAwDA,MAAMA,eAAqC,EACzC,YAAY,OACZ,WAAW,OACX,WACA,aACA,OACA,GAAG,WACiC;CACpC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAMC,WAA+BC,8BAAsB,QAAQ;CACnE,MAAMC,OAAyCC,yBAC7C,CAAC,cAAc,MAAM,OAAO,GAC3B,YAAY,QAAQ;EAAC,GAAG;EAAU;EAAS;EAAO,CAAC,EACpD,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAMC,SAA2DD,yBAC/D;EAAC,eAAe;EAAa;EAAQ;EAAQ,EAAC,OAAO,KAAI;EAAC,GACzD,YAAY,cAAc;EAAC,GAAG;EAAU;EAAS,OAAO;EAAK;EAAO,CAAC,EACtE,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAME,WAAiDC,6BACpD,IAAY,YAAY,WAAW;EAAC,GAAG;EAAU;EAAS,QAAQ;EAAG,CAAC,QACjE,CAAC,CAAC,cAAc,MAAM,CAAC,EAC7B,EAAC,WAAW,WAAU,CACvB;CACD,MAAMC,SAAmED,6BACtE,EAAC,MAAM,QAAQ,MAA0B,YAAY,WAAW;EAAC,GAAG;EAAU;EAAS,SAAS;EAAM,QAAQ;EAAG,CAAC,GAClH,EAAC,QAAQ,SAA6B,CAAC,CAAC,cAAc,MAAM,GAAG,EAAE,CAAC,cAAc,MAAM,CAAC,CACzF;AAED,QACE,oBAACE;EACC,MAAM,KAAK;EACX,QAAQ,OAAO,MAAM;EACrB,WAAW,KAAK;EAChB,OACE,yBAAyB,GAAG,KAAK,OAAO,4BAA4B,IACpE,yBAAyB,GAAG,SAAS,OAAO,mCAAmC,IAC/E,yBAAyB,GAAG,OAAO,OAAO,iCAAiC;EAE7E,YAAY,SAAS;EACrB,UAAU,kBAAwB,KAAK,SAAS,OAAO,OAAO,GAAG;EACjE,YAAY,OAAO;EACnB,UACE,YAAY,KAAK,QACZ,eACC,KAAK,OAAO,OAAO;GAAC,MAAM;IAAC;IAAY,MAAM,KAAK,KAAM;IAAM,MAAM,KAAK,KAAM;IAAK;GAAE;GAAO,CAAC,GAChG;EAEO;EACb,GAAI;GACJ;;AAIN,0BAAe"}
1
+ {"version":3,"file":"UserDetails.js","names":["UserDetails: FC<UserDetailsProps>","useTranslation","endpoint: ManagementEndpoint","useManagementEndpoint","user: ResourceQueryResult<ManagedUser>","useResourceQuery","groups: ResourceQueryResult<MembershipGroupListResponse>","deletion: ResourceMutationResult<void, string>","useResourceMutation","update: ResourceMutationResult<ManagedUser, UpdateUserVariables>","lifecycle: AccountLifecycleState","useAccountLifecycle","BaseUserDetails"],"sources":["../../../../src/components/admin/UserDetails/UserDetails.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n AdministrationMode,\n GroupQueryKeys,\n ManagedUser,\n MembershipGroupListResponse,\n UpdateManagedUserRequest,\n UserQueryKeys,\n deleteUser,\n getUser,\n getUserGroups,\n toManagementErrorMessage,\n updateUser,\n} from '@thunderid/browser';\nimport {FC, ReactElement} from 'react';\nimport BaseUserDetails, {BaseUserDetailsProps} from './BaseUserDetails';\nimport useAccountLifecycle, {AccountLifecycleState} from '../../../hooks/management/useAccountLifecycle';\nimport useManagementEndpoint, {ManagementEndpoint} from '../../../hooks/management/useManagementEndpoint';\nimport useResourceMutation, {ResourceMutationResult} from '../../../hooks/management/useResourceMutation';\nimport useResourceQuery, {ResourceQueryResult} from '../../../hooks/management/useResourceQuery';\nimport useTranslation from '../../../hooks/useTranslation';\n\ninterface UpdateUserVariables {\n data: UpdateManagedUserRequest;\n userId: string;\n}\n\nexport type UserDetailsProps = Omit<\n BaseUserDetailsProps,\n | 'error'\n | 'groups'\n | 'isDeleting'\n | 'isLoading'\n | 'isRunningLifecycleAction'\n | 'isUpdating'\n | 'lifecycleActions'\n | 'onDelete'\n | 'onLifecycleAction'\n | 'onUpdate'\n | 'user'\n> & {\n /**\n * How the delete runs. `flow` runs the user deletion flow the server configures, which revokes the\n * user's grants and ends their sessions before the record goes. It needs `baseUrl`.\n * @default 'native'\n */\n administrationMode?: AdministrationMode;\n /**\n * Whether the user can be deleted from this view.\n * @default false\n */\n deletable?: boolean;\n /**\n * Whether the user's text attributes can be edited from this view.\n * @default false\n */\n editable?: boolean;\n /**\n * Whether the user can be suspended, unsuspended and unlocked from this view. Each action runs\n * through its administration flow and is offered only when the server configures that flow. Reading\n * the configured flows needs a token that may read the server configuration.\n * @default false\n */\n lifecycleActions?: boolean;\n /**\n * Called after the user has been deleted.\n */\n onDeleted?: () => void;\n userId: string;\n};\n\n/**\n * Shows a user and the groups they belong to, and optionally lets the signed-in user delete them.\n *\n * @example\n * ```tsx\n * <UserDetails userId={userId} deletable onDeleted={() => navigate('/users')} />\n * ```\n */\nconst UserDetails: FC<UserDetailsProps> = ({\n administrationMode,\n deletable = false,\n editable = false,\n lifecycleActions = false,\n onDeleted,\n preferences,\n userId,\n ...rest\n}: UserDetailsProps): ReactElement => {\n const {t} = useTranslation(preferences?.i18n);\n const endpoint: ManagementEndpoint = useManagementEndpoint('users');\n const user: ResourceQueryResult<ManagedUser> = useResourceQuery(\n [UserQueryKeys.USER, userId],\n (fetcher) => getUser({...endpoint, fetcher, userId}),\n {enabled: Boolean(userId)},\n );\n const groups: ResourceQueryResult<MembershipGroupListResponse> = useResourceQuery(\n [GroupQueryKeys.MEMBERSHIPS, 'user', userId, {limit: 100}],\n (fetcher) => getUserGroups({...endpoint, fetcher, limit: 100, userId}),\n {enabled: Boolean(userId)},\n );\n const deletion: ResourceMutationResult<void, string> = useResourceMutation(\n (id: string, fetcher) => deleteUser({...endpoint, administrationMode, fetcher, userId: id}),\n () => [[UserQueryKeys.USERS]],\n {onSuccess: onDeleted},\n );\n const update: ResourceMutationResult<ManagedUser, UpdateUserVariables> = useResourceMutation(\n ({data, userId: id}: UpdateUserVariables, fetcher) => updateUser({...endpoint, fetcher, payload: data, userId: id}),\n ({userId: id}: UpdateUserVariables) => [[UserQueryKeys.USER, id], [UserQueryKeys.USERS]],\n );\n\n const lifecycle: AccountLifecycleState = useAccountLifecycle({\n enabled: lifecycleActions,\n invalidatedKeys: [[UserQueryKeys.USER, userId], [UserQueryKeys.USERS]],\n kind: 'user',\n status: user.data?.status?.value,\n subject: userId,\n });\n\n return (\n <BaseUserDetails\n user={user.data}\n groups={groups.data?.groups}\n isLoading={user.isLoading}\n error={\n toManagementErrorMessage(t, user.error, 'admin.users.details.error') ??\n toManagementErrorMessage(t, deletion.error, 'admin.users.details.delete.error') ??\n toManagementErrorMessage(t, update.error, 'admin.users.details.edit.error') ??\n toManagementErrorMessage(t, lifecycle.error, 'admin.common.lifecycle.error') ??\n toManagementErrorMessage(t, lifecycle.loadError, 'admin.common.lifecycle.load_error')\n }\n isDeleting={deletion.isLoading}\n onDelete={deletable ? (): void => void deletion.mutate(userId) : undefined}\n lifecycleActions={lifecycle.actions}\n isRunningLifecycleAction={lifecycle.isRunning}\n onLifecycleAction={lifecycleActions ? lifecycle.run : undefined}\n isUpdating={update.isLoading}\n onUpdate={\n editable && user.data\n ? (attributes: Record<string, unknown>): void =>\n void update.mutate({data: {attributes, ouId: user.data!.ouId, type: user.data!.type}, userId})\n : undefined\n }\n preferences={preferences}\n {...rest}\n />\n );\n};\n\nexport default UserDetails;\n"],"mappings":";;;;;;;;;;;;;;;;;;AAiFA,MAAMA,eAAqC,EACzC,oBACA,YAAY,OACZ,WAAW,OACX,mBAAmB,OACnB,WACA,aACA,OACA,GAAG,WACiC;CACpC,MAAM,EAAC,MAAKC,uBAAe,aAAa,KAAK;CAC7C,MAAMC,WAA+BC,8BAAsB,QAAQ;CACnE,MAAMC,OAAyCC,yBAC7C,CAAC,cAAc,MAAM,OAAO,GAC3B,YAAY,QAAQ;EAAC,GAAG;EAAU;EAAS;EAAO,CAAC,EACpD,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAMC,SAA2DD,yBAC/D;EAAC,eAAe;EAAa;EAAQ;EAAQ,EAAC,OAAO,KAAI;EAAC,GACzD,YAAY,cAAc;EAAC,GAAG;EAAU;EAAS,OAAO;EAAK;EAAO,CAAC,EACtE,EAAC,SAAS,QAAQ,OAAO,EAAC,CAC3B;CACD,MAAME,WAAiDC,6BACpD,IAAY,YAAY,WAAW;EAAC,GAAG;EAAU;EAAoB;EAAS,QAAQ;EAAG,CAAC,QACrF,CAAC,CAAC,cAAc,MAAM,CAAC,EAC7B,EAAC,WAAW,WAAU,CACvB;CACD,MAAMC,SAAmED,6BACtE,EAAC,MAAM,QAAQ,MAA0B,YAAY,WAAW;EAAC,GAAG;EAAU;EAAS,SAAS;EAAM,QAAQ;EAAG,CAAC,GAClH,EAAC,QAAQ,SAA6B,CAAC,CAAC,cAAc,MAAM,GAAG,EAAE,CAAC,cAAc,MAAM,CAAC,CACzF;CAED,MAAME,YAAmCC,4BAAoB;EAC3D,SAAS;EACT,iBAAiB,CAAC,CAAC,cAAc,MAAM,OAAO,EAAE,CAAC,cAAc,MAAM,CAAC;EACtE,MAAM;EACN,QAAQ,KAAK,MAAM,QAAQ;EAC3B,SAAS;EACV,CAAC;AAEF,QACE,oBAACC;EACC,MAAM,KAAK;EACX,QAAQ,OAAO,MAAM;EACrB,WAAW,KAAK;EAChB,OACE,yBAAyB,GAAG,KAAK,OAAO,4BAA4B,IACpE,yBAAyB,GAAG,SAAS,OAAO,mCAAmC,IAC/E,yBAAyB,GAAG,OAAO,OAAO,iCAAiC,IAC3E,yBAAyB,GAAG,UAAU,OAAO,+BAA+B,IAC5E,yBAAyB,GAAG,UAAU,WAAW,oCAAoC;EAEvF,YAAY,SAAS;EACrB,UAAU,kBAAwB,KAAK,SAAS,OAAO,OAAO,GAAG;EACjE,kBAAkB,UAAU;EAC5B,0BAA0B,UAAU;EACpC,mBAAmB,mBAAmB,UAAU,MAAM;EACtD,YAAY,OAAO;EACnB,UACE,YAAY,KAAK,QACZ,eACC,KAAK,OAAO,OAAO;GAAC,MAAM;IAAC;IAAY,MAAM,KAAK,KAAM;IAAM,MAAM,KAAK,KAAM;IAAK;GAAE;GAAO,CAAC,GAChG;EAEO;EACb,GAAI;GACJ;;AAIN,0BAAe"}
@@ -0,0 +1,32 @@
1
+ import { AccessStatusValue, AccountLifecycleAction } from '@thunderid/browser';
2
+ import { FC } from 'react';
3
+ export interface AccessStatusBadgeProps {
4
+ status?: AccessStatusValue;
5
+ }
6
+ /**
7
+ * The translated access status of an identity, or its raw value when the status is one this SDK does
8
+ * not know. Renders nothing when the server reports no status.
9
+ */
10
+ export declare const AccessStatusBadge: FC<AccessStatusBadgeProps>;
11
+ export interface AccountLifecycleButtonsProps {
12
+ /**
13
+ * The actions to offer, most expected first.
14
+ */
15
+ actions: AccountLifecycleAction[];
16
+ /**
17
+ * Whether a suspension asks for an optional operator note. The note is recorded by the flow.
18
+ */
19
+ collectsOperatorNote?: boolean;
20
+ isRunning?: boolean;
21
+ /**
22
+ * Name of the identity, already resolved, used in the confirmation question.
23
+ */
24
+ name: string;
25
+ onAction: (action: AccountLifecycleAction, operatorNote?: string) => void;
26
+ }
27
+ /**
28
+ * One button per account lifecycle action. Suspending and unsuspending ask for confirmation inline;
29
+ * unlocking is remedial and runs straight away.
30
+ */
31
+ export declare const AccountLifecycleButtons: FC<AccountLifecycleButtonsProps>;
32
+ //# sourceMappingURL=AccountLifecycleParts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AccountLifecycleParts.d.ts","sourceRoot":"","sources":["../../../../src/components/admin/shared/AccountLifecycleParts.tsx"],"names":[],"mappings":"AAGA,OAAO,EAAC,iBAAiB,EAAE,sBAAsB,EAAC,MAAM,oBAAoB,CAAC;AAC7E,OAAO,EAAc,EAAE,EAAyB,MAAM,OAAO,CAAC;AAW9D,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAED;;;GAGG;AACH,eAAO,MAAM,iBAAiB,EAAE,EAAE,CAAC,sBAAsB,CAiBxD,CAAC;AAEF,MAAM,WAAW,4BAA4B;IAC3C;;OAEG;IACH,OAAO,EAAE,sBAAsB,EAAE,CAAC;IAClC;;OAEG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,CAAC,MAAM,EAAE,sBAAsB,EAAE,YAAY,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC3E;AAED;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,EAAE,CAAC,4BAA4B,CAkFpE,CAAC"}
@@ -0,0 +1,101 @@
1
+ import useTranslation_default from "../../../hooks/useTranslation.js";
2
+ import Button_default from "../../primitives/Button/Button.js";
3
+ import TextField_default from "../../primitives/TextField/TextField.js";
4
+ import useAdminPartStyles_default from "./useAdminPartStyles.js";
5
+ import { useState } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+
8
+ //#region src/components/admin/shared/AccountLifecycleParts.tsx
9
+ /**
10
+ * Matches the server's limit on an operator note.
11
+ */
12
+ const OPERATOR_NOTE_MAX_LENGTH = 100;
13
+ /**
14
+ * The translated access status of an identity, or its raw value when the status is one this SDK does
15
+ * not know. Renders nothing when the server reports no status.
16
+ */
17
+ const AccessStatusBadge = ({ status }) => {
18
+ const styles = useAdminPartStyles_default();
19
+ const { t } = useTranslation_default();
20
+ if (!status) return null;
21
+ const labels = {
22
+ ACTIVE: t("admin.common.status.active"),
23
+ LOCKED: t("admin.common.status.locked"),
24
+ SUSPENDED: t("admin.common.status.suspended")
25
+ };
26
+ return /* @__PURE__ */ jsx("span", {
27
+ className: styles["chip"],
28
+ children: labels[status] ?? status
29
+ });
30
+ };
31
+ /**
32
+ * One button per account lifecycle action. Suspending and unsuspending ask for confirmation inline;
33
+ * unlocking is remedial and runs straight away.
34
+ */
35
+ const AccountLifecycleButtons = ({ actions, collectsOperatorNote = false, isRunning = false, name, onAction }) => {
36
+ const styles = useAdminPartStyles_default();
37
+ const { t } = useTranslation_default();
38
+ const [confirming, setConfirming] = useState(null);
39
+ const [operatorNote, setOperatorNote] = useState("");
40
+ if (actions.length === 0) return null;
41
+ const labels = {
42
+ suspend: t("admin.common.lifecycle.suspend"),
43
+ unlock: t("admin.common.lifecycle.unlock"),
44
+ unsuspend: t("admin.common.lifecycle.unsuspend")
45
+ };
46
+ if (confirming && actions.includes(confirming)) {
47
+ const question = confirming === "suspend" ? t("admin.common.lifecycle.suspend_confirm", { name }) : t("admin.common.lifecycle.unsuspend_confirm", { name });
48
+ const withNote = collectsOperatorNote && confirming === "suspend";
49
+ const close = () => {
50
+ setConfirming(null);
51
+ setOperatorNote("");
52
+ };
53
+ return /* @__PURE__ */ jsxs("span", {
54
+ className: styles["confirm"],
55
+ role: "group",
56
+ "aria-label": question,
57
+ children: [
58
+ /* @__PURE__ */ jsx("span", { children: question }),
59
+ withNote && /* @__PURE__ */ jsx(TextField_default, {
60
+ name: "operatorNote",
61
+ label: t("admin.common.lifecycle.operator_note"),
62
+ "aria-label": t("admin.common.lifecycle.operator_note"),
63
+ value: operatorNote,
64
+ maxLength: OPERATOR_NOTE_MAX_LENGTH,
65
+ disabled: isRunning,
66
+ onChange: (event) => setOperatorNote(event.target.value)
67
+ }),
68
+ /* @__PURE__ */ jsx(Button_default, {
69
+ variant: "text",
70
+ size: "small",
71
+ disabled: isRunning,
72
+ onClick: close,
73
+ children: t("admin.common.actions.cancel")
74
+ }),
75
+ /* @__PURE__ */ jsx(Button_default, {
76
+ color: confirming === "suspend" ? "error" : "primary",
77
+ size: "small",
78
+ loading: isRunning,
79
+ disabled: isRunning,
80
+ onClick: () => {
81
+ onAction(confirming, withNote ? operatorNote.trim() || void 0 : void 0);
82
+ close();
83
+ },
84
+ children: labels[confirming]
85
+ })
86
+ ]
87
+ });
88
+ }
89
+ return /* @__PURE__ */ jsx(Fragment$1, { children: actions.map((action) => /* @__PURE__ */ jsx(Button_default, {
90
+ variant: "outline",
91
+ color: action === "suspend" ? "error" : "primary",
92
+ disabled: isRunning,
93
+ loading: isRunning && action === "unlock",
94
+ onClick: () => action === "unlock" ? onAction(action) : setConfirming(action),
95
+ children: labels[action]
96
+ }, action)) });
97
+ };
98
+
99
+ //#endregion
100
+ export { AccessStatusBadge, AccountLifecycleButtons };
101
+ //# sourceMappingURL=AccountLifecycleParts.js.map