@truedat/auth 8.14.3 → 8.14.5

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 (78) hide show
  1. package/package.json +4 -4
  2. package/src/groups/components/GroupCard.js +2 -2
  3. package/src/groups/components/GroupCards.js +129 -37
  4. package/src/groups/components/GroupForm.js +13 -7
  5. package/src/groups/components/GroupFormModal.js +8 -7
  6. package/src/groups/components/GroupInfoModal.js +6 -6
  7. package/src/groups/components/GroupMemberPicker.js +2 -4
  8. package/src/groups/components/GroupRouteModal.js +15 -10
  9. package/src/groups/components/__tests__/GroupCards.spec.js +94 -0
  10. package/src/groups/components/__tests__/GroupForm.spec.js +2 -2
  11. package/src/groups/components/__tests__/GroupModals.spec.js +42 -16
  12. package/src/groups/components/__tests__/__snapshots__/GroupCards.spec.js.snap +14 -0
  13. package/src/messages/en.js +6 -5
  14. package/src/messages/es.js +8 -7
  15. package/src/styles/Groups.less +96 -131
  16. package/src/styles/UserCard.less +416 -0
  17. package/src/styles/UserModals.less +831 -0
  18. package/src/styles/Users.less +313 -0
  19. package/src/users/components/AdminUserRoutes.js +7 -20
  20. package/src/users/components/EditUser.js +7 -34
  21. package/src/users/components/EditUserModal.js +150 -0
  22. package/src/users/components/NewUser.js +128 -17
  23. package/src/users/components/Password.js +11 -189
  24. package/src/users/components/UserAcls.js +117 -6
  25. package/src/users/components/UserActions.js +99 -34
  26. package/src/users/components/UserCard.js +171 -22
  27. package/src/users/components/UserCards.js +188 -35
  28. package/src/users/components/UserFormFields.js +206 -0
  29. package/src/users/components/UserGroupAclRow.js +8 -2
  30. package/src/users/components/UserGroupAcls.js +2 -2
  31. package/src/users/components/UserGroupLink.js +21 -0
  32. package/src/users/components/UserInfoModal.js +228 -0
  33. package/src/users/components/UserPasswordField.js +66 -0
  34. package/src/users/components/UserPasswordModal.js +210 -0
  35. package/src/users/components/UserRoutes.js +4 -13
  36. package/src/users/components/UserTabs.js +5 -2
  37. package/src/users/components/UserTypeSelector.js +109 -0
  38. package/src/users/components/UsersAndGroups.js +39 -5
  39. package/src/users/components/UsersTable.js +234 -0
  40. package/src/users/components/__tests__/EditUser.spec.js +84 -10
  41. package/src/users/components/__tests__/NewUser.spec.js +34 -4
  42. package/src/users/components/__tests__/Password.spec.js +53 -146
  43. package/src/users/components/__tests__/UserAcls.spec.js +62 -2
  44. package/src/users/components/__tests__/UserCard.spec.js +111 -5
  45. package/src/users/components/__tests__/UserCards.spec.js +149 -0
  46. package/src/users/components/__tests__/UserGroupAclRow.spec.js +19 -1
  47. package/src/users/components/__tests__/UserModals.spec.js +554 -0
  48. package/src/users/components/__tests__/UserRoutes.spec.js +8 -3
  49. package/src/users/components/__tests__/UserTypeSelector.spec.js +32 -0
  50. package/src/users/components/__tests__/UsersAndGroups.spec.js +17 -0
  51. package/src/users/components/__tests__/__snapshots__/UserCard.spec.js.snap +66 -37
  52. package/src/users/components/__tests__/__snapshots__/UserCards.spec.js.snap +375 -75
  53. package/src/users/components/__tests__/__snapshots__/UserGroupAclRow.spec.js.snap +7 -3
  54. package/src/users/components/__tests__/__snapshots__/UserRoutes.spec.js.snap +4 -13
  55. package/src/users/hooks/useSortedGroups.js +13 -0
  56. package/src/users/hooks/useUserModalOperation.js +55 -0
  57. package/src/users/reducers/__tests__/userRedirect.spec.js +14 -3
  58. package/src/users/reducers/index.js +1 -0
  59. package/src/users/reducers/userModalOperation.js +29 -0
  60. package/src/users/reducers/userRedirect.js +5 -4
  61. package/src/users/sagas/__tests__/updateUser.spec.js +19 -1
  62. package/src/users/sagas/createUser.js +12 -5
  63. package/src/users/sagas/deleteUser.js +12 -5
  64. package/src/users/sagas/updateUser.js +19 -8
  65. package/src/users/selectors/__tests__/filterUsers.spec.js +11 -0
  66. package/src/users/selectors/__tests__/getUserAcls.spec.js +1 -0
  67. package/src/users/selectors/__tests__/getUserGroupAcls.spec.js +2 -0
  68. package/src/users/selectors/filterUsers.js +9 -1
  69. package/src/users/selectors/getUserGroupAcls.js +1 -0
  70. package/src/users/components/User.js +0 -68
  71. package/src/users/components/UserPassword.js +0 -24
  72. package/src/users/components/__tests__/User.spec.js +0 -20
  73. package/src/users/components/__tests__/UserPassword.spec.js +0 -24
  74. package/src/users/components/__tests__/__snapshots__/EditUser.spec.js.snap +0 -281
  75. package/src/users/components/__tests__/__snapshots__/NewUser.spec.js.snap +0 -321
  76. package/src/users/components/__tests__/__snapshots__/Password.spec.js.snap +0 -128
  77. package/src/users/components/__tests__/__snapshots__/User.spec.js.snap +0 -129
  78. package/src/users/components/__tests__/__snapshots__/UserPassword.spec.js.snap +0 -124
@@ -1,30 +1,141 @@
1
+ import { useEffect } from "react";
1
2
  import PropTypes from "prop-types";
2
- import { Header, Container, Segment } from "semantic-ui-react";
3
- import { connect } from "react-redux";
3
+ import { Form } from "semantic-ui-react";
4
4
  import { useIntl } from "react-intl";
5
+ import { useForm, useWatch } from "react-hook-form";
6
+ import TrueModal from "@truedat/core/components/TrueModal";
5
7
  import { createUser } from "../routines";
6
- import UserBreadcrumbs from "./UserBreadcrumbs";
7
- import UserForm from "./UserForm";
8
+ import useUserModalOperation from "../hooks/useUserModalOperation";
9
+ import useSortedGroups from "../hooks/useSortedGroups";
10
+ import UserFormFields from "./UserFormFields";
11
+ import UserPasswordField from "./UserPasswordField";
8
12
 
9
- const NewUser = ({ createUser }) => {
13
+ const getDefaultValues = () => ({
14
+ full_name: "",
15
+ email: "",
16
+ user_name: "",
17
+ external_id: "",
18
+ role: "user",
19
+ groups: [],
20
+ password: "",
21
+ rep_password: "",
22
+ });
23
+
24
+ export const NewUser = ({ groups, onClose }) => {
10
25
  const { formatMessage } = useIntl();
26
+ const {
27
+ submit,
28
+ isSubmitting,
29
+ onClose: closeModal,
30
+ error,
31
+ } = useUserModalOperation(createUser, onClose);
32
+ const {
33
+ handleSubmit,
34
+ control,
35
+ trigger,
36
+ getValues,
37
+ formState: { errors, isValid },
38
+ } = useForm({
39
+ mode: "all",
40
+ defaultValues: getDefaultValues(),
41
+ });
42
+ const password = useWatch({ control, name: "password", defaultValue: "" });
43
+ useEffect(() => {
44
+ if (getValues("rep_password")) void trigger("rep_password");
45
+ }, [password, getValues, trigger]);
46
+
11
47
  return (
12
- <>
13
- <UserBreadcrumbs name={formatMessage({ id: "users.actions.create" })} />
14
- <Container text as={Segment}>
15
- <Header
16
- as="h2"
17
- icon="user"
18
- content={formatMessage({ id: "users.actions.create" })}
48
+ <TrueModal
49
+ open
50
+ title={formatMessage({ id: "users.actions.create" })}
51
+ icon="add user"
52
+ actions={[
53
+ {
54
+ key: "cancel",
55
+ content: formatMessage({ id: "actions.cancel" }),
56
+ secondary: true,
57
+ onClick: closeModal,
58
+ disabled: isSubmitting,
59
+ },
60
+ {
61
+ key: "save",
62
+ content: formatMessage({
63
+ id: "user.create.submit",
64
+ }),
65
+ primary: true,
66
+ type: "submit",
67
+ form: "new-user-form",
68
+ disabled: isSubmitting || !isValid,
69
+ loading: isSubmitting,
70
+ },
71
+ ]}
72
+ onClose={closeModal}
73
+ aria-busy={isSubmitting}
74
+ closeOnEscape={!isSubmitting}
75
+ closeOnDimmerClick={!isSubmitting}
76
+ banner={error ? <span role="alert">{error}</span> : null}
77
+ bannerType="danger"
78
+ size="small"
79
+ className="user-modal user-modal--create"
80
+ >
81
+ <Form id="new-user-form" noValidate onSubmit={handleSubmit(submit)}>
82
+ <UserFormFields
83
+ control={control}
84
+ errors={errors}
85
+ isSubmitting={isSubmitting}
86
+ groups={groups}
19
87
  />
20
- <UserForm onSubmit={createUser} />
21
- </Container>
22
- </>
88
+ <div className="um-section um-section--password">
89
+ <div className="um-section-header">
90
+ <div className="um-section-bar um-section-bar--password" />
91
+ <span className="um-section-label">
92
+ {formatMessage({ id: "user.password.header" })}
93
+ </span>
94
+ </div>
95
+ <div className="um-form-grid">
96
+ <UserPasswordField
97
+ control={control}
98
+ name="password"
99
+ label={formatMessage({ id: "user.form.password" })}
100
+ disabled={isSubmitting}
101
+ rules={{
102
+ required: formatMessage({ id: "user.password.required" }),
103
+ minLength: {
104
+ value: 6,
105
+ message: formatMessage(
106
+ { id: "user.password.min_length" },
107
+ { count: 6 },
108
+ ),
109
+ },
110
+ }}
111
+ />
112
+ <UserPasswordField
113
+ control={control}
114
+ name="rep_password"
115
+ label={formatMessage({ id: "user.form.password_confirmation" })}
116
+ disabled={isSubmitting}
117
+ rules={{
118
+ required: formatMessage({ id: "user.password.required" }),
119
+ validate: (value) =>
120
+ value === getValues("password") ||
121
+ formatMessage({ id: "user.password.mismatch" }),
122
+ }}
123
+ />
124
+ </div>
125
+ </div>
126
+ </Form>
127
+ </TrueModal>
23
128
  );
24
129
  };
25
130
 
26
131
  NewUser.propTypes = {
27
- createUser: PropTypes.func,
132
+ groups: PropTypes.array,
133
+ onClose: PropTypes.func.isRequired,
134
+ };
135
+
136
+ const NewUserWithGroups = (props) => {
137
+ const groups = useSortedGroups();
138
+ return <NewUser {...props} groups={groups} />;
28
139
  };
29
140
 
30
- export default connect(null, { createUser })(NewUser);
141
+ export default NewUserWithGroups;
@@ -1,193 +1,16 @@
1
- import _ from "lodash/fp";
2
1
  import PropTypes from "prop-types";
3
- import { Container, Header, Icon, Segment } from "semantic-ui-react";
4
- import { FormattedMessage, useIntl } from "react-intl";
5
2
  import { connect } from "react-redux";
6
- import { useForm, Controller } from "react-hook-form";
7
- import { Button, Form } from "semantic-ui-react";
8
- import HistoryBackButton from "@truedat/core/components/HistoryBackButton";
9
- import { updatePassword } from "../routines";
10
-
11
- const MIN_LENGTH = 6;
12
-
13
- export const Password = ({
14
- id,
15
- userName,
16
- authUserName,
17
- updatePassword,
18
- passwordUpdating,
19
- }) => {
20
- const { formatMessage } = useIntl();
21
- const {
22
- handleSubmit,
23
- control,
24
- formState: { errors, isValid },
25
- watch,
26
- } = useForm({
27
- mode: "all",
28
- defaultValues: {
29
- currentPassword: "",
30
- password: "",
31
- passwordConfirmation: "",
32
- },
33
- });
34
-
35
- const needsCurrentPassword = _.isEmpty(userName) || userName === authUserName;
36
-
37
- const onSubmit = ({ currentPassword, password }) => {
38
- const user = needsCurrentPassword
39
- ? { new_password: password, old_password: currentPassword }
40
- : { id: id, new_password: password };
41
- updatePassword({ user });
42
- };
43
-
44
- const passwordConfirmationValidator = (value) =>
45
- value === watch("password") ||
46
- formatMessage({ id: "user.password.must.match" });
3
+ import { useNavigate } from "react-router";
4
+ import { UserPasswordModal } from "./UserPasswordModal";
47
5
 
6
+ export const Password = ({ id, userName, authUserName }) => {
7
+ const navigate = useNavigate();
48
8
  return (
49
- <Container text as={Segment}>
50
- <Header as="h2">
51
- <Icon
52
- circular
53
- name={formatMessage({
54
- id: "user.password.header.icon",
55
- defaultMessage: "user",
56
- })}
57
- />
58
- <Header.Content>
59
- {_.isEmpty(userName) ? (
60
- <FormattedMessage id="user.password.header" />
61
- ) : (
62
- userName
63
- )}
64
- <Header.Subheader>
65
- <FormattedMessage id="user.password.change" />
66
- </Header.Subheader>
67
- </Header.Content>
68
- </Header>
69
- <Segment attached="bottom">
70
- <Form onSubmit={handleSubmit(onSubmit)}>
71
- {needsCurrentPassword ? (
72
- <Controller
73
- control={control}
74
- name="currentPassword"
75
- rules={{
76
- minLength: {
77
- value: MIN_LENGTH,
78
- message: formatMessage(
79
- { id: "form.validation.minLength" },
80
- {
81
- prop: formatMessage({ id: "user.password.current" }),
82
- value: MIN_LENGTH,
83
- },
84
- ),
85
- },
86
- required: formatMessage(
87
- { id: "form.validation.required" },
88
- { prop: formatMessage({ id: "user.password.current" }) },
89
- ),
90
- }}
91
- render={({ field: { onChange, onBlur, value } }) => (
92
- <Form.Input
93
- autoComplete="off"
94
- error={
95
- errors.currentPassword?.type === "minLength" &&
96
- errors.currentPassword.message
97
- }
98
- icon="lock"
99
- iconPosition="left"
100
- id="currentPassword"
101
- label={formatMessage({ id: "user.password.current" })}
102
- onChange={onChange}
103
- onBlur={onBlur}
104
- value={value}
105
- type="password"
106
- required
107
- />
108
- )}
109
- />
110
- ) : null}
111
- <Controller
112
- control={control}
113
- name="password"
114
- rules={{
115
- minLength: {
116
- value: MIN_LENGTH,
117
- message: formatMessage(
118
- { id: "form.validation.minLength" },
119
- {
120
- prop: formatMessage({ id: "user.password.new_password" }),
121
- value: MIN_LENGTH,
122
- },
123
- ),
124
- },
125
- required: formatMessage(
126
- { id: "form.validation.required" },
127
- { prop: formatMessage({ id: "user.password.new_password" }) },
128
- ),
129
- }}
130
- render={({ field: { onBlur, onChange, value } }) => (
131
- <Form.Input
132
- autoComplete="off"
133
- error={
134
- errors.password?.type === "minLength" &&
135
- errors.password.message
136
- }
137
- icon="lock"
138
- iconPosition="left"
139
- id="password"
140
- label={formatMessage({ id: "user.password.new_password" })}
141
- onChange={onChange}
142
- onBlur={onBlur}
143
- value={value}
144
- type="password"
145
- required
146
- />
147
- )}
148
- />
149
- <Controller
150
- control={control}
151
- name="passwordConfirmation"
152
- rules={{
153
- validate: passwordConfirmationValidator,
154
- required: true,
155
- }}
156
- render={({ field: { onBlur, onChange, value } }) => (
157
- <Form.Input
158
- autoComplete="off"
159
- error={
160
- errors.passwordConfirmation?.type === "validate" &&
161
- errors.passwordConfirmation.message
162
- }
163
- icon="lock"
164
- iconPosition="left"
165
- id="passwordConfirmation"
166
- label={formatMessage({ id: "login.form.re_password.label" })}
167
- onChange={onChange}
168
- onBlur={onBlur}
169
- value={value}
170
- type="password"
171
- required
172
- />
173
- )}
174
- />
175
- <div className="actions">
176
- <HistoryBackButton
177
- content={<FormattedMessage id="actions.cancel" />}
178
- />
179
- <Button
180
- primary
181
- type="submit"
182
- disabled={!isValid}
183
- loading={passwordUpdating}
184
- >
185
- {formatMessage({ id: "user.password.save" })}
186
- </Button>
187
- </div>
188
- </Form>
189
- </Segment>
190
- </Container>
9
+ <UserPasswordModal
10
+ user={{ id, user_name: userName || authUserName }}
11
+ authUserName={authUserName}
12
+ onClose={() => navigate(-1)}
13
+ />
191
14
  );
192
15
  };
193
16
 
@@ -197,9 +20,8 @@ Password.propTypes = {
197
20
  authUserName: PropTypes.string,
198
21
  };
199
22
 
200
- const mapStateToProps = ({ authentication, passwordUpdating }) => ({
201
- passwordUpdating,
23
+ const mapStateToProps = ({ authentication }) => ({
202
24
  authUserName: authentication?.user_name,
203
25
  });
204
26
 
205
- export default connect(mapStateToProps, { updatePassword })(Password);
27
+ export default connect(mapStateToProps)(Password);
@@ -3,11 +3,89 @@ import PropTypes from "prop-types";
3
3
  import { connect } from "react-redux";
4
4
  import { Header, Table } from "semantic-ui-react";
5
5
  import { useIntl } from "react-intl";
6
+ import { DomainTooltip } from "@truedat/bg/taxonomy/components/DomainPath";
6
7
  import { getUserAcls } from "../selectors";
7
8
  import UserAclRow from "./UserAclRow";
9
+ import UserGroupLink from "./UserGroupLink";
8
10
 
9
- export const UserAcls = ({ acls }) => {
11
+ export const UnifiedAcls = ({ acls, groupBy }) => {
10
12
  const { formatMessage } = useIntl();
13
+ const grouped = Object.values(
14
+ _.groupBy((acl) =>
15
+ groupBy === "role"
16
+ ? acl.role
17
+ : JSON.stringify(
18
+ acl.groupId != null
19
+ ? ["group", acl.groupId]
20
+ : ["name", acl.group ?? null],
21
+ ),
22
+ )(acls),
23
+ ).sort(([left], [right]) =>
24
+ (groupBy === "role" ? left.role : left.group || "").localeCompare(
25
+ groupBy === "role" ? right.role : right.group || "",
26
+ ),
27
+ );
28
+ return _.isEmpty(acls) ? null : (
29
+ <Table>
30
+ <Table.Header>
31
+ <Table.Row>
32
+ {["role", "domain", "group"].map((column) => (
33
+ <Table.HeaderCell key={column}>
34
+ {formatMessage({ id: `user.acl.${column}` })}
35
+ </Table.HeaderCell>
36
+ ))}
37
+ </Table.Row>
38
+ </Table.Header>
39
+ <Table.Body>
40
+ {grouped.flatMap((entries, groupIndex) =>
41
+ _.sortBy(["role", "resource.name"])(entries).map((acl, index) => (
42
+ <Table.Row key={`${groupIndex}:${index}`}>
43
+ {(groupBy !== "role" || index === 0) && (
44
+ <Table.Cell rowSpan={groupBy === "role" ? entries.length : 1}>
45
+ {acl.role}
46
+ </Table.Cell>
47
+ )}
48
+ <Table.Cell>
49
+ <DomainTooltip domain={acl.resource} />
50
+ </Table.Cell>
51
+ {(groupBy !== "group" || index === 0) && (
52
+ <Table.Cell rowSpan={groupBy === "group" ? entries.length : 1}>
53
+ {acl.group && (
54
+ <UserGroupLink id={acl.groupId} name={acl.group} />
55
+ )}
56
+ </Table.Cell>
57
+ )}
58
+ </Table.Row>
59
+ )),
60
+ )}
61
+ </Table.Body>
62
+ </Table>
63
+ );
64
+ };
65
+
66
+ UnifiedAcls.propTypes = {
67
+ acls: PropTypes.array.isRequired,
68
+ groupBy: PropTypes.oneOf(["role", "group"]).isRequired,
69
+ };
70
+
71
+ export const UserAcls = ({ acls, groupBy }) => {
72
+ const { formatMessage } = useIntl();
73
+ const grouped = groupBy
74
+ ? Object.entries(
75
+ _.groupBy((acl) =>
76
+ groupBy === "role"
77
+ ? acl.role
78
+ : `${acl.resource?.type}:${acl.resource?.id ?? acl.resource?.name}`,
79
+ )(acls || []),
80
+ ).sort(([, left], [, right]) =>
81
+ (groupBy === "role"
82
+ ? left[0].role
83
+ : left[0].resource?.name || ""
84
+ ).localeCompare(
85
+ groupBy === "role" ? right[0].role : right[0].resource?.name || "",
86
+ ),
87
+ )
88
+ : [];
11
89
  return _.isEmpty(acls) ? null : (
12
90
  <>
13
91
  <Header as="h2" content={formatMessage({ id: "user.acl" })} />
@@ -15,17 +93,49 @@ export const UserAcls = ({ acls }) => {
15
93
  <Table.Header>
16
94
  <Table.Row>
17
95
  <Table.HeaderCell
18
- content={formatMessage({ id: "user.acl.domain" })}
96
+ content={formatMessage({
97
+ id: groupBy === "role" ? "user.acl.role" : "user.acl.domain",
98
+ })}
19
99
  />
20
100
  <Table.HeaderCell
21
- content={formatMessage({ id: "user.acl.role" })}
101
+ content={formatMessage({
102
+ id: groupBy === "role" ? "user.acl.domain" : "user.acl.role",
103
+ })}
22
104
  />
23
105
  </Table.Row>
24
106
  </Table.Header>
25
107
  <Table.Body>
26
- {acls.map((a, i) => (
27
- <UserAclRow key={i} {...a} />
28
- ))}
108
+ {groupBy
109
+ ? grouped.map(([key, entries]) => (
110
+ <Table.Row key={key}>
111
+ <Table.Cell>
112
+ {groupBy === "role" ? (
113
+ entries[0].role
114
+ ) : (
115
+ <DomainTooltip domain={entries[0].resource} />
116
+ )}
117
+ </Table.Cell>
118
+ <Table.Cell>
119
+ <ul className="um-acl-values">
120
+ {groupBy === "role"
121
+ ? _.uniqBy(
122
+ (acl) =>
123
+ `${acl.resource?.type}:${acl.resource?.id ?? acl.resource?.name}`,
124
+ )(entries).map(({ resource }) => (
125
+ <li
126
+ key={`${resource?.type}:${resource?.id ?? resource?.name}`}
127
+ >
128
+ <DomainTooltip domain={resource} />
129
+ </li>
130
+ ))
131
+ : _.uniq(entries.map(({ role }) => role))
132
+ .sort()
133
+ .map((role) => <li key={role}>{role}</li>)}
134
+ </ul>
135
+ </Table.Cell>
136
+ </Table.Row>
137
+ ))
138
+ : acls.map((a, i) => <UserAclRow key={i} {...a} />)}
29
139
  </Table.Body>
30
140
  </Table>
31
141
  </>
@@ -34,6 +144,7 @@ export const UserAcls = ({ acls }) => {
34
144
 
35
145
  UserAcls.propTypes = {
36
146
  acls: PropTypes.array,
147
+ groupBy: PropTypes.oneOf(["domain", "role"]),
37
148
  };
38
149
 
39
150
  const mapStateToProps = (state) => ({
@@ -1,45 +1,112 @@
1
1
  import _ from "lodash/fp";
2
- import { connect, useDispatch } from "react-redux";
2
+ import { connect } from "react-redux";
3
+ import { useState } from "react";
3
4
  import PropTypes from "prop-types";
4
5
  import { Button, Icon } from "semantic-ui-react";
5
- import { FormattedMessage } from "react-intl";
6
- import ConfirmModal from "@truedat/core/components/ConfirmModal";
6
+ import { FormattedMessage, useIntl } from "react-intl";
7
+ import TrueModal from "@truedat/core/components/TrueModal";
7
8
  import GroupActions from "@truedat/core/components/GroupActions";
8
9
  import { linkTo, USERS } from "@truedat/core/routes";
9
10
  import { Link, useNavigate } from "react-router";
10
- import { deleteUser, fetchUsers } from "../routines";
11
+ import { deleteUser } from "../routines";
12
+ import useUserModalOperation from "../hooks/useUserModalOperation";
11
13
 
12
- const ModalDelete = ({ handleDelete, id, name }) => (
13
- <ConfirmModal
14
- icon="trash"
15
- trigger={
14
+ export const DeleteUserModal = ({ id, name, onClose, onDeleted }) => {
15
+ const { formatMessage } = useIntl();
16
+ const {
17
+ submit,
18
+ isSubmitting,
19
+ onClose: closeModal,
20
+ error,
21
+ } = useUserModalOperation(deleteUser, onClose, () => {
22
+ onClose();
23
+ onDeleted?.();
24
+ });
25
+ return (
26
+ <TrueModal
27
+ open
28
+ title={formatMessage({ id: "user.delete.title" })}
29
+ headerColor="#ffffff"
30
+ headerTextColor="var(--td-danger, #b91c1c)"
31
+ icon="warning sign"
32
+ className="user-modal user-modal--delete"
33
+ size="mini"
34
+ onClose={closeModal}
35
+ aria-busy={isSubmitting}
36
+ closeOnEscape={!isSubmitting}
37
+ closeOnDimmerClick={!isSubmitting}
38
+ banner={error ? <span role="alert">{error}</span> : null}
39
+ bannerType="danger"
40
+ actions={[
41
+ {
42
+ key: "cancel",
43
+ secondary: true,
44
+ content: formatMessage({ id: "actions.cancel" }),
45
+ disabled: isSubmitting,
46
+ onClick: closeModal,
47
+ },
48
+ {
49
+ key: "delete",
50
+ negative: true,
51
+ content: formatMessage({ id: "user.delete.submit" }),
52
+ loading: isSubmitting,
53
+ onClick: () => submit({ id }),
54
+ },
55
+ ]}
56
+ >
57
+ <p>
58
+ <FormattedMessage
59
+ id="user.delete.description"
60
+ values={{
61
+ name,
62
+ strong: (chunks) => <strong>{chunks}</strong>,
63
+ danger: (chunks) => (
64
+ <span className="um-danger-text">{chunks}</span>
65
+ ),
66
+ }}
67
+ />
68
+ </p>
69
+ <p>
70
+ <FormattedMessage id="user.delete.confirm" />
71
+ </p>
72
+ </TrueModal>
73
+ );
74
+ };
75
+
76
+ DeleteUserModal.propTypes = {
77
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
78
+ name: PropTypes.string,
79
+ onDeleted: PropTypes.func,
80
+ onClose: PropTypes.func.isRequired,
81
+ };
82
+
83
+ const ModalDelete = ({ id, name }) => {
84
+ const navigate = useNavigate();
85
+ const [open, setOpen] = useState(false);
86
+ return (
87
+ <>
16
88
  <Button
17
89
  icon={<Icon name="trash alternate outline" color="red" />}
18
90
  content={<FormattedMessage id="actions.delete" />}
91
+ onClick={() => setOpen(true)}
19
92
  />
20
- }
21
- header={<FormattedMessage id="user.actions.delete.confirmation.header" />}
22
- size="small"
23
- content={
24
- <FormattedMessage
25
- id="user.actions.delete.confirmation.content"
26
- values={{ name: <i>{name}</i> }}
27
- />
28
- }
29
- onConfirm={() => handleDelete({ id })}
30
- />
31
- );
32
-
33
- export const UserActions = ({ deleteUser, user: { id, name } }) => {
34
- const navigate = useNavigate();
35
- const dispatch = useDispatch();
36
-
37
- const onConfirmDelete = async ({ id }) => {
38
- await deleteUser({ id });
39
- dispatch(fetchUsers());
40
- navigate(USERS);
41
- };
93
+ {open && (
94
+ <DeleteUserModal
95
+ onDeleted={() => navigate(USERS)}
96
+ id={id}
97
+ name={name}
98
+ onClose={() => setOpen(false)}
99
+ />
100
+ )}
101
+ </>
102
+ );
103
+ };
104
+ ModalDelete.propTypes = {
105
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
106
+ name: PropTypes.string,
107
+ };
42
108
 
109
+ export const UserActions = ({ user: { id, name, full_name, user_name } }) => {
43
110
  const generateAvailableActions = () => {
44
111
  const actions = {
45
112
  edit: true,
@@ -77,9 +144,8 @@ export const UserActions = ({ deleteUser, user: { id, name } }) => {
77
144
  icon: "trash",
78
145
  value: "delete",
79
146
  as: ModalDelete,
80
- handleDelete: onConfirmDelete,
81
147
  id,
82
- name,
148
+ name: full_name || name || user_name,
83
149
  selected: false,
84
150
  active: false,
85
151
  },
@@ -99,10 +165,9 @@ export const UserActions = ({ deleteUser, user: { id, name } }) => {
99
165
  };
100
166
 
101
167
  UserActions.propTypes = {
102
- deleteUser: PropTypes.func,
103
168
  user: PropTypes.object.isRequired,
104
169
  };
105
170
 
106
171
  const mapStateToProps = ({ user }) => ({ user });
107
172
 
108
- export default connect(mapStateToProps, { deleteUser })(UserActions);
173
+ export default connect(mapStateToProps)(UserActions);