Unify save-buttons in admin-ui (#30119)

* #30118 Unify save-buttons in admin-ui

Signed-off-by: Andreas Blaettlinger <bln1imb@bosch.com>

* #30118 Unify save-buttons in admin-ui

Signed-off-by: Andreas Blaettlinger <bln1imb@bosch.com>

* Introduced props for naming the buttons in FixedButtonGroup

Signed-off-by: Andreas Blaettlinger <bln1imb@bosch.com>

---------

Signed-off-by: Andreas Blaettlinger <bln1imb@bosch.com>
This commit is contained in:
Andreas Blättlinger 2024-06-18 13:00:45 +02:00 committed by GitHub
parent 5ad3abaa96
commit 2a88d01e5e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 45 additions and 85 deletions

View file

@ -1,5 +1,5 @@
export default class AttributesTab { export default class AttributesTab {
#saveAttributeBtn = "save-attributes"; #saveAttributeBtn = "attributes-save";
#attributesTab = "attributes"; #attributesTab = "attributes";
#emptyState = "attributes-empty-state"; #emptyState = "attributes-empty-state";
#addAttributeBtn: string; #addAttributeBtn: string;

View file

@ -31,7 +31,7 @@ export default class KeyValueInput {
} }
save() { save() {
cy.findByTestId("save-attributes").click(); cy.findByTestId("attributes-save").click();
return this; return this;
} }

View file

@ -11,8 +11,8 @@ enum RealmSettingsTab {
const expect = chai.expect; const expect = chai.expect;
export default class RealmSettingsPage extends CommonPage { export default class RealmSettingsPage extends CommonPage {
generalSaveBtn = "general-tab-save"; generalSaveBtn = "realmSettingsGeneralTab-save";
generalRevertBtn = "general-tab-revert"; generalRevertBtn = "realmSettingsGeneralTab-revert";
themesSaveBtn = "themes-tab-save"; themesSaveBtn = "themes-tab-save";
loginTab = "rs-login-tab"; loginTab = "rs-login-tab";
emailTab = "rs-email-tab"; emailTab = "rs-email-tab";

View file

@ -21,9 +21,9 @@ export default class CreateUserPage {
this.addUserBtn = "add-user"; this.addUserBtn = "add-user";
this.joinGroupsBtn = "join-groups-button"; this.joinGroupsBtn = "join-groups-button";
this.joinBtn = "join-button"; this.joinBtn = "join-button";
this.createBtn = "create-user"; this.createBtn = "user-creation-save";
this.saveBtn = "save-user"; this.saveBtn = "user-creation-save";
this.cancelBtn = "cancel-create-user"; this.cancelBtn = "user-creation-revert";
} }
//#region General Settings //#region General Settings

View file

@ -19,7 +19,7 @@ export default class UserDetailsPage extends PageObject {
constructor() { constructor() {
super(); super();
this.saveBtn = "save-user"; this.saveBtn = "user-creation-save";
this.cancelBtn = "cancel-create-user"; this.cancelBtn = "cancel-create-user";
this.emailInput = "email"; this.emailInput = "email";
this.emailValue = () => "example" + "_" + uuid() + "@example.com"; this.emailValue = () => "example" + "_" + uuid() + "@example.com";

View file

@ -7,7 +7,9 @@ import style from "./fixed-buttons.module.css";
type FixedButtonGroupProps = ActionGroupProps & { type FixedButtonGroupProps = ActionGroupProps & {
name: string; name: string;
save?: () => void; save?: () => void;
saveText?: string;
reset?: () => void; reset?: () => void;
resetText?: string;
isSubmit?: boolean; isSubmit?: boolean;
isActive?: boolean; isActive?: boolean;
}; };
@ -15,7 +17,9 @@ type FixedButtonGroupProps = ActionGroupProps & {
export const FixedButtonsGroup = ({ export const FixedButtonsGroup = ({
name, name,
save, save,
saveText,
reset, reset,
resetText,
isSubmit = false, isSubmit = false,
isActive = true, isActive = true,
children, children,
@ -31,7 +35,7 @@ export const FixedButtonsGroup = ({
onClick={() => save?.()} onClick={() => save?.()}
type={isSubmit ? "submit" : "button"} type={isSubmit ? "submit" : "button"}
> >
{t("save")} {!saveText ? t("save") : saveText}
</Button> </Button>
)} )}
{reset && ( {reset && (
@ -41,7 +45,7 @@ export const FixedButtonsGroup = ({
variant="link" variant="link"
onClick={() => reset()} onClick={() => reset()}
> >
{t("revert")} {!resetText ? t("revert") : resetText}
</Button> </Button>
)} )}
{children} {children}

View file

@ -1,11 +1,10 @@
import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation"; import type RoleRepresentation from "@keycloak/keycloak-admin-client/lib/defs/roleRepresentation";
import { ActionGroup, Button } from "@patternfly/react-core";
import { FormProvider, UseFormReturn } from "react-hook-form"; import { FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next";
import { FormAccess } from "../form/FormAccess"; import { FormAccess } from "../form/FormAccess";
import type { KeyValueType } from "./key-value-convert"; import type { KeyValueType } from "./key-value-convert";
import { KeyValueInput } from "./KeyValueInput"; import { KeyValueInput } from "./KeyValueInput";
import { FixedButtonsGroup } from "../form/FixedButtonGroup";
export type AttributeForm = Omit<RoleRepresentation, "attributes"> & { export type AttributeForm = Omit<RoleRepresentation, "attributes"> & {
attributes?: KeyValueType[]; attributes?: KeyValueType[];
@ -28,12 +27,8 @@ export const AttributesForm = ({
name = "attributes", name = "attributes",
isDisabled = false, isDisabled = false,
}: AttributesFormProps) => { }: AttributesFormProps) => {
const { t } = useTranslation();
const noSaveCancelButtons = !save && !reset; const noSaveCancelButtons = !save && !reset;
const { const { handleSubmit } = form;
formState: { isDirty },
handleSubmit,
} = form;
return ( return (
<FormAccess <FormAccess
@ -45,19 +40,7 @@ export const AttributesForm = ({
<KeyValueInput name={name} isDisabled={isDisabled} /> <KeyValueInput name={name} isDisabled={isDisabled} />
</FormProvider> </FormProvider>
{!noSaveCancelButtons && ( {!noSaveCancelButtons && (
<ActionGroup className="kc-attributes__action-group"> <FixedButtonsGroup name="attributes" reset={reset} isActive isSubmit />
<Button
data-testid="save-attributes"
variant="primary"
type="submit"
isDisabled={!isDirty}
>
{t("save")}
</Button>
<Button onClick={reset} variant="link" isDisabled={!isDirty}>
{t("revert")}
</Button>
</ActionGroup>
)} )}
</FormAccess> </FormAccess>
); );

View file

@ -4,8 +4,6 @@ import {
UserProfileConfig, UserProfileConfig,
} from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata"; } from "@keycloak/keycloak-admin-client/lib/defs/userProfileMetadata";
import { import {
ActionGroup,
Button,
ClipboardCopy, ClipboardCopy,
FormGroup, FormGroup,
PageSection, PageSection,
@ -27,6 +25,7 @@ import { FormattedLink } from "../components/external-link/FormattedLink";
import { FormAccess } from "../components/form/FormAccess"; import { FormAccess } from "../components/form/FormAccess";
import { KeyValueInput } from "../components/key-value-form/KeyValueInput"; import { KeyValueInput } from "../components/key-value-form/KeyValueInput";
import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner"; import { KeycloakSpinner } from "../components/keycloak-spinner/KeycloakSpinner";
import { FixedButtonsGroup } from "../components/form/FixedButtonGroup";
import { useRealm } from "../context/realm-context/RealmContext"; import { useRealm } from "../context/realm-context/RealmContext";
import { import {
addTrailingSlash, addTrailingSlash,
@ -105,7 +104,7 @@ function RealmSettingsGeneralTabForm({
control, control,
handleSubmit, handleSubmit,
setValue, setValue,
formState: { isDirty, errors }, formState: { errors },
} = form; } = form;
const isFeatureEnabled = useIsFeatureEnabled(); const isFeatureEnabled = useIsFeatureEnabled();
const isOrganizationsEnabled = isFeatureEnabled(Feature.Organizations); const isOrganizationsEnabled = isFeatureEnabled(Feature.Organizations);
@ -266,23 +265,12 @@ function RealmSettingsGeneralTabForm({
</StackItem> </StackItem>
</Stack> </Stack>
</FormGroup> </FormGroup>
<ActionGroup> <FixedButtonsGroup
<Button name="realmSettingsGeneralTab"
variant="primary" reset={setupForm}
type="submit" isActive
data-testid="general-tab-save" isSubmit
isDisabled={!isDirty} />
>
{t("save")}
</Button>
<Button
data-testid="general-tab-revert"
variant="link"
onClick={setupForm}
>
{t("revert")}
</Button>
</ActionGroup>
</FormAccess> </FormAccess>
</FormProvider> </FormProvider>
</PageSection> </PageSection>

View file

@ -4,14 +4,12 @@ import { UserProfileMetadata } from "@keycloak/keycloak-admin-client/lib/defs/us
import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation"; import type UserRepresentation from "@keycloak/keycloak-admin-client/lib/defs/userRepresentation";
import { import {
FormErrorText, FormErrorText,
FormSubmitButton,
HelpItem, HelpItem,
SwitchControl, SwitchControl,
TextControl, TextControl,
UserProfileFields, UserProfileFields,
} from "@keycloak/keycloak-ui-shared"; } from "@keycloak/keycloak-ui-shared";
import { import {
ActionGroup,
AlertVariant, AlertVariant,
Button, Button,
Chip, Chip,
@ -26,7 +24,6 @@ import { TFunction } from "i18next";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Controller, FormProvider, UseFormReturn } from "react-hook-form"; import { Controller, FormProvider, UseFormReturn } from "react-hook-form";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { useAdminClient } from "../admin-client"; import { useAdminClient } from "../admin-client";
import { DefaultSwitchControl } from "../components/SwitchControl"; import { DefaultSwitchControl } from "../components/SwitchControl";
import { useAlerts } from "../components/alert/Alerts"; import { useAlerts } from "../components/alert/Alerts";
@ -39,7 +36,9 @@ import useFormatDate from "../utils/useFormatDate";
import { FederatedUserLink } from "./FederatedUserLink"; import { FederatedUserLink } from "./FederatedUserLink";
import { UserFormFields, toUserFormFields } from "./form-state"; import { UserFormFields, toUserFormFields } from "./form-state";
import { toUsers } from "./routes/Users"; import { toUsers } from "./routes/Users";
import { FixedButtonsGroup } from "../components/form/FixedButtonGroup";
import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect"; import { RequiredActionMultiSelect } from "./user-credentials/RequiredActionMultiSelect";
import { useNavigate } from "react-router-dom";
export type BruteForced = { export type BruteForced = {
isBruteForceProtected?: boolean; isBruteForceProtected?: boolean;
@ -79,15 +78,15 @@ export const UserForm = ({
const { whoAmI } = useWhoAmI(); const { whoAmI } = useWhoAmI();
const currentLocale = whoAmI.getLocale(); const currentLocale = whoAmI.getLocale();
const { handleSubmit, setValue, watch, control, reset, formState } = form; const { handleSubmit, setValue, control, reset, formState } = form;
const { errors } = formState; const { errors } = formState;
const watchUsernameInput = watch("username");
const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>( const [selectedGroups, setSelectedGroups] = useState<GroupRepresentation[]>(
[], [],
); );
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [locked, setLocked] = useState(isLocked); const [locked, setLocked] = useState(isLocked);
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
setValue("requiredActions", user?.requiredActions || []); setValue("requiredActions", user?.requiredActions || []);
@ -132,6 +131,14 @@ export const UserForm = ({
setOpen(!open); setOpen(!open);
}; };
const onFormReset = () => {
if (user?.id) {
reset(toUserFormFields(user));
} else {
navigate(toUsers({ realm: realm.realm! }));
}
};
return ( return (
<FormAccess <FormAccess
isHorizontal isHorizontal
@ -327,37 +334,15 @@ export const UserForm = ({
)} )}
</FormGroup> </FormGroup>
)} )}
<ActionGroup>
<FormSubmitButton
formState={formState}
data-testid={!user?.id ? "create-user" : "save-user"}
isDisabled={
!user?.id &&
!watchUsernameInput &&
realm.registrationEmailAsUsername === false
}
allowNonDirty
allowInvalid
>
{user?.id ? t("save") : t("create")}
</FormSubmitButton>
<Button
data-testid="cancel-create-user"
variant="link"
onClick={user?.id ? () => reset(toUserFormFields(user)) : undefined}
component={
!user?.id
? (props) => (
<Link {...props} to={toUsers({ realm: realm.realm! })} />
)
: undefined
}
>
{user?.id ? t("revert") : t("cancel")}
</Button>
</ActionGroup>
</FormProvider> </FormProvider>
<FixedButtonsGroup
name="user-creation"
saveText={user?.id ? t("save") : t("create")}
reset={onFormReset}
resetText={user?.id ? t("revert") : t("cancel")}
isActive
isSubmit
/>
</FormAccess> </FormAccess>
); );
}; };