Skip to content

Commit

Permalink
wizard: add user information step (HMS-4903)
Browse files Browse the repository at this point in the history
This commit introduces the user information step with the following fields:
(*) `userName` field with validation according to Red Hat guidelines: https://access.redhat.com/solutions/30164
(*) `password` field with validation using the pam_pwquality module, which enforces strict password policies for Linux systems.
This module enhances security by enforcing minimum length, complexity, and avoiding weak passwords.
  More info: https://access.redhat.com/solutions/6979714
Validation is configured based on the requirements defined for our systems:
  https://github.com/osbuild/osbuild-composer/tree/main/test/data/manifests
  https://github.com/osbuild/images/pkg/distro/rhel/rhel10
(*) `confirm password` field that check if confirm password equal to password
(*) `ssh key` field - implement the same validation as we have in edge.
[x] [wip> unit tests- in progress because I want to make sure that all new fields are in a good shape.
  • Loading branch information
mgold1234 committed Dec 8, 2024
1 parent 3d75b5b commit a56a823
Show file tree
Hide file tree
Showing 10 changed files with 757 additions and 6 deletions.
9 changes: 8 additions & 1 deletion src/Components/CreateImageWizard/CreateImageWizard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {
useFirstBootValidation,
useDetailsValidation,
useRegistrationValidation,
useUserValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
Expand Down Expand Up @@ -218,6 +219,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const firstBootValidation = useFirstBootValidation();
// Details
const detailsValidation = useDetailsValidation();
// Users
const userValidation = useUserValidation();

let startIndex = 1; // default index
if (isEdit) {
Expand Down Expand Up @@ -448,9 +451,13 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
name="Users"
id="wizard-users"
key="wizard-users"
status={userValidation.disabledNext ? 'error' : 'default'}
isHidden={!isUsersEnabled}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={userValidation.disabledNext}
optional={true}
/>
}
>
<UsersStep />
Expand Down
176 changes: 175 additions & 1 deletion src/Components/CreateImageWizard/ValidatedTextInput.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import React, { useState } from 'react';
import React, { ChangeEvent, useState } from 'react';

import {
HelperText,
HelperTextItem,
TextInput,
TextInputProps,
Button,
TextAreaProps,
TextArea,
} from '@patternfly/react-core';
import { EyeSlashIcon, EyeIcon } from '@patternfly/react-icons';

import type { StepValidation } from './utilities/useValidation';

Expand All @@ -30,6 +34,22 @@ interface HookValidatedTextInputPropTypes extends TextInputProps {
warning?: string;
}

interface HookValidatedTextAreaPropTypes extends TextAreaProps {
dataTestId?: string | undefined;
ariaLabel?: string;
value: string;
placeholder?: string;
stepValidation: StepValidation;
fieldName: string;
}

interface HookValidatedTextInputWithButtonPropTypes
extends HookValidatedTextInputPropTypes {
togglePasswordVisibility: () => void;
isPasswordVisible: boolean;
isEmpty: boolean;
}

export const HookValidatedInput = ({
dataTestId,
ouiaId,
Expand Down Expand Up @@ -90,6 +110,103 @@ export const HookValidatedInput = ({
);
};

// Lucas I tried to write a wrapper function that reuse as much as I can the exciting code,
// and there is a bug with that code - the error message appears on the right side instead of under the input.
export const HookValidatedInputWithPasswordVisibilityButton1 = (
props: HookValidatedTextInputWithButtonPropTypes
) => {
const { togglePasswordVisibility, isPasswordVisible, ...restProps } = props;
return (
<>
<div
style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
>
<HookValidatedInput {...restProps} style={{ paddingRight: '2rem' }} />
<Button
variant="plain"
onClick={togglePasswordVisibility}
aria-label="Toggle password visibility"
style={{
position: 'absolute',
right: '0.5rem',
}}
>
{isPasswordVisible ? <EyeSlashIcon /> : <EyeIcon />}
</Button>
</div>
{restProps.validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{restProps.stepValidation.errors[restProps.fieldName]}
</HelperTextItem>
</HelperText>
)}
</>
);
};

// this function also reuse some of the code and works as it should be -
// error message appears under the input
export const HookValidatedInputWithPasswordVisibilityButton = (
props: HookValidatedTextInputWithButtonPropTypes
) => {
const { togglePasswordVisibility, isPasswordVisible, ...restProps } = props;
const [isPristine, setIsPristine] = useState(!restProps.value ? true : false);
// Do not surface validation on pristine state components
// Allow step validation to be set on pristine state, when needed
const validated = restProps.isEmpty
? 'default'
: isPristine
? 'default'
: restProps.stepValidation.errors[restProps.fieldName] === 'default'
? 'default'
: restProps.stepValidation.errors[restProps.fieldName]
? 'error'
: 'success';

const handleBlur = () => {
setIsPristine(false);
};
return (
<>
<div
style={{ position: 'relative', display: 'flex', alignItems: 'center' }}
>
<TextInput
value={restProps.value}
data-testid={restProps.dataTestId}
ouiaId={restProps.ouiaId}
type={restProps.type}
onChange={restProps.onChange}
validated={validated}
aria-label={restProps.ariaLabel}
onBlur={handleBlur}
placeholder={restProps.placeholder}
style={{ paddingRight: '2rem' }}
/>
<Button
variant="plain"
onClick={togglePasswordVisibility}
aria-label="Toggle password visibility"
style={{
position: 'absolute',
right: '0.5rem',
}}
>
{isPasswordVisible ? <EyeSlashIcon /> : <EyeIcon />}
</Button>
</div>
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{restProps.stepValidation.errors[restProps.fieldName]}
</HelperTextItem>
</HelperText>
)}
</>
);
};

export const ValidatedTextInput = ({
dataTestId,
ouiaId,
Expand Down Expand Up @@ -137,3 +254,60 @@ export const ValidatedTextInput = ({
</>
);
};

export const HookValidatedTextArea = ({
dataTestId,
ariaLabel,
value,
placeholder,
onChange,
stepValidation,
fieldName,
type = 'text',
isDisabled = false,
}: HookValidatedTextAreaPropTypes) => {
const [isPristine, setIsPristine] = useState(!value ? true : false);
// Do not surface validation on pristine state components
// Allow step validation to be set on pristine state, when needed
const validated = isDisabled
? 'default'
: isPristine
? 'default'
: stepValidation.errors[fieldName] === 'default'
? 'default'
: stepValidation.errors[fieldName]
? 'error'
: 'success';

const handleBlur = () => {
setIsPristine(false);
};
const handleChange = (event: ChangeEvent<HTMLTextAreaElement>) => {
if (onChange) {
onChange(event, event.target.value);
}
};

return (
<>
<TextArea
value={value}
data-testid={dataTestId}
type={type}
onChange={handleChange}
validated={validated}
aria-label={ariaLabel}
onBlur={handleBlur}
placeholder={placeholder}
isDisabled={isDisabled}
/>
{validated === 'error' && (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{stepValidation.errors[fieldName]}
</HelperTextItem>
</HelperText>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,22 @@ import {
} from '@patternfly/react-core';
import UserIcon from '@patternfly/react-icons/dist/esm/icons/user-icon';

import { useAppDispatch } from '../../../../../store/hooks';
import { addUser } from '../../../../../store/wizardSlice';

const EmptyUserState = () => {
const dispatch = useAppDispatch();
const onAddUserClick = () => {
dispatch(addUser());
};
return (
<EmptyState variant={EmptyStateVariant.lg}>
<EmptyStateHeader
icon={<EmptyStateIcon icon={UserIcon} />}
headingLevel="h4"
/>
<EmptyStateFooter>
<Button variant="secondary" onClick={() => {}}>
<Button variant="secondary" onClick={onAddUserClick}>
Add a user
</Button>
</EmptyStateFooter>
Expand Down
Loading

0 comments on commit a56a823

Please sign in to comment.