Users: Adds user details tab and save/update functionality (#426)
* userDetails tab done, save function wip * userDetails update working * add cypress test * fix default value * PR feedback from Erik
This commit is contained in:
parent
b035143781
commit
28f1fdfda4
9 changed files with 273 additions and 81 deletions
|
@ -3,18 +3,31 @@ import LoginPage from "../support/pages/LoginPage";
|
|||
import CreateUserPage from "../support/pages/admin_console/manage/users/CreateUserPage";
|
||||
import Masthead from "../support/pages/admin_console/Masthead";
|
||||
import ListingPage from "../support/pages/admin_console/ListingPage";
|
||||
import UserDetailsPage from "../support/pages/admin_console/manage/users/UserDetailsPage";
|
||||
import ModalUtils from "../support/util/ModalUtils";
|
||||
|
||||
describe("Users test", () => {
|
||||
const loginPage = new LoginPage();
|
||||
const sidebarPage = new SidebarPage();
|
||||
const createUserPage = new CreateUserPage();
|
||||
const masthead = new Masthead();
|
||||
const modalUtils = new ModalUtils();
|
||||
const listingPage = new ListingPage();
|
||||
const userDetailsPage = new UserDetailsPage();
|
||||
|
||||
let itemId = "user_crud";
|
||||
|
||||
describe("User creation", () => {
|
||||
beforeEach(function () {
|
||||
beforeEach(() => {
|
||||
/*
|
||||
Prevent unpredictable 401 errors from failing individual tests.
|
||||
These are most often occurring during the login process:
|
||||
GET /admin/serverinfo/
|
||||
GET /admin/master/console/whoami
|
||||
*/
|
||||
cy.on("uncaught:exception", () => {
|
||||
return false;
|
||||
});
|
||||
cy.visit("");
|
||||
loginPage.logIn();
|
||||
sidebarPage.goToUsers();
|
||||
|
@ -39,12 +52,36 @@ describe("Users test", () => {
|
|||
|
||||
createUserPage.goToCreateUser();
|
||||
|
||||
createUserPage.fillRealmRoleData(itemId).save();
|
||||
createUserPage.createUser(itemId).save();
|
||||
|
||||
masthead.checkNotificationMessage("The user has been created");
|
||||
|
||||
sidebarPage.goToUsers();
|
||||
});
|
||||
|
||||
it("Go to user details test", function () {
|
||||
cy.wait(1000);
|
||||
listingPage.searchItem(itemId).itemExist(itemId);
|
||||
|
||||
cy.wait(1000);
|
||||
listingPage.goToItemDetails(itemId);
|
||||
|
||||
userDetailsPage.fillUserData().save();
|
||||
|
||||
masthead.checkNotificationMessage("The user has been saved");
|
||||
|
||||
sidebarPage.goToUsers();
|
||||
listingPage.searchItem(itemId).itemExist(itemId);
|
||||
|
||||
// Delete
|
||||
cy.wait(1000);
|
||||
listingPage.deleteItem(itemId);
|
||||
|
||||
modalUtils.checkModalTitle("Delete user?").confirmModal();
|
||||
|
||||
masthead.checkNotificationMessage("The user has been deleted");
|
||||
|
||||
listingPage.itemExist(itemId, false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,21 +3,23 @@ export default class CreateUserPage {
|
|||
usersEmptyState: string;
|
||||
emptyStateCreateUserBtn: string;
|
||||
searchPgCreateUserBtn: string;
|
||||
addUserBtn: string;
|
||||
saveBtn: string;
|
||||
cancelBtn: string;
|
||||
|
||||
constructor() {
|
||||
this.usernameInput = "#kc-username";
|
||||
|
||||
this.usersEmptyState = "[data-testid=empty-state]";
|
||||
this.emptyStateCreateUserBtn = "[data-testid=empty-primary-action]";
|
||||
this.searchPgCreateUserBtn = "[data-testid=create-new-user]";
|
||||
this.saveBtn = "[data-testid=create-user]";
|
||||
this.cancelBtn = "[data-testid=cancel-create-user]";
|
||||
this.usersEmptyState = "empty-state";
|
||||
this.emptyStateCreateUserBtn = "empty-primary-action";
|
||||
this.searchPgCreateUserBtn = "create-new-user";
|
||||
this.addUserBtn = "add-user";
|
||||
this.saveBtn = "create-user";
|
||||
this.cancelBtn = "cancel-create-user";
|
||||
}
|
||||
|
||||
//#region General Settings
|
||||
fillRealmRoleData(username: string) {
|
||||
createUser(username: string) {
|
||||
cy.get(this.usernameInput).clear();
|
||||
|
||||
if (username) {
|
||||
|
@ -30,12 +32,12 @@ export default class CreateUserPage {
|
|||
goToCreateUser() {
|
||||
cy.wait(100);
|
||||
cy.get("body").then((body) => {
|
||||
if (body.find(this.usersEmptyState).length > 0) {
|
||||
cy.get(this.emptyStateCreateUserBtn).click();
|
||||
if (body.find("[data-testid=empty-state]").length > 0) {
|
||||
cy.getId(this.emptyStateCreateUserBtn).click();
|
||||
} else if (body.find("[data-testid=search-users-title]").length > 0) {
|
||||
cy.get(this.searchPgCreateUserBtn).click();
|
||||
cy.getId(this.searchPgCreateUserBtn).click();
|
||||
} else {
|
||||
cy.get("[data-testid=add-user]").click();
|
||||
cy.getId(this.addUserBtn).click();
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -43,13 +45,13 @@ export default class CreateUserPage {
|
|||
}
|
||||
|
||||
save() {
|
||||
cy.get(this.saveBtn).click();
|
||||
cy.getId(this.saveBtn).click();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
cy.get(this.cancelBtn).click();
|
||||
cy.getId(this.cancelBtn).click();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import { RequiredActionAlias } from "keycloak-admin/lib/defs/requiredActionProviderRepresentation";
|
||||
|
||||
export default class UserDetailsPage {
|
||||
saveBtn: string;
|
||||
cancelBtn: string;
|
||||
emailInput: string;
|
||||
emailValue: string;
|
||||
firstNameInput: string;
|
||||
firstNameValue: string;
|
||||
lastNameInput: string;
|
||||
lastNameValue: string;
|
||||
enabledSwitch: string;
|
||||
enabledValue: boolean
|
||||
requiredUserActions: RequiredActionAlias[];
|
||||
|
||||
constructor() {
|
||||
this.saveBtn = "save-user";
|
||||
this.cancelBtn = "cancel-create-user";
|
||||
this.emailInput = "email-input"
|
||||
this.emailValue = "example" + "_" + (Math.random() + 1).toString(36).substring(7) + "@example.com";
|
||||
this.firstNameInput = "firstName-input"
|
||||
this.firstNameValue = "firstname";
|
||||
this.lastNameInput = "lastName-input"
|
||||
this.lastNameValue = "lastname";
|
||||
this.enabledSwitch = "user-enabled-switch"
|
||||
this.enabledValue = true;
|
||||
this.requiredUserActions = [RequiredActionAlias.UPDATE_PASSWORD]
|
||||
}
|
||||
|
||||
|
||||
fillUserData() {
|
||||
|
||||
cy.getId(this.emailInput).type(this.emailValue);
|
||||
cy.getId(this.firstNameInput).type(this.firstNameValue);
|
||||
cy.getId(this.lastNameInput).type(this.lastNameValue);
|
||||
cy.getId(this.enabledSwitch).check({ force: true });
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
save() {
|
||||
cy.getId(this.saveBtn).click();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
cancel() {
|
||||
cy.getId(this.cancelBtn).click();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
@ -45,9 +45,6 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
const [name, setName] = useState("");
|
||||
const adminClient = useAdminClient();
|
||||
const [selectedRows, setSelectedRows] = useState<RoleRepresentation[]>([]);
|
||||
const [allClientRoles, setAllClientRoles] = useState<RoleRepresentation[]>(
|
||||
[]
|
||||
);
|
||||
|
||||
const [isFilterDropdownOpen, setIsFilterDropdownOpen] = useState(false);
|
||||
const [filterType, setFilterType] = useState("roles");
|
||||
|
@ -116,9 +113,6 @@ export const AssociatedRolesModal = (props: AssociatedRolesModalProps) => {
|
|||
id,
|
||||
});
|
||||
|
||||
setAllClientRoles(rolesList);
|
||||
console.log(allClientRoles);
|
||||
|
||||
return alphabetize(rolesList).filter((role: RoleRepresentation) => {
|
||||
return (
|
||||
existingAdditionalRoles.find(
|
||||
|
|
|
@ -145,6 +145,12 @@ export const routes: RoutesFn = (t: TFunction) => [
|
|||
breadcrumb: t("users:createUser"),
|
||||
access: "manage-users",
|
||||
},
|
||||
{
|
||||
path: "/:realm/users/:id/:tab",
|
||||
component: UsersTabs,
|
||||
breadcrumb: t("users:userDetails"),
|
||||
access: "manage-users",
|
||||
},
|
||||
{
|
||||
path: "/:realm/sessions",
|
||||
component: SessionsSection,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
ActionGroup,
|
||||
Button,
|
||||
|
@ -10,20 +10,26 @@ import {
|
|||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, UseFormMethods } from "react-hook-form";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { useHistory, useParams } from "react-router-dom";
|
||||
import { FormAccess } from "../components/form-access/FormAccess";
|
||||
import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
||||
import { HelpItem } from "../components/help-enabler/HelpItem";
|
||||
import { useRealm } from "../context/realm-context/RealmContext";
|
||||
import { asyncStateFetch, useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useErrorHandler } from "react-error-boundary";
|
||||
import moment from "moment";
|
||||
|
||||
export type UserFormProps = {
|
||||
form: UseFormMethods<UserRepresentation>;
|
||||
save: (user: UserRepresentation) => void;
|
||||
editMode: boolean;
|
||||
timestamp?: number;
|
||||
};
|
||||
|
||||
export const UserForm = ({
|
||||
form: { handleSubmit, register, errors, watch, control },
|
||||
form: { handleSubmit, register, errors, watch, control, setValue, reset },
|
||||
save,
|
||||
editMode,
|
||||
}: UserFormProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { realm } = useRealm();
|
||||
|
@ -32,33 +38,56 @@ export const UserForm = ({
|
|||
isRequiredUserActionsDropdownOpen,
|
||||
setRequiredUserActionsDropdownOpen,
|
||||
] = useState(false);
|
||||
const [selected, setSelected] = useState<string[]>([]);
|
||||
const history = useHistory();
|
||||
const adminClient = useAdminClient();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const handleError = useErrorHandler();
|
||||
|
||||
const watchUsernameInput = watch("username");
|
||||
const [timestamp, setTimestamp] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (editMode) {
|
||||
return asyncStateFetch(
|
||||
() => adminClient.users.find({ username: id }),
|
||||
(user) => {
|
||||
setupForm(user[0]);
|
||||
},
|
||||
handleError
|
||||
);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const setupForm = (user: UserRepresentation) => {
|
||||
reset();
|
||||
Object.entries(user).map((entry) => {
|
||||
console.log(entry[0], entry[1]);
|
||||
if (entry[0] == "createdTimestamp") {
|
||||
setTimestamp(entry[1]);
|
||||
} else {
|
||||
setValue(entry[0], entry[1]);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const emailRegexPattern = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||
|
||||
const requiredUserActionsOptions = [
|
||||
<SelectOption key={0} value="Configure OTP">
|
||||
<SelectOption key={0} value="CONFIGURE_TOTP">
|
||||
{t("configureOTP")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={1} value="Update Password">
|
||||
<SelectOption key={1} value="UPDATE_PASSWORD">
|
||||
{t("updatePassword")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={2} value="Update Profile">
|
||||
<SelectOption key={2} value="UPDATE_PROFILE">
|
||||
{t("updateProfile")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={3} value="Verify Email">
|
||||
<SelectOption key={3} value="VERIFY_EMAIL">
|
||||
{t("verifyEmail")}
|
||||
</SelectOption>,
|
||||
<SelectOption key={4} value="Update User Locale">
|
||||
{t("updateUserLocale")}
|
||||
</SelectOption>,
|
||||
];
|
||||
|
||||
const clearSelection = () => {
|
||||
setSelected([]);
|
||||
setRequiredUserActionsDropdownOpen(false);
|
||||
};
|
||||
|
||||
|
@ -69,6 +98,40 @@ export const UserForm = ({
|
|||
role="manage-users"
|
||||
className="pf-u-mt-lg"
|
||||
>
|
||||
{editMode ? (
|
||||
<>
|
||||
<FormGroup
|
||||
label={t("id")}
|
||||
fieldId="kc-id"
|
||||
isRequired
|
||||
validated={errors.id ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: !editMode })}
|
||||
type="text"
|
||||
id="kc-id"
|
||||
name="id"
|
||||
isReadOnly={editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("createdAt")}
|
||||
fieldId="kc-created-at"
|
||||
isRequired
|
||||
validated={errors.createdTimestamp ? "error" : "default"}
|
||||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
value={moment(timestamp).format("MM/DD/YY hh:MM:ss A")}
|
||||
type="text"
|
||||
id="kc-created-at"
|
||||
name="createdTimestamp"
|
||||
isReadOnly={editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
</>
|
||||
) : (
|
||||
<FormGroup
|
||||
label={t("username")}
|
||||
fieldId="kc-username"
|
||||
|
@ -77,12 +140,14 @@ export const UserForm = ({
|
|||
helperTextInvalid={t("common:required")}
|
||||
>
|
||||
<TextInput
|
||||
ref={register({ required: true })}
|
||||
ref={register()}
|
||||
type="text"
|
||||
id="kc-username"
|
||||
name="username"
|
||||
isReadOnly={editMode}
|
||||
/>
|
||||
</FormGroup>
|
||||
)}
|
||||
<FormGroup
|
||||
label={t("email")}
|
||||
fieldId="kc-description"
|
||||
|
@ -96,6 +161,7 @@ export const UserForm = ({
|
|||
type="email"
|
||||
id="kc-email"
|
||||
name="email"
|
||||
data-testid="email-input"
|
||||
aria-label={t("emailInput")}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
@ -112,15 +178,16 @@ export const UserForm = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name="user-email-verified"
|
||||
name="emailVerified"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
data-testid="email-verified-switch"
|
||||
id={"kc-user-email-verified"}
|
||||
isDisabled={false}
|
||||
onChange={(value) => onChange([`${value}`])}
|
||||
isChecked={value[0] === "true"}
|
||||
onChange={(value) => onChange(value)}
|
||||
isChecked={value}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
/>
|
||||
|
@ -135,6 +202,7 @@ export const UserForm = ({
|
|||
>
|
||||
<TextInput
|
||||
ref={register()}
|
||||
data-testid="firstName-input"
|
||||
type="text"
|
||||
id="kc-firstname"
|
||||
name="firstName"
|
||||
|
@ -147,6 +215,7 @@ export const UserForm = ({
|
|||
>
|
||||
<TextInput
|
||||
ref={register()}
|
||||
data-testid="lastName-input"
|
||||
type="text"
|
||||
id="kc-lastname"
|
||||
name="lastName"
|
||||
|
@ -165,15 +234,16 @@ export const UserForm = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name="user-enabled"
|
||||
name="enabled"
|
||||
defaultValue={false}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
data-testid="user-enabled-switch"
|
||||
id={"kc-user-enabled"}
|
||||
isDisabled={false}
|
||||
onChange={(value) => onChange([`${value}`])}
|
||||
isChecked={value[0] === "true"}
|
||||
onChange={(value) => onChange(value)}
|
||||
isChecked={value}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
/>
|
||||
|
@ -194,12 +264,13 @@ export const UserForm = ({
|
|||
}
|
||||
>
|
||||
<Controller
|
||||
name="required-user-actions"
|
||||
defaultValue={["0"]}
|
||||
name="requiredActions"
|
||||
defaultValue={[]}
|
||||
typeAheadAriaLabel="Select an action"
|
||||
control={control}
|
||||
render={() => (
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
data-testid="required-actions-select"
|
||||
placeholderText="Select action"
|
||||
toggleId="kc-required-user-actions"
|
||||
onToggle={() =>
|
||||
|
@ -208,13 +279,13 @@ export const UserForm = ({
|
|||
)
|
||||
}
|
||||
isOpen={isRequiredUserActionsDropdownOpen}
|
||||
selections={selected}
|
||||
onSelect={(_, value) => {
|
||||
const option = value as string;
|
||||
if (selected.includes(option)) {
|
||||
setSelected(selected.filter((item) => item !== option));
|
||||
selections={value}
|
||||
onSelect={(_, v) => {
|
||||
const option = v as string;
|
||||
if (value.includes(option)) {
|
||||
onChange(value.filter((item: string) => item !== option));
|
||||
} else {
|
||||
setSelected([...selected, option]);
|
||||
onChange([...value, option]);
|
||||
}
|
||||
}}
|
||||
onClear={clearSelection}
|
||||
|
@ -223,16 +294,16 @@ export const UserForm = ({
|
|||
{requiredUserActionsOptions}
|
||||
</Select>
|
||||
)}
|
||||
></Controller>
|
||||
/>
|
||||
</FormGroup>
|
||||
<ActionGroup>
|
||||
<Button
|
||||
data-testid="create-user"
|
||||
isDisabled={!watchUsernameInput}
|
||||
data-testid={!editMode ? "create-user" : "save-user"}
|
||||
isDisabled={!editMode && !watchUsernameInput}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
>
|
||||
{t("common:Create")}
|
||||
{!editMode ? t("common:Create") : t("common:Save")}
|
||||
</Button>
|
||||
<Button
|
||||
data-testid="cancel-create-user"
|
||||
|
|
|
@ -28,7 +28,7 @@ import { emptyFormatter } from "../util";
|
|||
import { useConfirmDialog } from "../components/confirm-dialog/ConfirmDialog";
|
||||
|
||||
import "./user-section.css";
|
||||
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||
import { Link, useHistory, useRouteMatch } from "react-router-dom";
|
||||
|
||||
type BruteUser = UserRepresentation & {
|
||||
brute?: Record<string, object>;
|
||||
|
@ -71,6 +71,14 @@ export const UsersSection = () => {
|
|||
);
|
||||
}, []);
|
||||
|
||||
const UserDetailLink = (user: UserRepresentation) => (
|
||||
<>
|
||||
<Link key={user.username} to={`${url}/${user.username}/details`}>
|
||||
{user.username}
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
|
||||
const loader = async (first?: number, max?: number, search?: string) => {
|
||||
const params: { [name: string]: string | number } = {
|
||||
first: first!,
|
||||
|
@ -221,6 +229,7 @@ export const UsersSection = () => {
|
|||
{
|
||||
name: "username",
|
||||
displayKey: "users:username",
|
||||
cellRenderer: UserDetailLink,
|
||||
},
|
||||
{
|
||||
name: "email",
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import React from "react";
|
||||
import { AlertVariant, PageSection } from "@patternfly/react-core";
|
||||
import {
|
||||
AlertVariant,
|
||||
PageSection,
|
||||
Tab,
|
||||
TabTitleText,
|
||||
} from "@patternfly/react-core";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
|
@ -8,7 +13,8 @@ import UserRepresentation from "keycloak-admin/lib/defs/userRepresentation";
|
|||
import { UserForm } from "./UserForm";
|
||||
import { useAlerts } from "../components/alert/Alerts";
|
||||
import { useAdminClient } from "../context/auth/AdminClient";
|
||||
import { useHistory, useRouteMatch } from "react-router-dom";
|
||||
import { useHistory, useParams, useRouteMatch } from "react-router-dom";
|
||||
import { KeycloakTabs } from "../components/keycloak-tabs/KeycloakTabs";
|
||||
|
||||
export const UsersTabs = () => {
|
||||
const { t } = useTranslation("roles");
|
||||
|
@ -18,20 +24,18 @@ export const UsersTabs = () => {
|
|||
|
||||
const adminClient = useAdminClient();
|
||||
const form = useForm<UserRepresentation>({ mode: "onChange" });
|
||||
const { id } = useParams<{ id: string }>();
|
||||
|
||||
const save = async (user: UserRepresentation) => {
|
||||
try {
|
||||
await adminClient.users.create({
|
||||
username: user!.username,
|
||||
email: user!.email,
|
||||
emailVerified: user!.emailVerified,
|
||||
firstName: user!.firstName,
|
||||
lastName: user!.lastName,
|
||||
enabled: user!.enabled,
|
||||
requiredActions: user!.requiredActions,
|
||||
});
|
||||
if (id) {
|
||||
await adminClient.users.update({ id: user.id! }, user);
|
||||
addAlert(t("users:userSaved"), AlertVariant.success);
|
||||
} else {
|
||||
await adminClient.users.create(user);
|
||||
addAlert(t("users:userCreated"), AlertVariant.success);
|
||||
history.push(url.substr(0, url.lastIndexOf("/")));
|
||||
}
|
||||
} catch (error) {
|
||||
addAlert(
|
||||
t("users:userCreateError", {
|
||||
|
@ -45,12 +49,23 @@ export const UsersTabs = () => {
|
|||
return (
|
||||
<>
|
||||
<ViewHeader
|
||||
titleKey={t("users:createUser")}
|
||||
titleKey={id! || t("users:createUser")}
|
||||
subKey=""
|
||||
dividerComponent="div"
|
||||
/>
|
||||
<PageSection variant="light">
|
||||
<UserForm form={form} save={save} />
|
||||
{id && (
|
||||
<KeycloakTabs isBox>
|
||||
<Tab
|
||||
eventKey="details"
|
||||
data-testid="user-details-tab"
|
||||
title={<TabTitleText>{t("details")}</TabTitleText>}
|
||||
>
|
||||
<UserForm form={form} save={save} editMode={true} />
|
||||
</Tab>
|
||||
</KeycloakTabs>
|
||||
)}
|
||||
{!id && <UserForm form={form} save={save} editMode={false} />}
|
||||
</PageSection>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -8,6 +8,8 @@
|
|||
"noUsersFound": "No users found",
|
||||
"noUsersFoundError": "No users found due to {{error}}",
|
||||
"emptyInstructions": "Change your search criteria or add a user",
|
||||
"id": "ID",
|
||||
"createdAt": "Created at",
|
||||
"username": "Username",
|
||||
"email": "Email",
|
||||
"emailVerified": "Email verified",
|
||||
|
@ -28,6 +30,8 @@
|
|||
"deleteConfirmDialog": "Are you sure you want to permanently delete {{count}} selected user",
|
||||
"deleteConfirmDialog_plural": "Are you sure you want to permanently delete {{count}} selected users",
|
||||
"userCreated": "The user has been created",
|
||||
"userSaved": "The user has been saved",
|
||||
"userDetails": "User details",
|
||||
"userCreateError": "Could not create user: {{error}}",
|
||||
"userDeletedSuccess": "The user has been deleted",
|
||||
"userDeletedError": "The user could not be deleted {{error}}",
|
||||
|
|
Loading…
Reference in a new issue