@firecms/user_management 3.1.0 → 3.2.0-canary.44dc65b
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.
- package/dist/components/roles/RolesDetailsForm.d.ts +1 -1
- package/dist/components/users/UserDetailsForm.d.ts +1 -1
- package/dist/index.es.js +1889 -956
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1887 -954
- package/dist/index.umd.js.map +1 -1
- package/package.json +5 -5
- package/src/admin_views.tsx +2 -3
- package/src/components/roles/RolesDetailsForm.tsx +50 -71
- package/src/components/roles/RolesTable.tsx +15 -13
- package/src/components/roles/RolesView.tsx +6 -7
- package/src/components/users/UserDetailsForm.tsx +49 -24
- package/src/components/users/UsersTable.tsx +12 -11
- package/src/components/users/UsersView.tsx +6 -7
- package/src/useUserManagementPlugin.tsx +7 -6
|
@@ -3,35 +3,40 @@ import * as Yup from "yup";
|
|
|
3
3
|
import {
|
|
4
4
|
Button,
|
|
5
5
|
CheckIcon,
|
|
6
|
+
Chip,
|
|
7
|
+
CopyIcon,
|
|
6
8
|
Dialog,
|
|
7
9
|
DialogActions,
|
|
8
10
|
DialogContent,
|
|
9
11
|
DialogTitle,
|
|
12
|
+
IconButton,
|
|
10
13
|
LoadingButton,
|
|
11
14
|
MultiSelect,
|
|
12
15
|
MultiSelectItem,
|
|
13
16
|
TextField,
|
|
17
|
+
Tooltip,
|
|
14
18
|
} from "@firecms/ui";
|
|
15
|
-
import { FieldCaption, Role, useAuthController, User, useSnackbarController
|
|
19
|
+
import { FieldCaption, Role, useAuthController, User, useSnackbarController, useTranslation
|
|
20
|
+
} from "@firecms/core";
|
|
16
21
|
import { Formex, useCreateFormex } from "@firecms/formex";
|
|
17
22
|
|
|
18
23
|
import { areRolesEqual } from "../../utils";
|
|
19
24
|
import { useUserManagement } from "../../hooks";
|
|
20
25
|
import { RoleChip } from "../roles";
|
|
21
26
|
|
|
22
|
-
export const
|
|
23
|
-
displayName: Yup.string().required("
|
|
24
|
-
email: Yup.string().email().required("
|
|
27
|
+
export const getUserYupSchema = (t: any) => Yup.object().shape({
|
|
28
|
+
displayName: Yup.string().required(t("required")),
|
|
29
|
+
email: Yup.string().email().required(t("required")),
|
|
25
30
|
roles: Yup.array().min(1)
|
|
26
31
|
});
|
|
27
32
|
|
|
28
|
-
function canUserBeEdited(loggedUser: User, user: User, users: User[], roles: Role[], prevUser?: User) {
|
|
33
|
+
function canUserBeEdited(loggedUser: User, user: User, users: User[], roles: Role[], t: any, prevUser?: User) {
|
|
29
34
|
const admins = users.filter(u => u.roles?.map(r => r.id).includes("admin"));
|
|
30
35
|
const loggedUserIsAdmin = loggedUser.roles?.map(r => r.id).includes("admin");
|
|
31
36
|
const didRolesChange = !prevUser || !areRolesEqual(prevUser.roles ?? [], user.roles ?? []);
|
|
32
37
|
|
|
33
38
|
if (didRolesChange && !loggedUserIsAdmin) {
|
|
34
|
-
throw new Error("
|
|
39
|
+
throw new Error(t("only_admins_change_roles"));
|
|
35
40
|
}
|
|
36
41
|
|
|
37
42
|
// was the admin role removed
|
|
@@ -39,7 +44,7 @@ function canUserBeEdited(loggedUser: User, user: User, users: User[], roles: Rol
|
|
|
39
44
|
|
|
40
45
|
// avoid removing the last admin
|
|
41
46
|
if (adminRoleRemoved && admins.length === 1) {
|
|
42
|
-
throw new Error("
|
|
47
|
+
throw new Error(t("must_be_at_least_one_admin"));
|
|
43
48
|
}
|
|
44
49
|
return true;
|
|
45
50
|
}
|
|
@@ -53,7 +58,7 @@ export function UserDetailsForm({
|
|
|
53
58
|
user?: User,
|
|
54
59
|
handleClose: () => void
|
|
55
60
|
}) {
|
|
56
|
-
|
|
61
|
+
const { t } = useTranslation();
|
|
57
62
|
const snackbarController = useSnackbarController();
|
|
58
63
|
const {
|
|
59
64
|
user: loggedInUser
|
|
@@ -67,15 +72,15 @@ export function UserDetailsForm({
|
|
|
67
72
|
|
|
68
73
|
const onUserUpdated = useCallback((savedUser: User): Promise<User> => {
|
|
69
74
|
if (!loggedInUser) {
|
|
70
|
-
throw new Error("
|
|
75
|
+
throw new Error(t("logged_user_not_found"));
|
|
71
76
|
}
|
|
72
77
|
try {
|
|
73
|
-
canUserBeEdited(loggedInUser, savedUser, users, roles, userProp);
|
|
78
|
+
canUserBeEdited(loggedInUser, savedUser, users, roles, t, userProp);
|
|
74
79
|
return saveUser(savedUser);
|
|
75
80
|
} catch (e: any) {
|
|
76
81
|
return Promise.reject(e);
|
|
77
82
|
}
|
|
78
|
-
}, [roles, saveUser, userProp, users, loggedInUser]);
|
|
83
|
+
}, [roles, saveUser, userProp, users, loggedInUser, t]);
|
|
79
84
|
|
|
80
85
|
const formex = useCreateFormex({
|
|
81
86
|
initialValues: userProp ?? {
|
|
@@ -84,7 +89,7 @@ export function UserDetailsForm({
|
|
|
84
89
|
roles: roles.filter(r => r.id === "editor")
|
|
85
90
|
} as User,
|
|
86
91
|
validation: (values) => {
|
|
87
|
-
return
|
|
92
|
+
return getUserYupSchema(t).validate(values, { abortEarly: false })
|
|
88
93
|
.then(() => {
|
|
89
94
|
return {};
|
|
90
95
|
}).catch((e) => {
|
|
@@ -141,13 +146,35 @@ export function UserDetailsForm({
|
|
|
141
146
|
height: "100%"
|
|
142
147
|
}}>
|
|
143
148
|
|
|
144
|
-
<DialogTitle variant={"h4"} gutterBottom={false}>
|
|
145
|
-
User
|
|
146
|
-
</DialogTitle>
|
|
149
|
+
<DialogTitle variant={"h4"} gutterBottom={false}>{t("user")}</DialogTitle>
|
|
147
150
|
<DialogContent className="h-full flex-grow">
|
|
148
151
|
|
|
149
152
|
<div className={"grid grid-cols-12 gap-4"}>
|
|
150
153
|
|
|
154
|
+
{!isNewUser && userProp?.uid && (
|
|
155
|
+
<div className={"col-span-12"}>
|
|
156
|
+
<div className={"flex items-center gap-2"}>
|
|
157
|
+
<span className={"text-sm text-surface-accent-600 dark:text-surface-accent-400"}>{t("user_id")}</span>
|
|
158
|
+
<Chip size={"small"}>
|
|
159
|
+
{userProp.uid}
|
|
160
|
+
</Chip>
|
|
161
|
+
<Tooltip title={t("copy")}>
|
|
162
|
+
<IconButton
|
|
163
|
+
size={"small"}
|
|
164
|
+
onClick={() => {
|
|
165
|
+
navigator.clipboard.writeText(userProp.uid);
|
|
166
|
+
snackbarController.open({
|
|
167
|
+
type: "success",
|
|
168
|
+
message: t("copied")
|
|
169
|
+
});
|
|
170
|
+
}}>
|
|
171
|
+
<CopyIcon size={"small"}/>
|
|
172
|
+
</IconButton>
|
|
173
|
+
</Tooltip>
|
|
174
|
+
</div>
|
|
175
|
+
</div>
|
|
176
|
+
)}
|
|
177
|
+
|
|
151
178
|
<div className={"col-span-12"}>
|
|
152
179
|
<TextField
|
|
153
180
|
name="displayName"
|
|
@@ -156,10 +183,10 @@ export function UserDetailsForm({
|
|
|
156
183
|
value={values.displayName ?? ""}
|
|
157
184
|
onChange={handleChange}
|
|
158
185
|
aria-describedby="name-helper-text"
|
|
159
|
-
label="
|
|
186
|
+
label={t("name")}
|
|
160
187
|
/>
|
|
161
188
|
<FieldCaption>
|
|
162
|
-
{submitCount > 0 && Boolean(errors.displayName) ? errors.displayName : "
|
|
189
|
+
{submitCount > 0 && Boolean(errors.displayName) ? errors.displayName : t("name_of_this_user")}
|
|
163
190
|
</FieldCaption>
|
|
164
191
|
</div>
|
|
165
192
|
<div className={"col-span-12"}>
|
|
@@ -170,16 +197,16 @@ export function UserDetailsForm({
|
|
|
170
197
|
value={values.email ?? ""}
|
|
171
198
|
onChange={handleChange}
|
|
172
199
|
aria-describedby="email-helper-text"
|
|
173
|
-
label="
|
|
200
|
+
label={t("email")}
|
|
174
201
|
/>
|
|
175
202
|
<FieldCaption>
|
|
176
|
-
{submitCount > 0 && Boolean(errors.email) ? errors.email : "
|
|
203
|
+
{submitCount > 0 && Boolean(errors.email) ? errors.email : t("email_of_this_user")}
|
|
177
204
|
</FieldCaption>
|
|
178
205
|
</div>
|
|
179
206
|
<div className={"col-span-12"}>
|
|
180
207
|
<MultiSelect
|
|
181
208
|
className={"w-full"}
|
|
182
|
-
label="
|
|
209
|
+
label={t("roles")}
|
|
183
210
|
value={values.roles?.map(r => r.id) ?? []}
|
|
184
211
|
onValueChange={(value: string[]) => setFieldValue("roles", value.map(id => roles.find(r => r.id === id) as Role))}
|
|
185
212
|
// renderValue={(value: string) => {
|
|
@@ -207,9 +234,7 @@ export function UserDetailsForm({
|
|
|
207
234
|
<Button variant={"text"}
|
|
208
235
|
onClick={() => {
|
|
209
236
|
handleClose();
|
|
210
|
-
}}>
|
|
211
|
-
Cancel
|
|
212
|
-
</Button>
|
|
237
|
+
}}>{t("cancel")}</Button>
|
|
213
238
|
|
|
214
239
|
<LoadingButton
|
|
215
240
|
variant="filled"
|
|
@@ -217,7 +242,7 @@ export function UserDetailsForm({
|
|
|
217
242
|
disabled={!dirty}
|
|
218
243
|
loading={isSubmitting}
|
|
219
244
|
>
|
|
220
|
-
{isNewUser ? "
|
|
245
|
+
{isNewUser ? t("create_user") : t("update")}
|
|
221
246
|
</LoadingButton>
|
|
222
247
|
</DialogActions>
|
|
223
248
|
</form>
|
|
@@ -8,7 +8,8 @@ import {
|
|
|
8
8
|
ConfirmationDialog, Role,
|
|
9
9
|
useAuthController,
|
|
10
10
|
useCustomizationController, User,
|
|
11
|
-
useSnackbarController
|
|
11
|
+
useSnackbarController,
|
|
12
|
+
useTranslation
|
|
12
13
|
} from "@firecms/core";
|
|
13
14
|
import {
|
|
14
15
|
Avatar,
|
|
@@ -31,6 +32,7 @@ import { PersistedUser } from "../../types";
|
|
|
31
32
|
export function UsersTable({ onUserClicked }: {
|
|
32
33
|
onUserClicked: (user: User) => void;
|
|
33
34
|
}) {
|
|
35
|
+
const { t } = useTranslation();
|
|
34
36
|
|
|
35
37
|
const {
|
|
36
38
|
users,
|
|
@@ -55,10 +57,10 @@ export function UsersTable({ onUserClicked }: {
|
|
|
55
57
|
|
|
56
58
|
<TableHeader>
|
|
57
59
|
<TableCell className="w-12"></TableCell>
|
|
58
|
-
<TableCell>
|
|
59
|
-
<TableCell>
|
|
60
|
-
<TableCell>
|
|
61
|
-
<TableCell>
|
|
60
|
+
<TableCell>{t("email")}</TableCell>
|
|
61
|
+
<TableCell>{t("name")}</TableCell>
|
|
62
|
+
<TableCell>{t("roles")}</TableCell>
|
|
63
|
+
<TableCell>{t("created_on")}</TableCell>
|
|
62
64
|
<TableCell className="w-12"></TableCell>
|
|
63
65
|
</TableHeader>
|
|
64
66
|
<TableBody>
|
|
@@ -102,7 +104,7 @@ export function UsersTable({ onUserClicked }: {
|
|
|
102
104
|
<TableCell className={"w-12"}>
|
|
103
105
|
<Tooltip
|
|
104
106
|
asChild={true}
|
|
105
|
-
title={"
|
|
107
|
+
title={t("delete_this_user")}>
|
|
106
108
|
<IconButton
|
|
107
109
|
size={"smallest"}
|
|
108
110
|
onClick={(event) => {
|
|
@@ -121,7 +123,7 @@ export function UsersTable({ onUserClicked }: {
|
|
|
121
123
|
<TableCell colspan={6}>
|
|
122
124
|
<CenteredView className={"flex flex-col gap-4 my-8 items-center"}>
|
|
123
125
|
<Typography variant={"label"}>
|
|
124
|
-
|
|
126
|
+
{t("no_users_yet")}
|
|
125
127
|
</Typography>
|
|
126
128
|
<Button
|
|
127
129
|
onClick={() => {
|
|
@@ -151,8 +153,7 @@ export function UsersTable({ onUserClicked }: {
|
|
|
151
153
|
})
|
|
152
154
|
});
|
|
153
155
|
}}>
|
|
154
|
-
|
|
155
|
-
Add the logged user as an admin
|
|
156
|
+
{t("add_logged_user_as_admin")}
|
|
156
157
|
</Button>
|
|
157
158
|
</CenteredView>
|
|
158
159
|
</TableCell>
|
|
@@ -185,7 +186,7 @@ export function UsersTable({ onUserClicked }: {
|
|
|
185
186
|
onCancel={() => {
|
|
186
187
|
setUserToBeDeleted(undefined);
|
|
187
188
|
}}
|
|
188
|
-
title={<>
|
|
189
|
-
body={<>
|
|
189
|
+
title={<>{t("delete_confirmation_title")}</>}
|
|
190
|
+
body={<>{t("delete_user_confirmation")}</>} />
|
|
190
191
|
</div>);
|
|
191
192
|
}
|
|
@@ -4,9 +4,12 @@ import { UsersTable } from "./UsersTable";
|
|
|
4
4
|
import { UserDetailsForm } from "./UserDetailsForm";
|
|
5
5
|
import React, { useCallback, useState } from "react";
|
|
6
6
|
import { useUserManagement } from "../../hooks/useUserManagement";
|
|
7
|
-
import { User
|
|
7
|
+
import { User, useTranslation
|
|
8
|
+
} from "@firecms/core";
|
|
8
9
|
|
|
9
10
|
export const UsersView = function UsersView({ children }: { children?: React.ReactNode }) {
|
|
11
|
+
const { t } = useTranslation();
|
|
12
|
+
|
|
10
13
|
|
|
11
14
|
const [dialogOpen, setDialogOpen] = useState<boolean>(false);
|
|
12
15
|
const [selectedUser, setSelectedUser] = useState<User | undefined>();
|
|
@@ -39,15 +42,11 @@ export const UsersView = function UsersView({ children }: { children?: React.Rea
|
|
|
39
42
|
className="flex items-center mt-12">
|
|
40
43
|
<Typography gutterBottom variant="h4"
|
|
41
44
|
className="flex-grow"
|
|
42
|
-
component="h4">
|
|
43
|
-
Users
|
|
44
|
-
</Typography>
|
|
45
|
+
component="h4">{t("users")}</Typography>
|
|
45
46
|
<Button
|
|
46
47
|
size={"large"}
|
|
47
48
|
startIcon={<AddIcon/>}
|
|
48
|
-
onClick={handleAddUser}>
|
|
49
|
-
Add user
|
|
50
|
-
</Button>
|
|
49
|
+
onClick={handleAddUser}>{t("add_user")}</Button>
|
|
51
50
|
</div>
|
|
52
51
|
|
|
53
52
|
<UsersTable onUserClicked={onUserClicked}/>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { FireCMSPlugin, useAuthController, User, useSnackbarController } from "@firecms/core";
|
|
1
|
+
import { FireCMSPlugin, useAuthController, User, useSnackbarController, useTranslation } from "@firecms/core";
|
|
2
2
|
import { UserManagementProvider } from "./UserManagementProvider";
|
|
3
3
|
import { UserManagement } from "./types";
|
|
4
4
|
import { AddIcon, Button, Paper, Typography } from "@firecms/ui";
|
|
@@ -46,19 +46,20 @@ export function IntroWidget({
|
|
|
46
46
|
|
|
47
47
|
const authController = useAuthController();
|
|
48
48
|
const snackbarController = useSnackbarController();
|
|
49
|
+
const { t } = useTranslation();
|
|
49
50
|
|
|
50
51
|
const buttonLabel = noUsers && noRoles
|
|
51
|
-
? "
|
|
52
|
+
? t("create_default_roles_and_add_admin")
|
|
52
53
|
: noUsers
|
|
53
|
-
? "
|
|
54
|
-
: noRoles ? "
|
|
54
|
+
? t("add_current_user_as_admin")
|
|
55
|
+
: noRoles ? t("create_default_roles") : undefined;
|
|
55
56
|
|
|
56
57
|
return (
|
|
57
58
|
<Paper
|
|
58
59
|
className={"my-4 flex flex-col px-4 py-6 bg-white dark:bg-surface-accent-800 gap-2"}>
|
|
59
|
-
<Typography variant={"subtitle2"} className={"uppercase"}>
|
|
60
|
+
<Typography variant={"subtitle2"} className={"uppercase"}>{t("create_your_users_and_roles")}</Typography>
|
|
60
61
|
<Typography>
|
|
61
|
-
|
|
62
|
+
{t("no_users_or_roles_defined")}
|
|
62
63
|
</Typography>
|
|
63
64
|
<Button
|
|
64
65
|
|