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:
parent
ba233f2c69
commit
49fa0ee735
4 changed files with 101 additions and 11 deletions
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
HelperText,
|
HelperText,
|
||||||
|
|
@ -15,7 +15,7 @@ import { EyeIcon, EyeSlashIcon } from '@patternfly/react-icons';
|
||||||
|
|
||||||
import type { StepValidation } from './utilities/useValidation';
|
import type { StepValidation } from './utilities/useValidation';
|
||||||
|
|
||||||
interface ValidatedTextInputPropTypes extends TextInputProps {
|
type ValidatedTextInputPropTypes = TextInputProps & {
|
||||||
dataTestId?: string | undefined;
|
dataTestId?: string | undefined;
|
||||||
ouiaId?: string;
|
ouiaId?: string;
|
||||||
ariaLabel: string | undefined;
|
ariaLabel: string | undefined;
|
||||||
|
|
@ -23,7 +23,7 @@ interface ValidatedTextInputPropTypes extends TextInputProps {
|
||||||
validator: (value: string | undefined) => boolean;
|
validator: (value: string | undefined) => boolean;
|
||||||
value: string;
|
value: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
type HookValidatedInputPropTypes = TextInputProps &
|
type HookValidatedInputPropTypes = TextInputProps &
|
||||||
TextAreaProps & {
|
TextAreaProps & {
|
||||||
|
|
@ -38,6 +38,26 @@ type HookValidatedInputPropTypes = TextInputProps &
|
||||||
inputType?: 'textInput' | 'textArea';
|
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 = ({
|
export const HookPasswordValidatedInput = ({
|
||||||
ariaLabel,
|
ariaLabel,
|
||||||
placeholder,
|
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 = ({
|
export const HookValidatedInput = ({
|
||||||
dataTestId,
|
dataTestId,
|
||||||
ouiaId,
|
ouiaId,
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import { useUsersValidation } from '../../../utilities/useValidation';
|
||||||
import {
|
import {
|
||||||
HookPasswordValidatedInput,
|
HookPasswordValidatedInput,
|
||||||
HookValidatedInput,
|
HookValidatedInput,
|
||||||
|
ValidatedInputAndTextArea,
|
||||||
} from '../../../ValidatedInput';
|
} from '../../../ValidatedInput';
|
||||||
const UserInfo = () => {
|
const UserInfo = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|
@ -72,7 +73,7 @@ const UserInfo = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<FormGroup isRequired label="Username">
|
<FormGroup isRequired label="Username">
|
||||||
<HookValidatedInput
|
<ValidatedInputAndTextArea
|
||||||
ariaLabel="blueprint user name"
|
ariaLabel="blueprint user name"
|
||||||
value={userName || ''}
|
value={userName || ''}
|
||||||
placeholder="Enter username"
|
placeholder="Enter username"
|
||||||
|
|
|
||||||
|
|
@ -414,12 +414,10 @@ export function useUsersValidation(): StepValidation {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
errors: {
|
errors: {
|
||||||
userName: !isUserNameValid(userName) ? 'Invalid user name' : '',
|
userName:
|
||||||
userSshKey: !userSshKey
|
userName && !isUserNameValid(userName) ? 'Invalid user name' : '',
|
||||||
? ''
|
userSshKey:
|
||||||
: !isSshKeyValid(userSshKey)
|
userSshKey && !isSshKeyValid(userSshKey) ? 'Invalid SSH key' : '',
|
||||||
? 'Invalid SSH key'
|
|
||||||
: '',
|
|
||||||
},
|
},
|
||||||
disabledNext: !canProceed,
|
disabledNext: !canProceed,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ export const isFileSystemConfigValid = (partitions: Partition[]) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isUserNameValid = (userName: string) => {
|
export const isUserNameValid = (userName: string) => {
|
||||||
if (userName === undefined) return false;
|
|
||||||
const isLengthValid = userName.length <= 32;
|
const isLengthValid = userName.length <= 32;
|
||||||
const isNotNumericOnly = !/^\d+$/.test(userName);
|
const isNotNumericOnly = !/^\d+$/.test(userName);
|
||||||
const isPatternValid = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9_$]$/.test(
|
const isPatternValid = /^[a-zA-Z0-9][a-zA-Z0-9_.-]*[a-zA-Z0-9_$]$/.test(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue