Wizard: Refactor HookValidatedInput component

This commit splits the HookValidatedInput component into three separate functions to improve modularity and readability:

- `getValidationState`: Calculates the validation state ('default', 'success', 'error') based on whether the input is pristine and if there is an error message.
- `ValidatedInputAndTextArea`: Renders the TextInput or TextArea component, utilizing the `getValidationState` output.
- `ErrorMessage`: Displays validation error messages.

This refactoring enhances code maintainability and testability, and the updated structure is now implemented for the username field.
This commit is contained in:
Michal Gold 2025-02-20 12:28:04 +02:00 committed by Lucas Garfield
parent ba233f2c69
commit 49fa0ee735
4 changed files with 101 additions and 11 deletions

View file

@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import {
HelperText,
@ -15,7 +15,7 @@ import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
import type { StepValidation } from './utilities/useValidation';
interface ValidatedTextInputPropTypes extends TextInputProps {
type ValidatedTextInputPropTypes = TextInputProps & {
dataTestId?: string | undefined;
ouiaId?: string;
ariaLabel: string | undefined;
@ -23,7 +23,7 @@ interface ValidatedTextInputPropTypes extends TextInputProps {
validator: (value: string | undefined) => boolean;
value: string;
placeholder?: string;
}
};
type HookValidatedInputPropTypes = TextInputProps &
TextAreaProps & {
@ -38,6 +38,26 @@ type HookValidatedInputPropTypes = TextInputProps &
inputType?: 'textInput' | 'textArea';
};
type ValidationInputProp = TextInputProps &
TextAreaProps & {
value: string;
placeholder: string;
stepValidation: StepValidation;
fieldName: string;
inputType?: 'textInput' | 'textArea';
ariaLabel: string;
onChange: (
event: React.FormEvent<HTMLInputElement | HTMLTextAreaElement>,
value: string
) => void;
};
type ErrorMessageProps = {
errorMessage: string;
};
type ValidationResult = 'default' | 'success' | 'error';
export const HookPasswordValidatedInput = ({
ariaLabel,
placeholder,
@ -89,6 +109,78 @@ export const HookPasswordValidatedInput = ({
);
};
export const ValidatedInputAndTextArea = ({
value,
stepValidation,
fieldName,
placeholder,
onChange,
ariaLabel,
inputType = 'textInput',
}: ValidationInputProp) => {
const errorMessage = stepValidation.errors[fieldName];
const hasError = errorMessage !== '';
const [isPristine, setIsPristine] = useState(!value);
const validated = getValidationState(isPristine, errorMessage);
const handleBlur = () => {
if (value) {
setIsPristine(false);
}
};
useEffect(() => {
if (!value) {
setIsPristine(true);
}
}, [value, setIsPristine]);
return (
<>
{inputType === 'textArea' ? (
<TextArea
value={value}
onChange={onChange}
validated={validated}
onBlur={handleBlur}
placeholder={placeholder}
aria-label={ariaLabel}
/>
) : (
<TextInput
value={value}
onChange={onChange}
validated={validated}
onBlur={handleBlur}
placeholder={placeholder}
aria-label={ariaLabel}
/>
)}
{hasError && <ErrorMessage errorMessage={errorMessage} />}
</>
);
};
const getValidationState = (
isPristine: boolean,
errorMessage: string
): ValidationResult => {
const validated = isPristine ? 'default' : errorMessage ? 'error' : 'success';
return validated;
};
export const ErrorMessage = ({ errorMessage }: ErrorMessageProps) => {
return (
<HelperText>
<HelperTextItem variant="error" hasIcon>
{errorMessage}
</HelperTextItem>
</HelperText>
);
};
export const HookValidatedInput = ({
dataTestId,
ouiaId,

View file

@ -20,6 +20,7 @@ import { useUsersValidation } from '../../../utilities/useValidation';
import {
HookPasswordValidatedInput,
HookValidatedInput,
ValidatedInputAndTextArea,
} from '../../../ValidatedInput';
const UserInfo = () => {
const dispatch = useAppDispatch();
@ -72,7 +73,7 @@ const UserInfo = () => {
return (
<>
<FormGroup isRequired label="Username">
<HookValidatedInput
<ValidatedInputAndTextArea
ariaLabel="blueprint user name"
value={userName || ''}
placeholder="Enter username"

View file

@ -414,12 +414,10 @@ export function useUsersValidation(): StepValidation {
return {
errors: {
userName: !isUserNameValid(userName) ? 'Invalid user name' : '',
userSshKey: !userSshKey
? ''
: !isSshKeyValid(userSshKey)
? 'Invalid SSH key'
: '',
userName:
userName && !isUserNameValid(userName) ? 'Invalid user name' : '',
userSshKey:
userSshKey && !isSshKeyValid(userSshKey) ? 'Invalid SSH key' : '',
},
disabledNext: !canProceed,
};

View file

@ -58,7 +58,6 @@ export const isFileSystemConfigValid = (partitions: Partition[]) => {
};
export const isUserNameValid = (userName: string) => {
if (userName === undefined) return false;
const isLengthValid = userName.length <= 32;
const isNotNumericOnly = !/^\d+$/.test(userName);
const isPatternValid = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9_$]$/.test(