Refactored user credentials into seperate components (#2024)
This commit is contained in:
parent
2030c55e48
commit
8de96be98b
11 changed files with 797 additions and 913 deletions
|
@ -197,8 +197,9 @@ describe("User creation", () => {
|
|||
.goToCredentialsTab()
|
||||
.clickEmptyStateResetBtn()
|
||||
.fillResetCredentialForm();
|
||||
masthead.checkNotificationMessage("Failed to send email to user.");
|
||||
modalUtils.cancelModal();
|
||||
masthead.checkNotificationMessage(
|
||||
"Failed: Failed to send execute actions email"
|
||||
);
|
||||
});
|
||||
|
||||
it("Reset credential of User with existing credentials", () => {
|
||||
|
@ -208,8 +209,9 @@ describe("User creation", () => {
|
|||
.clickResetBtn()
|
||||
.fillResetCredentialForm();
|
||||
|
||||
masthead.checkNotificationMessage("Failed to send email to user.");
|
||||
modalUtils.cancelModal();
|
||||
masthead.checkNotificationMessage(
|
||||
"Failed: Failed to send execute actions email"
|
||||
);
|
||||
});
|
||||
|
||||
it("Delete user test", () => {
|
||||
|
|
|
@ -3,7 +3,7 @@ export default class CredentialsPage {
|
|||
private readonly emptyStatePasswordBtn = "no-credentials-empty-action";
|
||||
private readonly emptyStateResetBtn = "credential-reset-empty-action";
|
||||
private readonly resetBtn = "credentialResetBtn";
|
||||
private readonly setPasswordBtn = "setPasswordBtn";
|
||||
private readonly setPasswordBtn = "confirm";
|
||||
private readonly credentialResetModal = "credential-reset-modal";
|
||||
private readonly resetModalActionsToggleBtn =
|
||||
"[data-testid=credential-reset-modal] #actions";
|
||||
|
@ -16,7 +16,7 @@ export default class CredentialsPage {
|
|||
"UPDATE_PASSWORD-option",
|
||||
"terms_and_conditions-option",
|
||||
];
|
||||
private readonly confirmationButton = "okBtn";
|
||||
private readonly confirmationButton = "confirm";
|
||||
|
||||
goToCredentialsTab() {
|
||||
cy.findByTestId(this.credentialsTab).click();
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -126,19 +126,17 @@ export default {
|
|||
cancel: "Cancel",
|
||||
savePasswordSuccess: "The password has been set successfully.",
|
||||
savePasswordError: "Error saving password: {{error}}",
|
||||
savePasswordNotMatchError:
|
||||
"Error saving password: Password and confirmation does not match.",
|
||||
confirmPasswordDoesNotMatch: "Password and confirmation does not match.",
|
||||
credentialType: "Type",
|
||||
credentialUserLabel: "User Label",
|
||||
credentialData: "Data",
|
||||
credentialsList: "Credentials List",
|
||||
setPasswordConfirm: "Set password?",
|
||||
setPasswordConfirmText:
|
||||
"Are you sure you want to set the password for the user",
|
||||
"Are you sure you want to set the password for the user {{username}}?",
|
||||
password: "Password",
|
||||
passwordConfirmation: "Password confirmation",
|
||||
resetPasswordConfirmation: "New password confirmation",
|
||||
questionMark: "?",
|
||||
savePassword: "Save password",
|
||||
deleteCredentialsConfirmTitle: "Delete credentials?",
|
||||
deleteCredentialsConfirm:
|
||||
|
@ -149,12 +147,10 @@ export default {
|
|||
resetPasswordFor: "Reset password for {{username}}",
|
||||
resetPasswordConfirm: "Reset password?",
|
||||
resetPasswordConfirmText:
|
||||
"Are you sure you want to reset the password for the user",
|
||||
"Are you sure you want to reset the password for the user {{username}}?",
|
||||
resetPassword: "Reset password",
|
||||
resetCredentialsSuccess: "The password has been reset successfully.",
|
||||
resetCredentialsError: "Error resetting users credentials: {{error}}",
|
||||
resetPasswordNotMatchError:
|
||||
"Error resetting password: Password and confirmation does not match.",
|
||||
resetPasswordError: "Error resetting password: {{error}}",
|
||||
resetPasswordBtn: "Reset password",
|
||||
showPasswordDataName: "Name",
|
||||
|
@ -185,6 +181,6 @@ export default {
|
|||
credentialResetConfirm: "Send Email",
|
||||
credentialResetConfirmText: "Are you sure you want to send email to user",
|
||||
credentialResetEmailSuccess: "Email sent to user.",
|
||||
credentialResetEmailError: "Failed to send email to user.",
|
||||
credentialResetEmailError: "Failed: {{error}}",
|
||||
},
|
||||
};
|
||||
|
|
40
src/user/user-credentials/CredentialDataDialog.tsx
Normal file
40
src/user/user-credentials/CredentialDataDialog.tsx
Normal file
|
@ -0,0 +1,40 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Modal, ModalVariant } from "@patternfly/react-core";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableHeader,
|
||||
TableVariant,
|
||||
} from "@patternfly/react-table";
|
||||
|
||||
type CredentialDataDialogProps = {
|
||||
credentialData: [string, string][];
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export const CredentialDataDialog = ({
|
||||
credentialData,
|
||||
onClose,
|
||||
}: CredentialDataDialogProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
return (
|
||||
<Modal
|
||||
variant={ModalVariant.medium}
|
||||
title={t("passwordDataTitle")}
|
||||
isOpen
|
||||
onClose={onClose}
|
||||
>
|
||||
<Table
|
||||
aria-label={t("passwordDataTitle")}
|
||||
data-testid="password-data-dialog"
|
||||
variant={TableVariant.compact}
|
||||
cells={[t("showPasswordDataName"), t("showPasswordDataValue")]}
|
||||
rows={credentialData}
|
||||
>
|
||||
<TableHeader />
|
||||
<TableBody />
|
||||
</Table>
|
||||
</Modal>
|
||||
);
|
||||
};
|
115
src/user/user-credentials/CredentialRow.tsx
Normal file
115
src/user/user-credentials/CredentialRow.tsx
Normal file
|
@ -0,0 +1,115 @@
|
|||
import React, { ReactNode, useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Td } from "@patternfly/react-table";
|
||||
import {
|
||||
Button,
|
||||
Dropdown,
|
||||
DropdownPosition,
|
||||
KebabToggle,
|
||||
DropdownItem,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
|
||||
import { useWhoAmI } from "../../context/whoami/WhoAmI";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
import { CredentialDataDialog } from "./CredentialDataDialog";
|
||||
|
||||
type CredentialRowProps = {
|
||||
credential: CredentialRepresentation;
|
||||
resetPassword: () => void;
|
||||
toggleDelete: () => void;
|
||||
children: ReactNode;
|
||||
};
|
||||
|
||||
export const CredentialRow = ({
|
||||
credential,
|
||||
resetPassword,
|
||||
toggleDelete,
|
||||
children,
|
||||
}: CredentialRowProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const [showData, toggleShow] = useToggle();
|
||||
const [kebabOpen, toggleKebab] = useToggle();
|
||||
|
||||
const { whoAmI } = useWhoAmI();
|
||||
|
||||
const rows = useMemo(() => {
|
||||
if (!credential.credentialData) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const credentialData: Record<string, unknown> = JSON.parse(
|
||||
credential.credentialData
|
||||
);
|
||||
const locale = whoAmI.getLocale();
|
||||
|
||||
return Object.entries(credentialData)
|
||||
.sort(([a], [b]) => a.localeCompare(b, locale))
|
||||
.map<[string, string]>(([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
return [key, value];
|
||||
}
|
||||
|
||||
return [key, JSON.stringify(value)];
|
||||
});
|
||||
}, [credential.credentialData]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{showData && Object.keys(credential).length !== 0 && (
|
||||
<CredentialDataDialog
|
||||
credentialData={rows}
|
||||
onClose={() => {
|
||||
toggleShow();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Td>{children}</Td>
|
||||
<Td>
|
||||
<Button
|
||||
className="kc-showData-btn"
|
||||
variant="link"
|
||||
data-testid="showDataBtn"
|
||||
onClick={toggleShow}
|
||||
>
|
||||
{t("showDataBtn")}
|
||||
</Button>
|
||||
</Td>
|
||||
{credential.type === "password" ? (
|
||||
<Td>
|
||||
<Button
|
||||
variant="secondary"
|
||||
data-testid="resetPasswordBtn"
|
||||
onClick={resetPassword}
|
||||
>
|
||||
{t("resetPasswordBtn")}
|
||||
</Button>
|
||||
</Td>
|
||||
) : (
|
||||
<Td />
|
||||
)}
|
||||
<Td>
|
||||
<Dropdown
|
||||
isPlain
|
||||
position={DropdownPosition.right}
|
||||
toggle={<KebabToggle onToggle={toggleKebab} />}
|
||||
isOpen={kebabOpen}
|
||||
dropdownItems={[
|
||||
<DropdownItem
|
||||
key={credential.id}
|
||||
data-testid="deleteDropdownItem"
|
||||
component="button"
|
||||
onClick={() => {
|
||||
toggleDelete();
|
||||
toggleKebab();
|
||||
}}
|
||||
>
|
||||
{t("deleteBtn")}
|
||||
</DropdownItem>,
|
||||
]}
|
||||
/>
|
||||
</Td>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,72 @@
|
|||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import {
|
||||
FormGroup,
|
||||
Select,
|
||||
SelectOption,
|
||||
SelectVariant,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
|
||||
export const CredentialsResetActionMultiSelect = () => {
|
||||
const { t } = useTranslation("users");
|
||||
const { control } = useFormContext();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
label={t("resetActions")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="clients-help:resetActions"
|
||||
fieldLabelId="resetActions"
|
||||
/>
|
||||
}
|
||||
fieldId="actions"
|
||||
>
|
||||
<Controller
|
||||
name="actions"
|
||||
defaultValue={[]}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Select
|
||||
toggleId="actions"
|
||||
variant={SelectVariant.typeaheadMulti}
|
||||
chipGroupProps={{
|
||||
numChips: 3,
|
||||
}}
|
||||
menuAppendTo="parent"
|
||||
onToggle={(open) => setOpen(open)}
|
||||
isOpen={open}
|
||||
selections={value}
|
||||
onSelect={(_, selectedValue) =>
|
||||
onChange(
|
||||
value.find((o: string) => o === selectedValue)
|
||||
? value.filter((item: string) => item !== selectedValue)
|
||||
: [...value, selectedValue]
|
||||
)
|
||||
}
|
||||
onClear={(event) => {
|
||||
event.stopPropagation();
|
||||
onChange([]);
|
||||
}}
|
||||
aria-label={t("resetActions")}
|
||||
>
|
||||
{Object.values(RequiredActionAlias).map((action, index) => (
|
||||
<SelectOption
|
||||
key={index}
|
||||
value={action}
|
||||
data-testid={`${action}-option`}
|
||||
>
|
||||
{t(action)}
|
||||
</SelectOption>
|
||||
))}
|
||||
</Select>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
106
src/user/user-credentials/InlineLabelEdit.tsx
Normal file
106
src/user/user-credentials/InlineLabelEdit.tsx
Normal file
|
@ -0,0 +1,106 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useForm } from "react-hook-form";
|
||||
import {
|
||||
AlertVariant,
|
||||
Button,
|
||||
Form,
|
||||
FormGroup,
|
||||
TextInput,
|
||||
} from "@patternfly/react-core";
|
||||
import { CheckIcon, PencilAltIcon, TimesIcon } from "@patternfly/react-icons";
|
||||
|
||||
import type CredentialRepresentation from "@keycloak/keycloak-admin-client/lib/defs/credentialRepresentation";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
|
||||
type UserLabelForm = {
|
||||
userLabel: string;
|
||||
};
|
||||
|
||||
type InlineLabelEditProps = {
|
||||
userId: string;
|
||||
credential: CredentialRepresentation;
|
||||
isEditable: boolean;
|
||||
toggle: () => void;
|
||||
};
|
||||
|
||||
export const InlineLabelEdit = ({
|
||||
userId,
|
||||
credential,
|
||||
isEditable,
|
||||
toggle,
|
||||
}: InlineLabelEditProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const { register, handleSubmit } = useForm<UserLabelForm>();
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const saveUserLabel = async (userLabel: UserLabelForm) => {
|
||||
try {
|
||||
await adminClient.users.updateCredentialLabel(
|
||||
{
|
||||
id: userId,
|
||||
credentialId: credential.id!,
|
||||
},
|
||||
userLabel.userLabel || ""
|
||||
);
|
||||
addAlert(t("updateCredentialUserLabelSuccess"), AlertVariant.success);
|
||||
toggle();
|
||||
} catch (error) {
|
||||
addError("users:updateCredentialUserLabelError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Form isHorizontal className="kc-form-userLabel">
|
||||
<FormGroup fieldId="kc-userLabel" className="kc-userLabel-row">
|
||||
<div className="kc-form-group-userLabel">
|
||||
{isEditable ? (
|
||||
<>
|
||||
<TextInput
|
||||
name="userLabel"
|
||||
defaultValue={credential.userLabel}
|
||||
ref={register()}
|
||||
type="text"
|
||||
className="kc-userLabel"
|
||||
aria-label={t("userLabel")}
|
||||
data-testid="user-label-fld"
|
||||
/>
|
||||
<div className="kc-userLabel-actionBtns">
|
||||
<Button
|
||||
data-testid="editUserLabel-acceptBtn"
|
||||
variant="link"
|
||||
className="kc-editUserLabel-acceptBtn"
|
||||
onClick={() => {
|
||||
handleSubmit(saveUserLabel)();
|
||||
}}
|
||||
icon={<CheckIcon />}
|
||||
/>
|
||||
<Button
|
||||
data-testid="editUserLabel-cancelBtn"
|
||||
variant="link"
|
||||
className="kc-editUserLabel-cancelBtn"
|
||||
onClick={toggle}
|
||||
icon={<TimesIcon />}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{credential.userLabel}
|
||||
<Button
|
||||
variant="link"
|
||||
className="kc-editUserLabel-btn"
|
||||
onClick={toggle}
|
||||
data-testid="editUserLabelBtn"
|
||||
icon={<PencilAltIcon />}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
);
|
||||
};
|
38
src/user/user-credentials/LifespanField.tsx
Normal file
38
src/user/user-credentials/LifespanField.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import { FormGroup } from "@patternfly/react-core";
|
||||
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { TimeSelector } from "../../components/time-selector/TimeSelector";
|
||||
import { credResetFormDefaultValues } from "./ResetCredentialDialog";
|
||||
|
||||
export const LifespanField = () => {
|
||||
const { t } = useTranslation("users");
|
||||
const { control } = useFormContext();
|
||||
|
||||
return (
|
||||
<FormGroup
|
||||
fieldId="lifespan"
|
||||
label={t("lifespan")}
|
||||
isStack
|
||||
labelIcon={
|
||||
<HelpItem helpText="clients-help:lifespan" fieldLabelId="lifespan" />
|
||||
}
|
||||
>
|
||||
<Controller
|
||||
name="lifespan"
|
||||
defaultValue={credResetFormDefaultValues.lifespan}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<TimeSelector
|
||||
value={value}
|
||||
units={["minutes", "hours", "days"]}
|
||||
onChange={onChange}
|
||||
menuAppendTo="parent"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
);
|
||||
};
|
94
src/user/user-credentials/ResetCredentialDialog.tsx
Normal file
94
src/user/user-credentials/ResetCredentialDialog.tsx
Normal file
|
@ -0,0 +1,94 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FormProvider, useForm, useWatch } from "react-hook-form";
|
||||
import { ModalVariant, Form, AlertVariant } from "@patternfly/react-core";
|
||||
|
||||
import type { RequiredActionAlias } from "@keycloak/keycloak-admin-client/lib/defs/requiredActionProviderRepresentation";
|
||||
import { CredentialsResetActionMultiSelect } from "./CredentialsResetActionMultiSelect";
|
||||
import { ConfirmDialogModal } from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import { LifespanField } from "./LifespanField";
|
||||
import { isEmpty } from "lodash-es";
|
||||
|
||||
type ResetCredentialDialogProps = {
|
||||
userId: string;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
type CredentialResetForm = {
|
||||
actions: RequiredActionAlias[];
|
||||
lifespan: number;
|
||||
};
|
||||
|
||||
export const credResetFormDefaultValues: CredentialResetForm = {
|
||||
actions: [],
|
||||
lifespan: 43200, // 12 hours
|
||||
};
|
||||
|
||||
export const ResetCredentialDialog = ({
|
||||
userId,
|
||||
onClose,
|
||||
}: ResetCredentialDialogProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const form = useForm<CredentialResetForm>({
|
||||
defaultValues: credResetFormDefaultValues,
|
||||
});
|
||||
const { handleSubmit, control } = form;
|
||||
|
||||
const resetActionWatcher = useWatch<CredentialResetForm["actions"]>({
|
||||
control: control,
|
||||
name: "actions",
|
||||
});
|
||||
const resetIsNotDisabled = !isEmpty(resetActionWatcher);
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const sendCredentialsResetEmail = async ({
|
||||
actions,
|
||||
lifespan,
|
||||
}: CredentialResetForm) => {
|
||||
if (isEmpty(actions)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await adminClient.users.executeActionsEmail({
|
||||
id: userId,
|
||||
actions,
|
||||
lifespan,
|
||||
});
|
||||
addAlert(t("credentialResetEmailSuccess"), AlertVariant.success);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
addError("users:credentialResetEmailError", error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ConfirmDialogModal
|
||||
variant={ModalVariant.medium}
|
||||
titleKey="users:credentialReset"
|
||||
open
|
||||
onCancel={onClose}
|
||||
toggleDialog={onClose}
|
||||
continueButtonLabel="users:credentialResetConfirm"
|
||||
onConfirm={() => {
|
||||
handleSubmit(sendCredentialsResetEmail)();
|
||||
}}
|
||||
confirmButtonDisabled={!resetIsNotDisabled}
|
||||
>
|
||||
<Form
|
||||
id="userCredentialsReset-form"
|
||||
isHorizontal
|
||||
data-testid="credential-reset-modal"
|
||||
>
|
||||
<FormProvider {...form}>
|
||||
<CredentialsResetActionMultiSelect />
|
||||
<LifespanField />
|
||||
</FormProvider>
|
||||
</Form>
|
||||
</ConfirmDialogModal>
|
||||
);
|
||||
};
|
212
src/user/user-credentials/ResetPasswordDialog.tsx
Normal file
212
src/user/user-credentials/ResetPasswordDialog.tsx
Normal file
|
@ -0,0 +1,212 @@
|
|||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Controller, useForm } from "react-hook-form";
|
||||
import {
|
||||
AlertVariant,
|
||||
ButtonVariant,
|
||||
Form,
|
||||
FormGroup,
|
||||
Switch,
|
||||
ValidatedOptions,
|
||||
} from "@patternfly/react-core";
|
||||
|
||||
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
|
||||
import { HelpItem } from "../../components/help-enabler/HelpItem";
|
||||
import { PasswordInput } from "../../components/password-input/PasswordInput";
|
||||
import {
|
||||
ConfirmDialogModal,
|
||||
useConfirmDialog,
|
||||
} from "../../components/confirm-dialog/ConfirmDialog";
|
||||
import { useAdminClient } from "../../context/auth/AdminClient";
|
||||
import { useAlerts } from "../../components/alert/Alerts";
|
||||
import useToggle from "../../utils/useToggle";
|
||||
|
||||
type ResetPasswordDialogProps = {
|
||||
user: UserRepresentation;
|
||||
isResetPassword: boolean;
|
||||
refresh: () => void;
|
||||
onClose: () => void;
|
||||
};
|
||||
|
||||
export type CredentialsForm = {
|
||||
password: string;
|
||||
passwordConfirmation: string;
|
||||
temporaryPassword: boolean;
|
||||
};
|
||||
|
||||
const credFormDefaultValues: CredentialsForm = {
|
||||
password: "",
|
||||
passwordConfirmation: "",
|
||||
temporaryPassword: true,
|
||||
};
|
||||
|
||||
export const ResetPasswordDialog = ({
|
||||
user,
|
||||
isResetPassword,
|
||||
refresh,
|
||||
onClose,
|
||||
}: ResetPasswordDialogProps) => {
|
||||
const { t } = useTranslation("users");
|
||||
const {
|
||||
register,
|
||||
control,
|
||||
errors,
|
||||
formState: { isValid },
|
||||
watch,
|
||||
handleSubmit,
|
||||
} = useForm<CredentialsForm>({
|
||||
defaultValues: credFormDefaultValues,
|
||||
mode: "onChange",
|
||||
shouldUnregister: false,
|
||||
});
|
||||
|
||||
const [confirm, toggle] = useToggle(true);
|
||||
const password = watch("password", "");
|
||||
|
||||
const adminClient = useAdminClient();
|
||||
const { addAlert, addError } = useAlerts();
|
||||
|
||||
const [toggleConfirmSaveModal, ConfirmSaveModal] = useConfirmDialog({
|
||||
titleKey: isResetPassword
|
||||
? "users:resetPasswordConfirm"
|
||||
: "users:setPasswordConfirm",
|
||||
messageKey: isResetPassword
|
||||
? t("resetPasswordConfirmText", { username: user.username })
|
||||
: t("setPasswordConfirmText", { username: user.username }),
|
||||
continueButtonLabel: isResetPassword
|
||||
? "users:resetPassword"
|
||||
: "users:savePassword",
|
||||
continueButtonVariant: ButtonVariant.danger,
|
||||
onConfirm: () => handleSubmit(saveUserPassword)(),
|
||||
});
|
||||
|
||||
const saveUserPassword = async ({
|
||||
password,
|
||||
temporaryPassword,
|
||||
}: CredentialsForm) => {
|
||||
try {
|
||||
await adminClient.users.resetPassword({
|
||||
id: user.id!,
|
||||
credential: {
|
||||
temporary: temporaryPassword,
|
||||
type: "password",
|
||||
value: password,
|
||||
},
|
||||
});
|
||||
addAlert(
|
||||
isResetPassword
|
||||
? t("resetCredentialsSuccess")
|
||||
: t("savePasswordSuccess"),
|
||||
AlertVariant.success
|
||||
);
|
||||
refresh();
|
||||
} catch (error) {
|
||||
addError(
|
||||
isResetPassword
|
||||
? "users:resetPasswordError"
|
||||
: "users:savePasswordError",
|
||||
error
|
||||
);
|
||||
}
|
||||
|
||||
onClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ConfirmSaveModal />
|
||||
<ConfirmDialogModal
|
||||
titleKey={
|
||||
isResetPassword
|
||||
? t("resetPasswordFor", { username: user.username })
|
||||
: t("setPasswordFor", { username: user.username })
|
||||
}
|
||||
open={confirm}
|
||||
onCancel={onClose}
|
||||
toggleDialog={toggle}
|
||||
onConfirm={toggleConfirmSaveModal}
|
||||
confirmButtonDisabled={!isValid}
|
||||
continueButtonLabel="common:save"
|
||||
>
|
||||
<Form
|
||||
id="userCredentials-form"
|
||||
isHorizontal
|
||||
className="keycloak__user-credentials__reset-form"
|
||||
>
|
||||
<FormGroup
|
||||
name="password"
|
||||
label={t("password")}
|
||||
fieldId="password"
|
||||
helperTextInvalid={t("common:required")}
|
||||
validated={
|
||||
errors.password
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<PasswordInput
|
||||
data-testid="passwordField"
|
||||
name="password"
|
||||
aria-label="password"
|
||||
ref={register({ required: true })}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
name="passwordConfirmation"
|
||||
label={
|
||||
isResetPassword
|
||||
? t("resetPasswordConfirmation")
|
||||
: t("passwordConfirmation")
|
||||
}
|
||||
fieldId="passwordConfirmation"
|
||||
helperTextInvalid={errors.passwordConfirmation?.message}
|
||||
validated={
|
||||
errors.passwordConfirmation
|
||||
? ValidatedOptions.error
|
||||
: ValidatedOptions.default
|
||||
}
|
||||
isRequired
|
||||
>
|
||||
<PasswordInput
|
||||
data-testid="passwordConfirmationField"
|
||||
name="passwordConfirmation"
|
||||
aria-label="passwordConfirm"
|
||||
ref={register({
|
||||
required: true,
|
||||
validate: (value) =>
|
||||
value === password ||
|
||||
t("confirmPasswordDoesNotMatch").toString(),
|
||||
})}
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup
|
||||
label={t("common:temporaryPassword")}
|
||||
labelIcon={
|
||||
<HelpItem
|
||||
helpText="temporaryPasswordHelpText"
|
||||
fieldLabelId="temporaryPassword"
|
||||
/>
|
||||
}
|
||||
fieldId="kc-temporaryPassword"
|
||||
>
|
||||
<Controller
|
||||
name="temporaryPassword"
|
||||
defaultValue={true}
|
||||
control={control}
|
||||
render={({ onChange, value }) => (
|
||||
<Switch
|
||||
className="kc-temporaryPassword"
|
||||
onChange={onChange}
|
||||
isChecked={value}
|
||||
label={t("common:on")}
|
||||
labelOff={t("common:off")}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ConfirmDialogModal>
|
||||
</>
|
||||
);
|
||||
};
|
Loading…
Reference in a new issue