From 62bbf6d6886425cbe97c3998d713018f894d44f7 Mon Sep 17 00:00:00 2001 From: regexowl Date: Thu, 13 Feb 2025 15:26:57 +0100 Subject: [PATCH] Wizard: Add step validation to Locale This adds step validation to Locale, allowing to properly validate imported values. --- .../CreateImageWizard/CreateImageWizard.tsx | 9 ++++- .../Locale/components/KeyboardDropDown.tsx | 13 +++++++ .../Locale/components/LanguagesDropDown.tsx | 26 ++++++++++++- .../utilities/useValidation.tsx | 37 +++++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/src/Components/CreateImageWizard/CreateImageWizard.tsx b/src/Components/CreateImageWizard/CreateImageWizard.tsx index 5373d264..a8f6e3c4 100644 --- a/src/Components/CreateImageWizard/CreateImageWizard.tsx +++ b/src/Components/CreateImageWizard/CreateImageWizard.tsx @@ -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={ - + } > diff --git a/src/Components/CreateImageWizard/steps/Locale/components/KeyboardDropDown.tsx b/src/Components/CreateImageWizard/steps/Locale/components/KeyboardDropDown.tsx index 913b019b..4709181b 100644 --- a/src/Components/CreateImageWizard/steps/Locale/components/KeyboardDropDown.tsx +++ b/src/Components/CreateImageWizard/steps/Locale/components/KeyboardDropDown.tsx @@ -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(''); const [filterValue, setFilterValue] = useState(''); @@ -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 = () => { ))} + {errorText && ( + + {errorText} + + )} ); }; diff --git a/src/Components/CreateImageWizard/steps/Locale/components/LanguagesDropDown.tsx b/src/Components/CreateImageWizard/steps/Locale/components/LanguagesDropDown.tsx index dfa2988b..ec899078 100644 --- a/src/Components/CreateImageWizard/steps/Locale/components/LanguagesDropDown.tsx +++ b/src/Components/CreateImageWizard/steps/Locale/components/LanguagesDropDown.tsx @@ -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(''); const [filterValue, setFilterValue] = useState(''); @@ -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) => ( { ))} + {unknownLanguages.length > 0 && ( + + {`Unknown languages: ${unknownLanguages.join( + ', ' + )}`} + + )} {languages?.map((lang) => ( - dispatch(removeLanguage(lang))}> + handleRemoveLang(e, lang)}> {lang} ))} diff --git a/src/Components/CreateImageWizard/utilities/useValidation.tsx b/src/Components/CreateImageWizard/utilities/useValidation.tsx index 0f4facd8..7763c3aa 100644 --- a/src/Components/CreateImageWizard/utilities/useValidation.tsx +++ b/src/Components/CreateImageWizard/utilities/useValidation.tsx @@ -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;