@firecms/user_management 3.0.1 → 3.1.0-canary.02232f4
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 +7 -10
- package/dist/components/users/UserDetailsForm.d.ts +9 -13
- package/dist/index.es.js +1922 -969
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +1920 -967
- package/dist/index.umd.js.map +1 -1
- package/package.json +9 -9
- package/src/admin_views.tsx +2 -3
- package/src/components/roles/RolesDetailsForm.tsx +50 -73
- package/src/components/roles/RolesTable.tsx +16 -14
- package/src/components/roles/RolesView.tsx +6 -7
- package/src/components/users/UserDetailsForm.tsx +47 -24
- package/src/components/users/UsersTable.tsx +76 -52
- package/src/components/users/UsersView.tsx +6 -7
- package/src/hooks/useBuildUserManagement.tsx +13 -5
- package/src/useUserManagementPlugin.tsx +8 -7
|
@@ -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) => {
|
|
@@ -142,7 +147,29 @@ export function UserDetailsForm({
|
|
|
142
147
|
}}>
|
|
143
148
|
|
|
144
149
|
<DialogTitle variant={"h4"} gutterBottom={false}>
|
|
145
|
-
|
|
150
|
+
<div className="flex items-center justify-between">
|
|
151
|
+
<div>{t("user")}</div>
|
|
152
|
+
{!isNewUser && userProp?.uid && (
|
|
153
|
+
<div className="flex items-center gap-2">
|
|
154
|
+
<span className={"text-xs font-mono text-surface-accent-500 dark:text-surface-accent-400 font-normal"}>
|
|
155
|
+
{userProp.uid}
|
|
156
|
+
</span>
|
|
157
|
+
<Tooltip title={t("copy")}>
|
|
158
|
+
<IconButton
|
|
159
|
+
size={"smallest"}
|
|
160
|
+
onClick={() => {
|
|
161
|
+
navigator.clipboard.writeText(userProp.uid!);
|
|
162
|
+
snackbarController.open({
|
|
163
|
+
type: "success",
|
|
164
|
+
message: t("copied")
|
|
165
|
+
});
|
|
166
|
+
}}>
|
|
167
|
+
<CopyIcon size={"smallest"}/>
|
|
168
|
+
</IconButton>
|
|
169
|
+
</Tooltip>
|
|
170
|
+
</div>
|
|
171
|
+
)}
|
|
172
|
+
</div>
|
|
146
173
|
</DialogTitle>
|
|
147
174
|
<DialogContent className="h-full flex-grow">
|
|
148
175
|
|
|
@@ -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) => {
|
|
@@ -205,21 +232,17 @@ export function UserDetailsForm({
|
|
|
205
232
|
<DialogActions>
|
|
206
233
|
|
|
207
234
|
<Button variant={"text"}
|
|
208
|
-
color={"primary"}
|
|
209
235
|
onClick={() => {
|
|
210
236
|
handleClose();
|
|
211
|
-
}}>
|
|
212
|
-
Cancel
|
|
213
|
-
</Button>
|
|
237
|
+
}}>{t("cancel")}</Button>
|
|
214
238
|
|
|
215
239
|
<LoadingButton
|
|
216
240
|
variant="filled"
|
|
217
|
-
color="primary"
|
|
218
241
|
type="submit"
|
|
219
242
|
disabled={!dirty}
|
|
220
243
|
loading={isSubmitting}
|
|
221
244
|
>
|
|
222
|
-
{isNewUser ? "
|
|
245
|
+
{isNewUser ? t("create_user") : t("update")}
|
|
223
246
|
</LoadingButton>
|
|
224
247
|
</DialogActions>
|
|
225
248
|
</form>
|
|
@@ -8,9 +8,11 @@ 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 {
|
|
15
|
+
Avatar,
|
|
14
16
|
Button,
|
|
15
17
|
CenteredView,
|
|
16
18
|
DeleteIcon,
|
|
@@ -30,6 +32,7 @@ import { PersistedUser } from "../../types";
|
|
|
30
32
|
export function UsersTable({ onUserClicked }: {
|
|
31
33
|
onUserClicked: (user: User) => void;
|
|
32
34
|
}) {
|
|
35
|
+
const { t } = useTranslation();
|
|
33
36
|
|
|
34
37
|
const {
|
|
35
38
|
users,
|
|
@@ -53,11 +56,12 @@ export function UsersTable({ onUserClicked }: {
|
|
|
53
56
|
<Table className={"w-full"}>
|
|
54
57
|
|
|
55
58
|
<TableHeader>
|
|
56
|
-
<TableCell className="
|
|
57
|
-
<TableCell>
|
|
58
|
-
<TableCell>
|
|
59
|
-
<TableCell>
|
|
60
|
-
<TableCell>
|
|
59
|
+
<TableCell className="w-12"></TableCell>
|
|
60
|
+
<TableCell>{t("email")}</TableCell>
|
|
61
|
+
<TableCell>{t("name")}</TableCell>
|
|
62
|
+
<TableCell>{t("roles")}</TableCell>
|
|
63
|
+
<TableCell>{t("created_on")}</TableCell>
|
|
64
|
+
<TableCell className="w-12"></TableCell>
|
|
61
65
|
</TableHeader>
|
|
62
66
|
<TableBody>
|
|
63
67
|
{users && users.map((user) => {
|
|
@@ -73,32 +77,53 @@ export function UsersTable({ onUserClicked }: {
|
|
|
73
77
|
onUserClicked(user);
|
|
74
78
|
}}
|
|
75
79
|
>
|
|
76
|
-
<TableCell className={"w-
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
80
|
+
<TableCell className={"w-12"}>
|
|
81
|
+
<Avatar
|
|
82
|
+
src={user.photoURL ?? undefined}
|
|
83
|
+
outerClassName="w-8 h-8 min-w-8 min-h-8 p-0"
|
|
84
|
+
className="text-sm"
|
|
85
|
+
hover={false}
|
|
86
|
+
>
|
|
87
|
+
{user.displayName
|
|
88
|
+
? user.displayName[0].toUpperCase()
|
|
89
|
+
: (user.email ? user.email[0].toUpperCase() : "U")}
|
|
90
|
+
</Avatar>
|
|
91
|
+
</TableCell>
|
|
92
|
+
<TableCell>
|
|
93
|
+
<div className="flex flex-col">
|
|
94
|
+
<div>{user.email}</div>
|
|
95
|
+
{user.uid && (
|
|
96
|
+
<div className="text-xs text-surface-accent-500 dark:text-surface-accent-400 font-mono mt-1">
|
|
97
|
+
{user.uid}
|
|
98
|
+
</div>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
89
101
|
</TableCell>
|
|
90
|
-
<TableCell>{user.email}</TableCell>
|
|
91
102
|
<TableCell className={"font-medium align-left"}>{user.displayName}</TableCell>
|
|
92
103
|
<TableCell className="align-left">
|
|
93
104
|
{userRoles
|
|
94
105
|
? <div className="flex flex-wrap gap-2">
|
|
95
106
|
{userRoles.map(userRole =>
|
|
96
|
-
<RoleChip key={userRole?.id} role={userRole}/>
|
|
107
|
+
<RoleChip key={userRole?.id} role={userRole} />
|
|
97
108
|
)}
|
|
98
109
|
</div>
|
|
99
110
|
: null}
|
|
100
111
|
</TableCell>
|
|
101
112
|
<TableCell>{formattedDate}</TableCell>
|
|
113
|
+
<TableCell className={"w-12"}>
|
|
114
|
+
<Tooltip
|
|
115
|
+
asChild={true}
|
|
116
|
+
title={t("delete_this_user")}>
|
|
117
|
+
<IconButton
|
|
118
|
+
size={"smallest"}
|
|
119
|
+
onClick={(event) => {
|
|
120
|
+
event.stopPropagation();
|
|
121
|
+
return setUserToBeDeleted(user);
|
|
122
|
+
}}>
|
|
123
|
+
<DeleteIcon size={"small"} />
|
|
124
|
+
</IconButton>
|
|
125
|
+
</Tooltip>
|
|
126
|
+
</TableCell>
|
|
102
127
|
</TableRow>
|
|
103
128
|
);
|
|
104
129
|
})}
|
|
@@ -107,38 +132,37 @@ export function UsersTable({ onUserClicked }: {
|
|
|
107
132
|
<TableCell colspan={6}>
|
|
108
133
|
<CenteredView className={"flex flex-col gap-4 my-8 items-center"}>
|
|
109
134
|
<Typography variant={"label"}>
|
|
110
|
-
|
|
135
|
+
{t("no_users_yet")}
|
|
111
136
|
</Typography>
|
|
112
|
-
<Button
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
137
|
+
<Button
|
|
138
|
+
onClick={() => {
|
|
139
|
+
if (!authController.user?.uid) {
|
|
140
|
+
throw Error("UsersTable, authController misconfiguration");
|
|
141
|
+
}
|
|
142
|
+
saveUser({
|
|
143
|
+
uid: authController.user?.uid,
|
|
144
|
+
email: authController.user?.email,
|
|
145
|
+
displayName: authController.user?.displayName,
|
|
146
|
+
photoURL: authController.user?.photoURL,
|
|
147
|
+
providerId: authController.user?.providerId,
|
|
148
|
+
isAnonymous: authController.user?.isAnonymous,
|
|
149
|
+
roles: [{ id: "admin", name: "Admin" }],
|
|
150
|
+
created_on: new Date()
|
|
151
|
+
})
|
|
152
|
+
.then(() => {
|
|
153
|
+
snackbarController.open({
|
|
154
|
+
type: "success",
|
|
155
|
+
message: "User added successfully"
|
|
156
|
+
})
|
|
126
157
|
})
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
})
|
|
158
|
+
.catch((error) => {
|
|
159
|
+
snackbarController.open({
|
|
160
|
+
type: "error",
|
|
161
|
+
message: "Error adding user: " + error.message,
|
|
132
162
|
})
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
message: "Error adding user: " + error.message,
|
|
137
|
-
})
|
|
138
|
-
});
|
|
139
|
-
}}>
|
|
140
|
-
|
|
141
|
-
Add the logged user as an admin
|
|
163
|
+
});
|
|
164
|
+
}}>
|
|
165
|
+
{t("add_logged_user_as_admin")}
|
|
142
166
|
</Button>
|
|
143
167
|
</CenteredView>
|
|
144
168
|
</TableCell>
|
|
@@ -171,7 +195,7 @@ export function UsersTable({ onUserClicked }: {
|
|
|
171
195
|
onCancel={() => {
|
|
172
196
|
setUserToBeDeleted(undefined);
|
|
173
197
|
}}
|
|
174
|
-
title={<>
|
|
175
|
-
body={<>
|
|
198
|
+
title={<>{t("delete_confirmation_title")}</>}
|
|
199
|
+
body={<>{t("delete_user_confirmation")}</>} />
|
|
176
200
|
</div>);
|
|
177
201
|
}
|
|
@@ -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}/>
|
|
@@ -298,12 +298,17 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
298
298
|
|
|
299
299
|
const mgmtUser = users.find(u => u.email?.toLowerCase() === user?.email?.toLowerCase());
|
|
300
300
|
if (mgmtUser) {
|
|
301
|
-
// check if the uid
|
|
302
|
-
|
|
303
|
-
|
|
301
|
+
// check if the uid or photoURL needs to be updated in the user management system
|
|
302
|
+
const needsUidUpdate = mgmtUser.uid !== user.uid;
|
|
303
|
+
const needsPhotoUpdate = user.photoURL && mgmtUser.photoURL !== user.photoURL;
|
|
304
|
+
|
|
305
|
+
if (needsUidUpdate || needsPhotoUpdate) {
|
|
306
|
+
const updateReason = needsUidUpdate ? "uid" : "photoURL";
|
|
307
|
+
console.debug(`User ${updateReason} has changed, updating user in user management system`);
|
|
304
308
|
saveUser({
|
|
305
309
|
...mgmtUser,
|
|
306
|
-
uid: user.uid
|
|
310
|
+
uid: user.uid,
|
|
311
|
+
...(needsPhotoUpdate ? { photoURL: user.photoURL } : {})
|
|
307
312
|
}).then(() => {
|
|
308
313
|
console.debug("User updated in user management system", mgmtUser);
|
|
309
314
|
}).catch(e => {
|
|
@@ -322,7 +327,10 @@ export function useBuildUserManagement<CONTROLLER extends AuthController<any> =
|
|
|
322
327
|
|
|
323
328
|
const userRoleIds = userRoles?.map(r => r.id);
|
|
324
329
|
useEffect(() => {
|
|
325
|
-
console.debug("Setting user roles", {
|
|
330
|
+
console.debug("Setting user roles", {
|
|
331
|
+
userRoles,
|
|
332
|
+
roles
|
|
333
|
+
});
|
|
326
334
|
authController.setUserRoles?.(userRoles ?? []);
|
|
327
335
|
}, [userRoleIds]);
|
|
328
336
|
|
|
@@ -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,22 +46,23 @@ 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
|
+
|
|
65
66
|
onClick={() => {
|
|
66
67
|
if (!authController.user?.uid) {
|
|
67
68
|
throw Error("UsersTable, authController misconfiguration");
|