@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
@@ -15,10 +15,10 @@ export const UserGroupAcls = ({ acls }) => {
15
15
  <Table.Header>
16
16
  <Table.Row>
17
17
  <Table.HeaderCell
18
- content={formatMessage({ id: "user.acl.domain" })}
18
+ content={formatMessage({ id: "user.acl.group" })}
19
19
  />
20
20
  <Table.HeaderCell
21
- content={formatMessage({ id: "user.acl.group" })}
21
+ content={formatMessage({ id: "user.acl.domain" })}
22
22
  />
23
23
  <Table.HeaderCell
24
24
  content={formatMessage({ id: "user.acl.role" })}
@@ -0,0 +1,21 @@
1
+ import PropTypes from "prop-types";
2
+ import { Icon } from "semantic-ui-react";
3
+ import { Link } from "react-router";
4
+ import { linkTo } from "@truedat/core/routes";
5
+
6
+ const UserGroupLink = ({ id, name }) =>
7
+ id != null ? (
8
+ <Link to={linkTo.GROUP({ id })} target="_blank" rel="noopener noreferrer">
9
+ <Icon name="external alternate" aria-hidden="true" />
10
+ <span>{name}</span>
11
+ </Link>
12
+ ) : (
13
+ name
14
+ );
15
+
16
+ UserGroupLink.propTypes = {
17
+ id: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
18
+ name: PropTypes.string,
19
+ };
20
+
21
+ export default UserGroupLink;
@@ -0,0 +1,228 @@
1
+ import PropTypes from "prop-types";
2
+ import { useState } from "react";
3
+ import { connect } from "react-redux";
4
+ import { Checkbox, Icon } from "semantic-ui-react";
5
+ import { FormattedMessage, useIntl } from "react-intl";
6
+ import { Link } from "react-router";
7
+ import TrueModal from "@truedat/core/components/TrueModal";
8
+ import { linkTo } from "@truedat/core/routes";
9
+ import { useGroups } from "../../groups/hooks/useGroups";
10
+ import { getUserAcls, getUserGroupAcls } from "../selectors";
11
+ import { UnifiedAcls } from "./UserAcls";
12
+ import UserGroupLink from "./UserGroupLink";
13
+ import { userTypeOptions } from "./UserTypeSelector";
14
+
15
+ const getGroupName = (group) => {
16
+ if (typeof group === "string") return group;
17
+ return group?.alias || group?.name || String(group);
18
+ };
19
+
20
+ export const UserInfoModal = ({
21
+ user,
22
+ groups = [],
23
+ acls,
24
+ groupAcls,
25
+ onClose,
26
+ }) => {
27
+ const { formatMessage } = useIntl();
28
+ const [groupByRole, setGroupByRole] = useState(false);
29
+ const [aclsOpen, setAclsOpen] = useState(true);
30
+
31
+ if (!user?.id) return null;
32
+
33
+ const displayName = user.full_name?.trim() || user.user_name || "";
34
+ const nameParts = displayName.split(/\s+/);
35
+ const initials =
36
+ (nameParts[0]?.[0] || "?") +
37
+ (nameParts.length > 1 ? nameParts[nameParts.length - 1][0] : "");
38
+ const userType = userTypeOptions.find(({ value }) => value === user.role);
39
+ const unavailable = formatMessage({ id: "user.info.unavailable" });
40
+ const permissions = [...(acls || []), ...(groupAcls || [])];
41
+ const roleCount = new Set(permissions.map(({ role }) => role)).size;
42
+ const hasAcls = permissions.length > 0;
43
+ const aclsPanelId = `user-acls-panel-${user.id}`;
44
+
45
+ return (
46
+ <TrueModal
47
+ open
48
+ title={<FormattedMessage id="user.info.title" />}
49
+ icon="info circle"
50
+ actions={[
51
+ {
52
+ key: "close",
53
+ content: formatMessage({
54
+ id: "actions.close",
55
+ defaultMessage: "Close",
56
+ }),
57
+ secondary: true,
58
+ onClick: onClose,
59
+ },
60
+ {
61
+ key: "edit",
62
+ content: formatMessage({ id: "actions.edit" }),
63
+ primary: true,
64
+ as: Link,
65
+ to: linkTo.USER_EDIT({ id: user.id }),
66
+ },
67
+ ]}
68
+ onClose={onClose}
69
+ size="small"
70
+ className="user-modal user-modal--info"
71
+ >
72
+ <div className="um-profile">
73
+ <div className="um-avatar" aria-hidden="true">
74
+ {initials.toUpperCase()}
75
+ </div>
76
+ <div className="um-profile-identity">
77
+ <h2 className="um-profile-name">{displayName}</h2>
78
+ <p>
79
+ <Icon name="mail" size="small" aria-hidden="true" />
80
+ {user.email || <em className="um-unavailable">{unavailable}</em>}
81
+ </p>
82
+ </div>
83
+ {userType ? (
84
+ <span className="um-user-type-box um-user-type-box--active">
85
+ <Icon name={userType.icon} aria-hidden="true" />
86
+ {formatMessage({ id: userType.messageId })}
87
+ </span>
88
+ ) : (
89
+ <em className="um-unavailable">{user.role || unavailable}</em>
90
+ )}
91
+ </div>
92
+ <dl className="um-profile-fields">
93
+ {[
94
+ ["user circle", "user.form.user_name", user.user_name],
95
+ ["id badge", "user.form.external_id", user.external_id],
96
+ ].map(([icon, messageId, value]) => (
97
+ <div className="um-profile-field" key={messageId}>
98
+ <dt>
99
+ <Icon name={icon} aria-hidden="true" />
100
+ {formatMessage({ id: messageId })}
101
+ </dt>
102
+ <dd>
103
+ {value || <em className="um-unavailable">{unavailable}</em>}
104
+ </dd>
105
+ </div>
106
+ ))}
107
+ </dl>
108
+ {user.groups?.length ? (
109
+ <section className="um-profile-groups">
110
+ <h3 className="um-section-label">
111
+ <span>
112
+ <Icon name="users" aria-hidden="true" />
113
+ </span>
114
+ <span>
115
+ <FormattedMessage id="user.form.groups" />
116
+ <span className="um-acl-count">({user.groups.length})</span>
117
+ </span>
118
+ </h3>
119
+ <ul className="um-groups">
120
+ {user.groups.map((group, index) => {
121
+ const name = typeof group === "string" ? group : group?.name;
122
+ const alias =
123
+ typeof group === "string" ? group : group?.alias || name;
124
+ const namedGroup = groups.find(
125
+ (candidate) => name != null && candidate.name === name,
126
+ );
127
+ const aliasGroups = groups.filter(
128
+ (candidate) => alias && candidate.alias === alias,
129
+ );
130
+ const id =
131
+ group?.id ??
132
+ namedGroup?.id ??
133
+ (aliasGroups.length === 1 ? aliasGroups[0].id : undefined);
134
+ return (
135
+ <li key={id ?? index} className="um-group-chip">
136
+ <UserGroupLink id={id} name={getGroupName(group)} />
137
+ </li>
138
+ );
139
+ })}
140
+ </ul>
141
+ </section>
142
+ ) : (
143
+ <p className="um-empty">
144
+ <Icon name="users" aria-hidden="true" />
145
+ <FormattedMessage id="user.info.no_groups" />
146
+ </p>
147
+ )}
148
+
149
+ <div className="um-permissions">
150
+ {hasAcls ? (
151
+ <div className="um-acl-section">
152
+ <div className="um-acl-toolbar">
153
+ <button
154
+ type="button"
155
+ className="um-acl-header"
156
+ aria-expanded={aclsOpen}
157
+ aria-controls={aclsPanelId}
158
+ onClick={() => setAclsOpen((open) => !open)}
159
+ >
160
+ <span className="um-acl-icon--primary">
161
+ <Icon name="shield alternate" />
162
+ </span>
163
+ <span className="um-acl-title">
164
+ <FormattedMessage id="user.acl" />
165
+ <span className="um-acl-count">({roleCount})</span>
166
+ </span>
167
+ <Icon name={aclsOpen ? "chevron up" : "chevron down"} />
168
+ </button>
169
+ {roleCount > 1 && (
170
+ <div className="um-acl-grouping">
171
+ <Checkbox
172
+ id={`user-group-by-role-${user.id}`}
173
+ toggle
174
+ label={formatMessage({ id: "user.info.group_by_role" })}
175
+ checked={groupByRole}
176
+ onChange={(_event, { checked }) => setGroupByRole(checked)}
177
+ />
178
+ </div>
179
+ )}
180
+ </div>
181
+ {aclsOpen && (
182
+ <div
183
+ id={aclsPanelId}
184
+ className="um-acl-panel"
185
+ role="region"
186
+ aria-label={formatMessage({
187
+ id: "user.acl",
188
+ })}
189
+ tabIndex={0}
190
+ >
191
+ <UnifiedAcls
192
+ acls={permissions}
193
+ groupBy={groupByRole && roleCount > 1 ? "role" : "group"}
194
+ />
195
+ </div>
196
+ )}
197
+ </div>
198
+ ) : (
199
+ <p className="um-empty">
200
+ <Icon name="shield alternate" aria-hidden="true" />
201
+ <FormattedMessage id="user.info.no_permissions" />
202
+ </p>
203
+ )}
204
+ </div>
205
+ </TrueModal>
206
+ );
207
+ };
208
+
209
+ UserInfoModal.propTypes = {
210
+ user: PropTypes.object,
211
+ groups: PropTypes.array,
212
+ acls: PropTypes.array,
213
+ groupAcls: PropTypes.array,
214
+ onClose: PropTypes.func.isRequired,
215
+ };
216
+
217
+ const UserInfoModalWithGroups = (props) => {
218
+ const { groups } = useGroups();
219
+ return <UserInfoModal {...props} groups={groups} />;
220
+ };
221
+
222
+ const mapStateToProps = (state) => ({
223
+ user: state.user,
224
+ acls: getUserAcls(state),
225
+ groupAcls: getUserGroupAcls(state),
226
+ });
227
+
228
+ export default connect(mapStateToProps)(UserInfoModalWithGroups);
@@ -0,0 +1,66 @@
1
+ import { useEffect, useState } from "react";
2
+ import PropTypes from "prop-types";
3
+ import { Controller } from "react-hook-form";
4
+ import { Form, Icon } from "semantic-ui-react";
5
+
6
+ const Feedback = ({ id, message }) => {
7
+ const [lastMessage, setLastMessage] = useState(message);
8
+ useEffect(() => {
9
+ if (message) setLastMessage(message);
10
+ }, [message]);
11
+
12
+ return (
13
+ <div className="um-field-feedback" id={id} aria-live="polite">
14
+ <span className={message ? "is-visible" : ""} aria-hidden={!message}>
15
+ {lastMessage}
16
+ </span>
17
+ </div>
18
+ );
19
+ };
20
+
21
+ Feedback.propTypes = { id: PropTypes.string, message: PropTypes.string };
22
+
23
+ const UserPasswordField = ({
24
+ control,
25
+ name,
26
+ label,
27
+ rules,
28
+ disabled,
29
+ autoComplete = "new-password",
30
+ }) => (
31
+ <Controller
32
+ control={control}
33
+ name={name}
34
+ rules={rules}
35
+ render={({ field, fieldState: { error } }) => (
36
+ <Form.Field required disabled={disabled} error={Boolean(error)}>
37
+ <label htmlFor={name}>{label}</label>
38
+ <div className="ui left icon input">
39
+ <input
40
+ {...field}
41
+ id={name}
42
+ type="password"
43
+ autoComplete={autoComplete}
44
+ disabled={disabled}
45
+ required
46
+ aria-invalid={Boolean(error)}
47
+ aria-describedby={`${name}-feedback`}
48
+ />
49
+ <Icon name="lock" aria-hidden="true" />
50
+ </div>
51
+ <Feedback id={`${name}-feedback`} message={error?.message} />
52
+ </Form.Field>
53
+ )}
54
+ />
55
+ );
56
+
57
+ UserPasswordField.propTypes = {
58
+ control: PropTypes.object.isRequired,
59
+ name: PropTypes.string.isRequired,
60
+ label: PropTypes.string.isRequired,
61
+ rules: PropTypes.object,
62
+ disabled: PropTypes.bool,
63
+ autoComplete: PropTypes.string,
64
+ };
65
+
66
+ export default UserPasswordField;
@@ -0,0 +1,210 @@
1
+ import { useEffect, useRef } from "react";
2
+ import { flushSync } from "react-dom";
3
+ import PropTypes from "prop-types";
4
+ import { connect } from "react-redux";
5
+ import { Form } from "semantic-ui-react";
6
+ import { FormattedMessage, useIntl } from "react-intl";
7
+ import { useForm, useWatch } from "react-hook-form";
8
+ import TrueModal from "@truedat/core/components/TrueModal";
9
+ import { updatePassword } from "../routines";
10
+ import useUserModalOperation from "../hooks/useUserModalOperation";
11
+
12
+ import UserPasswordField from "./UserPasswordField";
13
+
14
+ const MIN_LENGTH = 6;
15
+ const EMPTY_PASSWORDS = {
16
+ currentPassword: "",
17
+ password: "",
18
+ passwordConfirmation: "",
19
+ };
20
+
21
+ const getInitials = (fullName, userName) => {
22
+ const normalized = fullName?.trim() || "";
23
+ if (normalized) {
24
+ const parts = normalized.split(/\s+/);
25
+ return parts.length >= 2
26
+ ? (parts[0][0] + parts[parts.length - 1][0]).toUpperCase()
27
+ : parts[0][0].toUpperCase();
28
+ }
29
+ return userName ? userName[0].toUpperCase() : "?";
30
+ };
31
+
32
+ export const UserPasswordModal = ({ user, authUserName, onClose }) => {
33
+ const { formatMessage } = useIntl();
34
+ const discardTimer = useRef(null);
35
+ useEffect(() => () => clearTimeout(discardTimer.current), []);
36
+ const {
37
+ handleSubmit,
38
+ control,
39
+ reset,
40
+ getValues,
41
+ trigger,
42
+ formState: { isValid },
43
+ } = useForm({ mode: "all", defaultValues: EMPTY_PASSWORDS });
44
+ const password = useWatch({ control, name: "password" });
45
+ const discardAndClose = () => {
46
+ if (discardTimer.current !== null) return;
47
+ // Chrome retains typed passwords after value assignments. Native editing
48
+ // clears that state too; allow it to settle before removing the form/routing.
49
+ if (document.execCommand) {
50
+ document
51
+ .getElementById("password-modal-form")
52
+ ?.querySelectorAll('input[type="password"]')
53
+ .forEach((input) => {
54
+ if (!input.value) return;
55
+ input.focus({ preventScroll: true });
56
+ input.select();
57
+ document.execCommand("delete");
58
+ });
59
+ }
60
+ flushSync(() => reset(EMPTY_PASSWORDS));
61
+ discardTimer.current = setTimeout(() => {
62
+ discardTimer.current = null;
63
+ onClose();
64
+ }, 0);
65
+ };
66
+ const {
67
+ submit,
68
+ isSubmitting,
69
+ onClose: closeModal,
70
+ error,
71
+ } = useUserModalOperation(updatePassword, discardAndClose, onClose);
72
+
73
+ useEffect(() => {
74
+ if (getValues("passwordConfirmation")) void trigger("passwordConfirmation");
75
+ }, [password, getValues, trigger]);
76
+
77
+ const needsCurrentPassword =
78
+ !!authUserName && user?.user_name === authUserName;
79
+
80
+ if (!user?.id && !(authUserName && needsCurrentPassword)) return null;
81
+ const passwordRules = {
82
+ required: formatMessage({ id: "user.password.required" }),
83
+ minLength: {
84
+ value: MIN_LENGTH,
85
+ message: formatMessage(
86
+ { id: "user.password.min_length" },
87
+ { count: MIN_LENGTH },
88
+ ),
89
+ },
90
+ };
91
+
92
+ const onSubmit = ({ currentPassword, password }) => {
93
+ if (discardTimer.current !== null) return;
94
+ const userData = needsCurrentPassword
95
+ ? { new_password: password, old_password: currentPassword }
96
+ : { id: user.id, new_password: password };
97
+ submit({ user: userData });
98
+ };
99
+
100
+ const initials = getInitials(user.full_name, user.user_name);
101
+
102
+ return (
103
+ <TrueModal
104
+ open
105
+ title={formatMessage({ id: "user.password.change" })}
106
+ icon="lock"
107
+ actions={[
108
+ {
109
+ key: "cancel",
110
+ content: formatMessage({ id: "actions.cancel" }),
111
+ secondary: true,
112
+ onClick: closeModal,
113
+ disabled: isSubmitting,
114
+ },
115
+ {
116
+ key: "save",
117
+ content: formatMessage({ id: "user.password.change" }),
118
+ primary: true,
119
+ type: "submit",
120
+ form: "password-modal-form",
121
+ disabled: !isValid,
122
+ loading: isSubmitting,
123
+ },
124
+ ]}
125
+ onClose={closeModal}
126
+ aria-busy={isSubmitting}
127
+ closeOnEscape={!isSubmitting}
128
+ closeOnDimmerClick={!isSubmitting}
129
+ banner={error ? <span role="alert">{error}</span> : null}
130
+ bannerType="danger"
131
+ size="small"
132
+ className="user-modal user-modal--password"
133
+ >
134
+ <div className="um-password-user">
135
+ <div className="um-avatar um-avatar--sm">{initials}</div>
136
+ <div>
137
+ <h2 className="um-profile-name">
138
+ {user.full_name?.trim() || user.user_name}
139
+ </h2>
140
+ <div className="um-password-email">
141
+ {user.email || (
142
+ <em className="um-unavailable">
143
+ <FormattedMessage id="user.info.unavailable" />
144
+ </em>
145
+ )}
146
+ </div>
147
+ </div>
148
+ </div>
149
+
150
+ <Form
151
+ id="password-modal-form"
152
+ noValidate
153
+ onSubmit={handleSubmit(onSubmit)}
154
+ >
155
+ <input
156
+ className="um-accessible-field"
157
+ type="text"
158
+ name="username"
159
+ autoComplete="username"
160
+ value={user.user_name || authUserName || ""}
161
+ readOnly
162
+ tabIndex={-1}
163
+ aria-hidden="true"
164
+ />
165
+ {needsCurrentPassword && (
166
+ <UserPasswordField
167
+ control={control}
168
+ name="currentPassword"
169
+ label={formatMessage({ id: "user.password.current" })}
170
+ rules={passwordRules}
171
+ disabled={isSubmitting}
172
+ autoComplete="current-password"
173
+ />
174
+ )}
175
+ <UserPasswordField
176
+ control={control}
177
+ name="password"
178
+ label={formatMessage({ id: "user.password.new_password" })}
179
+ rules={passwordRules}
180
+ disabled={isSubmitting}
181
+ />
182
+ <UserPasswordField
183
+ control={control}
184
+ name="passwordConfirmation"
185
+ label={formatMessage({ id: "user.form.password_confirmation" })}
186
+ rules={{
187
+ required: formatMessage({ id: "user.password.required" }),
188
+ validate: (value) =>
189
+ value === getValues("password") ||
190
+ formatMessage({ id: "user.password.mismatch" }),
191
+ }}
192
+ disabled={isSubmitting}
193
+ />
194
+ </Form>
195
+ </TrueModal>
196
+ );
197
+ };
198
+
199
+ UserPasswordModal.propTypes = {
200
+ user: PropTypes.object,
201
+ authUserName: PropTypes.string,
202
+ onClose: PropTypes.func.isRequired,
203
+ };
204
+
205
+ const mapStateToProps = ({ user, authentication }) => ({
206
+ user,
207
+ authUserName: authentication?.user_name,
208
+ });
209
+
210
+ export default connect(mapStateToProps)(UserPasswordModal);
@@ -1,15 +1,9 @@
1
1
  import { Routes, Route, Outlet } from "react-router";
2
2
  import { ProtectedRoute, Loader } from "@truedat/core/router";
3
3
  import GroupRouteModal from "../../groups/components/GroupRouteModal";
4
-
5
4
  import Password from "./Password";
6
- import EditUser from "./EditUser";
7
- import NewUser from "./NewUser";
8
- import User from "./User";
9
- import UserLoader from "./UserLoader";
10
5
  import UsersAndGroups from "./UsersAndGroups";
11
6
  import UsersLoader from "./UsersLoader";
12
- import UserPassword from "./UserPassword";
13
7
 
14
8
  export const UserRoutes = () => (
15
9
  <Routes>
@@ -22,13 +16,10 @@ export const UserRoutes = () => (
22
16
  }
23
17
  >
24
18
  <Route index element={<UsersAndGroups />} />
25
- <Route path={"new"} element={<NewUser />} />
26
-
27
- <Route path={":id"} element={<Loader loaders={<UserLoader />} />}>
28
- <Route index element={<User />} />
29
- <Route path={"edit"} element={<EditUser />} />
30
- <Route path={"password"} element={<UserPassword />} />
31
- </Route>
19
+ <Route path={"new"} element={<UsersAndGroups />} />
20
+ <Route path={":id"} element={<UsersAndGroups />} />
21
+ <Route path={":id/edit"} element={<UsersAndGroups />} />
22
+ <Route path={":id/password"} element={<UsersAndGroups />} />
32
23
  </Route>
33
24
  <Route
34
25
  path={"groups"}
@@ -6,12 +6,15 @@ import { GROUPS, USERS } from "@truedat/core/routes";
6
6
 
7
7
  const UserTabs = () => {
8
8
  const path = usePath();
9
+ const isUsersPath = path === USERS || path.startsWith(USERS + "/");
10
+ const isGroupsPath = path === GROUPS || path.startsWith(GROUPS + "/");
11
+
9
12
  return (
10
13
  <Menu attached="top" secondary pointing tabular>
11
- <Menu.Item active={path === USERS} as={Link} to={USERS}>
14
+ <Menu.Item active={isUsersPath} as={Link} to={USERS}>
12
15
  <FormattedMessage id="tabs.users" />
13
16
  </Menu.Item>
14
- <Menu.Item active={path === GROUPS} as={Link} to={GROUPS}>
17
+ <Menu.Item active={isGroupsPath} as={Link} to={GROUPS}>
15
18
  <FormattedMessage id="tabs.groups" />
16
19
  </Menu.Item>
17
20
  </Menu>