@truedat/auth 8.14.3 → 8.14.4

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 (66) hide show
  1. package/package.json +4 -4
  2. package/src/messages/en.js +6 -5
  3. package/src/messages/es.js +8 -7
  4. package/src/styles/UserCard.less +582 -0
  5. package/src/styles/UserModals.less +826 -0
  6. package/src/styles/Users.less +280 -0
  7. package/src/users/components/AdminUserRoutes.js +7 -20
  8. package/src/users/components/EditUser.js +7 -34
  9. package/src/users/components/EditUserModal.js +150 -0
  10. package/src/users/components/NewUser.js +128 -17
  11. package/src/users/components/Password.js +11 -189
  12. package/src/users/components/UserAcls.js +56 -6
  13. package/src/users/components/UserActions.js +99 -34
  14. package/src/users/components/UserCard.js +168 -22
  15. package/src/users/components/UserCards.js +273 -35
  16. package/src/users/components/UserFormFields.js +206 -0
  17. package/src/users/components/UserGroupAclRow.js +8 -2
  18. package/src/users/components/UserGroupAcls.js +2 -2
  19. package/src/users/components/UserGroupLink.js +21 -0
  20. package/src/users/components/UserInfoModal.js +268 -0
  21. package/src/users/components/UserPasswordField.js +66 -0
  22. package/src/users/components/UserPasswordModal.js +210 -0
  23. package/src/users/components/UserRoutes.js +4 -13
  24. package/src/users/components/UserTabs.js +5 -2
  25. package/src/users/components/UserTypeSelector.js +109 -0
  26. package/src/users/components/UsersAndGroups.js +39 -5
  27. package/src/users/components/UsersTable.js +234 -0
  28. package/src/users/components/__tests__/EditUser.spec.js +84 -10
  29. package/src/users/components/__tests__/NewUser.spec.js +34 -4
  30. package/src/users/components/__tests__/Password.spec.js +53 -146
  31. package/src/users/components/__tests__/UserAcls.spec.js +23 -2
  32. package/src/users/components/__tests__/UserCard.spec.js +111 -5
  33. package/src/users/components/__tests__/UserCards.spec.js +194 -0
  34. package/src/users/components/__tests__/UserGroupAclRow.spec.js +19 -1
  35. package/src/users/components/__tests__/UserModals.spec.js +507 -0
  36. package/src/users/components/__tests__/UserRoutes.spec.js +8 -3
  37. package/src/users/components/__tests__/UserTypeSelector.spec.js +32 -0
  38. package/src/users/components/__tests__/UsersAndGroups.spec.js +17 -0
  39. package/src/users/components/__tests__/__snapshots__/UserCard.spec.js.snap +64 -37
  40. package/src/users/components/__tests__/__snapshots__/UserCards.spec.js.snap +424 -75
  41. package/src/users/components/__tests__/__snapshots__/UserGroupAclRow.spec.js.snap +7 -3
  42. package/src/users/components/__tests__/__snapshots__/UserRoutes.spec.js.snap +4 -13
  43. package/src/users/hooks/useSortedGroups.js +13 -0
  44. package/src/users/hooks/useUserModalOperation.js +55 -0
  45. package/src/users/reducers/__tests__/userRedirect.spec.js +14 -3
  46. package/src/users/reducers/index.js +1 -0
  47. package/src/users/reducers/userModalOperation.js +29 -0
  48. package/src/users/reducers/userRedirect.js +5 -4
  49. package/src/users/sagas/__tests__/updateUser.spec.js +19 -1
  50. package/src/users/sagas/createUser.js +12 -5
  51. package/src/users/sagas/deleteUser.js +12 -5
  52. package/src/users/sagas/updateUser.js +19 -8
  53. package/src/users/selectors/__tests__/filterUsers.spec.js +11 -0
  54. package/src/users/selectors/__tests__/getUserAcls.spec.js +1 -0
  55. package/src/users/selectors/__tests__/getUserGroupAcls.spec.js +2 -0
  56. package/src/users/selectors/filterUsers.js +9 -1
  57. package/src/users/selectors/getUserGroupAcls.js +1 -0
  58. package/src/users/components/User.js +0 -68
  59. package/src/users/components/UserPassword.js +0 -24
  60. package/src/users/components/__tests__/User.spec.js +0 -20
  61. package/src/users/components/__tests__/UserPassword.spec.js +0 -24
  62. package/src/users/components/__tests__/__snapshots__/EditUser.spec.js.snap +0 -281
  63. package/src/users/components/__tests__/__snapshots__/NewUser.spec.js.snap +0 -321
  64. package/src/users/components/__tests__/__snapshots__/Password.spec.js.snap +0 -128
  65. package/src/users/components/__tests__/__snapshots__/User.spec.js.snap +0 -129
  66. package/src/users/components/__tests__/__snapshots__/UserPassword.spec.js.snap +0 -124
@@ -0,0 +1,268 @@
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 { UserAcls } from "./UserAcls";
12
+ import { UserGroupAcls } from "./UserGroupAcls";
13
+ import UserGroupLink from "./UserGroupLink";
14
+ import { userTypeOptions } from "./UserTypeSelector";
15
+
16
+ const getGroupName = (group) => {
17
+ if (typeof group === "string") return group;
18
+ return group?.alias || group?.name || String(group);
19
+ };
20
+
21
+ export const UserInfoModal = ({
22
+ user,
23
+ groups = [],
24
+ acls,
25
+ groupAcls,
26
+ onClose,
27
+ }) => {
28
+ const { formatMessage } = useIntl();
29
+ const [groupByRole, setGroupByRole] = useState(false);
30
+ const [aclsOpen, setAclsOpen] = useState(true);
31
+ const [groupAclsOpen, setGroupAclsOpen] = useState(true);
32
+
33
+ if (!user?.id) return null;
34
+
35
+ const displayName = user.full_name?.trim() || user.user_name || "";
36
+ const nameParts = displayName.split(/\s+/);
37
+ const initials =
38
+ (nameParts[0]?.[0] || "?") +
39
+ (nameParts.length > 1 ? nameParts[nameParts.length - 1][0] : "");
40
+ const userType = userTypeOptions.find(({ value }) => value === user.role);
41
+ const unavailable = formatMessage({ id: "user.info.unavailable" });
42
+ const hasAcls = acls?.length > 0;
43
+ const hasGroupAcls = groupAcls?.length > 0;
44
+ const aclsPanelId = `user-acls-panel-${user.id}`;
45
+ const groupAclsPanelId = `user-group-acls-panel-${user.id}`;
46
+
47
+ return (
48
+ <TrueModal
49
+ open
50
+ title={<FormattedMessage id="user.info.title" />}
51
+ icon="info circle"
52
+ actions={[
53
+ {
54
+ key: "close",
55
+ content: formatMessage({
56
+ id: "actions.close",
57
+ defaultMessage: "Close",
58
+ }),
59
+ secondary: true,
60
+ onClick: onClose,
61
+ },
62
+ {
63
+ key: "edit",
64
+ content: formatMessage({ id: "users.actions.edit" }),
65
+ primary: true,
66
+ as: Link,
67
+ to: linkTo.USER_EDIT({ id: user.id }),
68
+ },
69
+ ]}
70
+ onClose={onClose}
71
+ size="small"
72
+ className="user-modal user-modal--info"
73
+ >
74
+ <div className="um-profile">
75
+ <div className="um-avatar" aria-hidden="true">
76
+ {initials.toUpperCase()}
77
+ </div>
78
+ <div className="um-profile-identity">
79
+ <h2 className="um-profile-name">{displayName}</h2>
80
+ <p>
81
+ <Icon name="mail" size="small" aria-hidden="true" />
82
+ {user.email || <em className="um-unavailable">{unavailable}</em>}
83
+ </p>
84
+ </div>
85
+ {userType ? (
86
+ <span className="um-user-type-box um-user-type-box--active">
87
+ <Icon name={userType.icon} aria-hidden="true" />
88
+ {formatMessage({ id: userType.messageId })}
89
+ </span>
90
+ ) : (
91
+ <em className="um-unavailable">{user.role || unavailable}</em>
92
+ )}
93
+ </div>
94
+ <dl className="um-profile-fields">
95
+ {[
96
+ ["user circle", "user.form.user_name", user.user_name],
97
+ ["id badge", "user.form.external_id", user.external_id],
98
+ ].map(([icon, messageId, value]) => (
99
+ <div className="um-profile-field" key={messageId}>
100
+ <dt>
101
+ <Icon name={icon} aria-hidden="true" />
102
+ {formatMessage({ id: messageId })}
103
+ </dt>
104
+ <dd>
105
+ {value || <em className="um-unavailable">{unavailable}</em>}
106
+ </dd>
107
+ </div>
108
+ ))}
109
+ </dl>
110
+ {user.groups?.length ? (
111
+ <section className="um-profile-groups">
112
+ <h3 className="um-section-label">
113
+ <span>
114
+ <Icon name="users" aria-hidden="true" />
115
+ </span>
116
+ <span>
117
+ <FormattedMessage id="user.form.groups" />
118
+ <span className="um-acl-count">({user.groups.length})</span>
119
+ </span>
120
+ </h3>
121
+ <ul className="um-groups">
122
+ {user.groups.map((group, index) => {
123
+ const name = typeof group === "string" ? group : group?.name;
124
+ const alias =
125
+ typeof group === "string" ? group : group?.alias || name;
126
+ const namedGroup = groups.find(
127
+ (candidate) => name != null && candidate.name === name,
128
+ );
129
+ const aliasGroups = groups.filter(
130
+ (candidate) => alias && candidate.alias === alias,
131
+ );
132
+ const id =
133
+ group?.id ??
134
+ namedGroup?.id ??
135
+ (aliasGroups.length === 1 ? aliasGroups[0].id : undefined);
136
+ return (
137
+ <li key={id ?? index} className="um-group-chip">
138
+ <UserGroupLink id={id} name={getGroupName(group)} />
139
+ </li>
140
+ );
141
+ })}
142
+ </ul>
143
+ </section>
144
+ ) : (
145
+ <p className="um-empty">
146
+ <Icon name="users" aria-hidden="true" />
147
+ <FormattedMessage id="user.info.no_groups" />
148
+ </p>
149
+ )}
150
+
151
+ <div className="um-permissions">
152
+ {hasAcls ? (
153
+ <div className="um-acl-section">
154
+ <div className="um-acl-toolbar">
155
+ <button
156
+ type="button"
157
+ className="um-acl-header"
158
+ aria-expanded={aclsOpen}
159
+ aria-controls={aclsPanelId}
160
+ onClick={() => setAclsOpen((open) => !open)}
161
+ >
162
+ <span className="um-acl-icon--primary">
163
+ <Icon name="shield alternate" />
164
+ </span>
165
+ <span className="um-acl-title">
166
+ <FormattedMessage id="user.info.direct_permissions" />
167
+ <span className="um-acl-count">
168
+ ({new Set(acls.map(({ role }) => role)).size})
169
+ </span>
170
+ </span>
171
+ <Icon name={aclsOpen ? "chevron up" : "chevron down"} />
172
+ </button>
173
+ <div className="um-acl-grouping">
174
+ <Checkbox
175
+ toggle
176
+ label={formatMessage({ id: "user.info.group_by_role" })}
177
+ checked={groupByRole}
178
+ onChange={(_event, { checked }) => setGroupByRole(checked)}
179
+ />
180
+ </div>
181
+ </div>
182
+ {aclsOpen && (
183
+ <div
184
+ id={aclsPanelId}
185
+ className="um-acl-panel"
186
+ role="region"
187
+ aria-label={formatMessage({
188
+ id: "user.info.direct_permissions",
189
+ })}
190
+ tabIndex={0}
191
+ >
192
+ <UserAcls
193
+ acls={acls}
194
+ groupBy={groupByRole ? "role" : "domain"}
195
+ />
196
+ </div>
197
+ )}
198
+ </div>
199
+ ) : (
200
+ <p className="um-empty">
201
+ <Icon name="shield alternate" aria-hidden="true" />
202
+ <FormattedMessage id="user.info.no_permissions" />
203
+ </p>
204
+ )}
205
+
206
+ {hasGroupAcls ? (
207
+ <div className="um-acl-section um-acl-section--groups">
208
+ <button
209
+ type="button"
210
+ className="um-acl-header"
211
+ aria-expanded={groupAclsOpen}
212
+ aria-controls={groupAclsPanelId}
213
+ onClick={() => setGroupAclsOpen((open) => !open)}
214
+ >
215
+ <span className="um-acl-icon--groups">
216
+ <Icon name="users" />
217
+ </span>
218
+ <span className="um-acl-title">
219
+ <FormattedMessage id="user.info.group_permissions" />
220
+ <span className="um-acl-count">({groupAcls.length})</span>
221
+ </span>
222
+ <Icon name={groupAclsOpen ? "chevron up" : "chevron down"} />
223
+ </button>
224
+ {groupAclsOpen && (
225
+ <div
226
+ id={groupAclsPanelId}
227
+ className="um-acl-panel"
228
+ role="region"
229
+ aria-label={formatMessage({
230
+ id: "user.info.group_permissions",
231
+ })}
232
+ tabIndex={0}
233
+ >
234
+ <UserGroupAcls acls={groupAcls} />
235
+ </div>
236
+ )}
237
+ </div>
238
+ ) : (
239
+ <p className="um-empty">
240
+ <Icon name="users" aria-hidden="true" />
241
+ <FormattedMessage id="user.info.no_group_permissions" />
242
+ </p>
243
+ )}
244
+ </div>
245
+ </TrueModal>
246
+ );
247
+ };
248
+
249
+ UserInfoModal.propTypes = {
250
+ user: PropTypes.object,
251
+ groups: PropTypes.array,
252
+ acls: PropTypes.array,
253
+ groupAcls: PropTypes.array,
254
+ onClose: PropTypes.func.isRequired,
255
+ };
256
+
257
+ const UserInfoModalWithGroups = (props) => {
258
+ const { groups } = useGroups();
259
+ return <UserInfoModal {...props} groups={groups} />;
260
+ };
261
+
262
+ const mapStateToProps = (state) => ({
263
+ user: state.user,
264
+ acls: getUserAcls(state),
265
+ groupAcls: getUserGroupAcls(state),
266
+ });
267
+
268
+ 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>