Wizard: Add step validation to Locale

This adds step validation to Locale, allowing to properly validate imported values.
This commit is contained in:
regexowl 2025-02-13 15:26:57 +01:00 committed by Klara Simickova
parent 52c790bb4a
commit 62bbf6d688
4 changed files with 83 additions and 2 deletions

View file

@ -47,6 +47,7 @@ import {
useTimezoneValidation,
useFirewallValidation,
useServicesValidation,
useLocaleValidation,
} from './utilities/useValidation';
import {
isAwsAccountIdValid,
@ -225,6 +226,8 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
const fileSystemValidation = useFilesystemValidation();
// Timezone
const timezoneValidation = useTimezoneValidation();
// Locale
const localeValidation = useLocaleValidation();
// Hostname
const hostnameValidation = useHostnameValidation();
// Kernel
@ -506,8 +509,12 @@ const CreateImageWizard = ({ isEdit }: CreateImageWizardProps) => {
key="wizard-locale"
navItem={customStatusNavItem}
isHidden={!isLocaleEnabled}
status={localeValidation.disabledNext ? 'error' : 'default'}
footer={
<CustomWizardFooter disableNext={false} optional={true} />
<CustomWizardFooter
disableNext={localeValidation.disabledNext}
optional={true}
/>
}
>
<LocaleStep />

View file

@ -3,6 +3,8 @@ import React, { useEffect, useState } from 'react';
import {
Button,
FormGroup,
HelperText,
HelperTextItem,
MenuToggle,
MenuToggleElement,
Select,
@ -20,12 +22,16 @@ import {
selectKeyboard,
} from '../../../../../store/wizardSlice';
import sortfn from '../../../../../Utilities/sortfn';
import { useLocaleValidation } from '../../../utilities/useValidation';
import { keyboardsList } from '../keyboardsList';
const KeyboardDropDown = () => {
const keyboard = useAppSelector(selectKeyboard);
const dispatch = useAppDispatch();
const stepValidation = useLocaleValidation();
const [errorText, setErrorText] = useState(stepValidation.errors['keyboard']);
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState<string>('');
const [filterValue, setFilterValue] = useState<string>('');
@ -70,6 +76,7 @@ const KeyboardDropDown = () => {
if (value && !value.includes('No results')) {
setInputValue(value);
setFilterValue('');
setErrorText('');
dispatch(changeKeyboard(value));
setIsOpen(false);
}
@ -91,6 +98,7 @@ const KeyboardDropDown = () => {
const onClearButtonClick = () => {
setInputValue('');
setFilterValue('');
setErrorText('');
dispatch(changeKeyboard(''));
};
@ -146,6 +154,11 @@ const KeyboardDropDown = () => {
))}
</SelectList>
</Select>
{errorText && (
<HelperText>
<HelperTextItem variant={'error'}>{errorText}</HelperTextItem>
</HelperText>
)}
</FormGroup>
);
};

View file

@ -5,6 +5,8 @@ import {
Chip,
ChipGroup,
FormGroup,
HelperText,
HelperTextItem,
MenuToggle,
MenuToggleElement,
Select,
@ -23,12 +25,18 @@ import {
selectLanguages,
} from '../../../../../store/wizardSlice';
import sortfn from '../../../../../Utilities/sortfn';
import { useLocaleValidation } from '../../../utilities/useValidation';
import { languagesList } from '../languagesList';
const LanguagesDropDown = () => {
const languages = useAppSelector(selectLanguages);
const dispatch = useAppDispatch();
const stepValidation = useLocaleValidation();
const unknownLanguages = stepValidation.errors['languages']
? stepValidation.errors['languages'].split(' ')
: [];
const [isOpen, setIsOpen] = useState(false);
const [inputValue, setInputValue] = useState<string>('');
const [filterValue, setFilterValue] = useState<string>('');
@ -92,6 +100,13 @@ const LanguagesDropDown = () => {
setFilterValue('');
};
const handleRemoveLang = (_event: React.MouseEvent, value: string) => {
dispatch(removeLanguage(value));
if (unknownLanguages.length > 0) {
unknownLanguages.filter((lang) => lang !== value);
}
};
const toggle = (toggleRef: React.Ref<MenuToggleElement>) => (
<MenuToggle
ref={toggleRef}
@ -143,9 +158,18 @@ const LanguagesDropDown = () => {
))}
</SelectList>
</Select>
{unknownLanguages.length > 0 && (
<HelperText>
<HelperTextItem
variant={'error'}
>{`Unknown languages: ${unknownLanguages.join(
', '
)}`}</HelperTextItem>
</HelperText>
)}
<ChipGroup numChips={5} className="pf-v5-u-mt-sm pf-v5-u-w-100">
{languages?.map((lang) => (
<Chip key={lang} onClick={() => dispatch(removeLanguage(lang))}>
<Chip key={lang} onClick={(e) => handleRemoveLang(e, lang)}>
{lang}
</Chip>
))}

View file

@ -28,7 +28,11 @@ import {
selectNtpServers,
selectFirewall,
selectServices,
selectLanguages,
selectKeyboard,
} from '../../../store/wizardSlice';
import { keyboardsList } from '../steps/Locale/keyboardsList';
import { languagesList } from '../steps/Locale/languagesList';
import {
getDuplicateMountPoints,
isBlueprintNameValid,
@ -57,6 +61,7 @@ export function useIsBlueprintValid(): boolean {
const filesystem = useFilesystemValidation();
const snapshot = useSnapshotValidation();
const timezone = useTimezoneValidation();
const locale = useLocaleValidation();
const hostname = useHostnameValidation();
const kernel = useKernelValidation();
const firewall = useFirewallValidation();
@ -68,6 +73,7 @@ export function useIsBlueprintValid(): boolean {
!filesystem.disabledNext &&
!snapshot.disabledNext &&
!timezone.disabledNext &&
!locale.disabledNext &&
!hostname.disabledNext &&
!kernel.disabledNext &&
!firewall.disabledNext &&
@ -179,6 +185,37 @@ export function useTimezoneValidation(): StepValidation {
return { errors: {}, disabledNext: false };
}
export function useLocaleValidation(): StepValidation {
const languages = useAppSelector(selectLanguages);
const keyboard = useAppSelector(selectKeyboard);
const errors = {};
const unknownLanguages = [];
if (languages && languages.length > 0) {
for (const lang of languages) {
if (!languagesList.includes(lang)) {
unknownLanguages.push(lang);
}
}
if (unknownLanguages.length > 0) {
Object.assign(errors, {
languages: unknownLanguages.join(' '),
});
}
}
if (keyboard && !keyboardsList.includes(keyboard)) {
Object.assign(errors, { keyboard: 'Unknown keyboard' });
}
return {
errors,
disabledNext: unknownLanguages.length > 0 || 'keyboard' in errors,
};
}
export function useFirstBootValidation(): StepValidation {
const script = useAppSelector(selectFirstBootScript);
let hasShebang = false;