diff --git a/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx b/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx index 13746a6339f..329cc45d4ad 100644 --- a/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx +++ b/frontend/app-development/features/appContentLibrary/AppContentLibrary.tsx @@ -7,6 +7,9 @@ import { convertOptionListsToCodeLists } from './utils/convertOptionListsToCodeL import { StudioPageSpinner } from '@studio/components'; import { useTranslation } from 'react-i18next'; import { useAddOptionListMutation, useUpdateOptionListMutation } from 'app-shared/hooks/mutations'; +import type { ApiError } from 'app-shared/types/api/ApiError'; +import { toast } from 'react-toastify'; +import type { AxiosError } from 'axios'; export function AppContentLibrary(): React.ReactElement { const { org, app } = useStudioEnvironmentParams(); @@ -25,7 +28,16 @@ export function AppContentLibrary(): React.ReactElement { const codeLists = convertOptionListsToCodeLists(optionLists); const handleUpload = (file: File) => { - uploadOptionList(file); + uploadOptionList(file, { + onSuccess: () => { + toast.success(t('ux_editor.modal_properties_code_list_upload_success')); + }, + onError: (error: AxiosError) => { + if (!error.response?.data?.errorCode) { + toast.error(t('ux_editor.modal_properties_code_list_upload_generic_error')); + } + }, + }); }; const handleUpdate = ({ title, codeList }: CodeListWithMetadata) => { diff --git a/frontend/language/src/nb.json b/frontend/language/src/nb.json index a14d9a33dec..6e1c322fa5f 100644 --- a/frontend/language/src/nb.json +++ b/frontend/language/src/nb.json @@ -1793,6 +1793,7 @@ "ux_editor.upload_file_error_too_large": "Kunne ikke laste opp filen. Den er for stor.", "ux_editor.url_label": "Lenke", "ux_editor.warning": "Advarsel", + "validation_errors.file_name_invalid": "Filnavnet er ugyldig. Du kan bruke tall, understrek, punktum, bindestrek, og store/små bokstaver fra det norske alfabetet. Filnavnet må starte med en engelsk bokstav.", "validation_errors.length": "Antall tillatte tegn er {{0}}", "validation_errors.max": "Største gyldig verdi er {{0}}", "validation_errors.maxLength": "Bruk {{0}} eller færre tegn", @@ -1801,5 +1802,6 @@ "validation_errors.numbers_only": "Kun sifre er gyldige tegn", "validation_errors.pattern": "Feil format eller verdi", "validation_errors.required": "Feltet må fylles ut", + "validation_errors.upload_file_name_occupied": "Opplastning feilet. Du prøvde å laste opp en fil som finnes fra før.", "validation_errors.value_as_url": "Ugyldig lenke" } diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeList.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeList.tsx index 3838df10433..77e214d3586 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeList.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeList.tsx @@ -29,6 +29,8 @@ export function CodeList({ if (fetchDataError) return ; + const codeListTitles = codeLists.map((codeList) => codeList.title); + return (
{t('app_content_library.code_lists.page_name')} @@ -36,6 +38,7 @@ export function CodeList({
diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.test.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.test.tsx index d4b9b2643c7..fc16ace5aa9 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.test.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.test.tsx @@ -38,6 +38,10 @@ describe('CodeListsActionsBar', () => { const renderCodeListsActionsBar = () => { render( - , + , ); }; diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.tsx index 6488e028bcb..afe04279fd5 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CodeListsActionsBar.tsx @@ -5,17 +5,33 @@ import classes from './CodeListsActionsBar.module.css'; import { useTranslation } from 'react-i18next'; import type { CodeListWithMetadata } from '../CodeList'; import { CreateNewCodeListModal } from './CreateNewCodeListModal/CreateNewCodeListModal'; +import { FileNameValidationResult, FileNameUtils } from '@studio/pure-functions'; +import { useValidateFileName } from '../hooks/useValidateFileName'; type CodeListsActionsBarProps = { onUploadCodeList: (updatedCodeList: File) => void; onUpdateCodeList: (updatedCodeList: CodeListWithMetadata) => void; + codeListNames: string[]; }; export function CodeListsActionsBar({ onUploadCodeList, onUpdateCodeList, + codeListNames, }: CodeListsActionsBarProps) { const { t } = useTranslation(); + const { handleInvalidUploadedFileName } = useValidateFileName(); + + const onSubmit = (file: File) => { + const fileNameError = FileNameUtils.validateFileName( + FileNameUtils.removeExtension(file.name), + codeListNames, + ); + if (fileNameError !== FileNameValidationResult.Valid) + handleInvalidUploadedFileName(fileNameError); + else onUploadCodeList(file); + }; + return (
- +
); diff --git a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.tsx b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.tsx index 579f5a066d2..dce61b4c022 100644 --- a/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.tsx +++ b/frontend/libs/studio-content-library/src/ContentLibrary/LibraryBody/pages/CodeList/CodeListsActionsBar/CreateNewCodeListModal/CreateNewCodeListModal.tsx @@ -11,12 +11,18 @@ import { useOptionListEditorTexts } from '../../hooks/useCodeListEditorTexts'; import { CheckmarkIcon } from '@studio/icons'; import classes from './CreateNewCodeListModal.module.css'; import type { CodeListWithMetadata } from '../../CodeList'; +import { FileNameUtils, FileNameValidationResult } from '@studio/pure-functions'; +import { useValidateFileName } from '../../hooks/useValidateFileName'; type CreateNewCodeListModalProps = { onUpdateCodeList: (codeListWithMetadata: CodeListWithMetadata) => void; + codeListNames: string[]; }; -export function CreateNewCodeListModal({ onUpdateCodeList }: CreateNewCodeListModalProps) { +export function CreateNewCodeListModal({ + onUpdateCodeList, + codeListNames, +}: CreateNewCodeListModalProps) { const { t } = useTranslation(); const modalRef = createRef(); @@ -39,6 +45,7 @@ export function CreateNewCodeListModal({ onUpdateCodeList }: CreateNewCodeListMo > @@ -49,14 +56,22 @@ export function CreateNewCodeListModal({ onUpdateCodeList }: CreateNewCodeListMo type CreateNewCodeListProps = { codeList: CodeList; + codeListNames: string[]; onUpdateCodeList: (codeListWithMetadata: CodeListWithMetadata) => void; onCloseModal: () => void; }; -function CreateNewCodeList({ codeList, onUpdateCodeList, onCloseModal }: CreateNewCodeListProps) { +function CreateNewCodeList({ + codeList, + codeListNames, + onUpdateCodeList, + onCloseModal, +}: CreateNewCodeListProps) { const { t } = useTranslation(); const editorTexts: CodeListEditorTexts = useOptionListEditorTexts(); + const { getInvalidInputFileNameErrorMessage } = useValidateFileName(); const [isCodeListValid, setIsCodeListValid] = useState(true); + const [codeListTitleError, setCodeListTitleError] = useState(''); const [currentCodeListWithMetadata, setCurrentCodeListWithMetadata] = useState({ title: '', @@ -69,10 +84,14 @@ function CreateNewCodeList({ codeList, onUpdateCodeList, onCloseModal }: CreateN }; const handleCodeListTitleChange = (codeListTitle: string) => { - setCurrentCodeListWithMetadata({ - title: codeListTitle, - codeList: currentCodeListWithMetadata.codeList, - }); + const fileNameError = FileNameUtils.validateFileName(codeListTitle, codeListNames); + const errorMessage = getInvalidInputFileNameErrorMessage(fileNameError); + setCodeListTitleError(errorMessage); + if (fileNameError === FileNameValidationResult.Valid) + setCurrentCodeListWithMetadata({ + title: codeListTitle, + codeList: currentCodeListWithMetadata.codeList, + }); }; const handleCodeListChange = (updatedCodeList: CodeList) => { @@ -87,7 +106,8 @@ function CreateNewCodeList({ codeList, onUpdateCodeList, onCloseModal }: CreateN setIsCodeListValid(false); }; - const isSaveButtonDisabled = !isCodeListValid || !currentCodeListWithMetadata.title; + const isSaveButtonDisabled = + !isCodeListValid || !currentCodeListWithMetadata.title || codeListTitleError; return (
@@ -96,6 +116,7 @@ function CreateNewCodeList({ codeList, onUpdateCodeList, onCloseModal }: CreateN className={classes.codeListTitle} size='small' onChange={(event) => handleCodeListTitleChange(event.target.value)} + error={codeListTitleError} />
{ + switch (fileNameError) { + case FileNameValidationResult.NoRegExMatch: + return toast.error(t('validation_errors.file_name_invalid')); + case FileNameValidationResult.FileExists: + return toast.error(t('validation_errors.upload_file_name_occupied')); + default: + return null; + } + }; + + const getInvalidInputFileNameErrorMessage = (fileNameError: FileNameValidationResult) => { + switch (fileNameError) { + case FileNameValidationResult.FileNameIsEmpty: + return t('validation_errors.required'); + case FileNameValidationResult.NoRegExMatch: + return t('validation_errors.file_name_invalid'); + case FileNameValidationResult.FileExists: + return t('validation_errors.file_name_occupied'); + default: + return ''; + } + }; + + return { handleInvalidUploadedFileName, getInvalidInputFileNameErrorMessage }; +} diff --git a/frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.test.ts b/frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.test.ts similarity index 54% rename from frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.test.ts rename to frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.test.ts index ae51eb22ec8..3ccc140147c 100644 --- a/frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.test.ts +++ b/frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.test.ts @@ -1,4 +1,4 @@ -import { FileNameUtils } from './FilenameUtils'; +import { FileNameUtils, FileNameValidationResult } from './FileNameUtils'; describe('FileNameUtils', () => { describe('removeExtension', () => { @@ -57,13 +57,13 @@ describe('FileNameUtils', () => { describe('extractFilename', () => { it('Returns filename if path contains a slash', () => { - expect(FileNameUtils.extractFilename('/path/to/filename')).toEqual('filename'); - expect(FileNameUtils.extractFilename('/path/to/filename.json')).toEqual('filename.json'); + expect(FileNameUtils.extractFileName('/path/to/filename')).toEqual('filename'); + expect(FileNameUtils.extractFileName('/path/to/filename.json')).toEqual('filename.json'); }); it('Returns path if path does not contain a slash', () => { - expect(FileNameUtils.extractFilename('filename')).toEqual('filename'); - expect(FileNameUtils.extractFilename('filename.json')).toEqual('filename.json'); + expect(FileNameUtils.extractFileName('filename')).toEqual('filename'); + expect(FileNameUtils.extractFileName('filename.json')).toEqual('filename.json'); }); }); @@ -90,4 +90,79 @@ describe('FileNameUtils', () => { expect(FileNameUtils.removeFileNameFromPath('filename.json', true)).toEqual(''); }); }); + + describe('validateFileName', () => { + it('Returns "FileNameIsEmpty" when file name is empty', () => { + const fileName: string = ''; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + [], + ); + expect(fileNameValidation).toBe(FileNameValidationResult.FileNameIsEmpty); + }); + + it('Returns "NoRegExMatch" when file name does not match given regex', () => { + const fileName: string = 'ABC'; + const fileNameRegEx: RegExp = /^[a-z]+$/; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + [], + fileNameRegEx, + ); + expect(fileNameValidation).toBe(FileNameValidationResult.NoRegExMatch); + }); + + it('Returns "FileExists" when file name matches regEx and exists in list', () => { + const fileName: string = 'fileName1'; + const invalidFileNames: string[] = ['fileName1', 'fileName2', 'fileName3']; + const fileNameRegEx: RegExp = /^[a-zA-Z0-9]+$/; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + invalidFileNames, + fileNameRegEx, + ); + expect(fileNameValidation).toBe(FileNameValidationResult.FileExists); + }); + + it('Returns "FileExists" when no regEx is provided and exists in list', () => { + const fileName: string = 'fileName1'; + const invalidFileNames: string[] = ['fileName1', 'fileName2', 'fileName3']; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + invalidFileNames, + ); + expect(fileNameValidation).toBe(FileNameValidationResult.FileExists); + }); + + it('Returns "Valid" when file name matches regEx and does not exist in list of invalid names', () => { + const fileName: string = 'fileName'; + const invalidFileNames: string[] = ['fileName2', 'fileName3']; + const fileNameRegEx: RegExp = /^[a-zA-Z]+$/; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + invalidFileNames, + fileNameRegEx, + ); + expect(fileNameValidation).toBe(FileNameValidationResult.Valid); + }); + + it('Returns "Valid" when no regEx is provided and file name does not exist in list of invalid names', () => { + const fileName: string = 'fileName'; + const invalidFileNames: string[] = ['fileName2', 'fileName3']; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + invalidFileNames, + ); + expect(fileNameValidation).toBe(FileNameValidationResult.Valid); + }); + + it('Returns "Valid" when no regEx is provided and list of invalid names is empty', () => { + const fileName: string = 'fileName'; + const fileNameValidation: FileNameValidationResult = FileNameUtils.validateFileName( + fileName, + [], + ); + expect(fileNameValidation).toBe(FileNameValidationResult.Valid); + }); + }); }); diff --git a/frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.ts b/frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.ts similarity index 55% rename from frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.ts rename to frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.ts index fdd8b63c166..dd02eefde4f 100644 --- a/frontend/libs/studio-pure-functions/src/FileNameUtils/FilenameUtils.ts +++ b/frontend/libs/studio-pure-functions/src/FileNameUtils/FileNameUtils.ts @@ -1,5 +1,12 @@ import { StringUtils } from '@studio/pure-functions'; +export enum FileNameValidationResult { + FileNameIsEmpty = 'fileNameIsEmpty', + NoRegExMatch = 'noRegExMatch', + FileExists = 'fileExists', + Valid = 'valid', +} + export class FileNameUtils { /** * Remove extension from filename. @@ -26,17 +33,47 @@ export class FileNameUtils { */ static isXsdFile = (filename: string): boolean => filename.toLowerCase().endsWith('.xsd'); - static extractFilename = (path: string): string => { + static extractFileName = (path: string): string => { const indexOfLastSlash = path.lastIndexOf('/'); return indexOfLastSlash < 0 ? path : path.substring(indexOfLastSlash + 1); }; static removeFileNameFromPath = (path: string, excludeLastSlash: boolean = false): string => { - const fileName = this.extractFilename(path); + const fileName = this.extractFileName(path); const indexOfLastSlash = path.lastIndexOf('/'); return path.slice( 0, path.lastIndexOf(excludeLastSlash && indexOfLastSlash > 0 ? '/' + fileName : fileName), ); }; + + /** + * Validates if file name does not exist in list of invalid names and if name matches regEx, if provided. + * @param fileName + * @param invalidFileNames + * @param regEx + * @returns FileNameValidationResult + */ + static validateFileName = ( + fileName: string, + invalidFileNames: string[], + regEx?: RegExp, + ): FileNameValidationResult => { + if (fileName === '') { + return FileNameValidationResult.FileNameIsEmpty; + } + + const isFileNameNotMatchingRegEx: boolean = regEx ? Boolean(!fileName.match(regEx)) : false; + const isFileNameInInvalidList: boolean = invalidFileNames.some( + (invalidFileName) => invalidFileName === fileName, + ); + + if (isFileNameNotMatchingRegEx) { + return FileNameValidationResult.NoRegExMatch; + } + if (isFileNameInInvalidList) { + return FileNameValidationResult.FileExists; + } + return FileNameValidationResult.Valid; + }; } diff --git a/frontend/libs/studio-pure-functions/src/FileNameUtils/index.ts b/frontend/libs/studio-pure-functions/src/FileNameUtils/index.ts index 179352dfda5..61024b8bb54 100644 --- a/frontend/libs/studio-pure-functions/src/FileNameUtils/index.ts +++ b/frontend/libs/studio-pure-functions/src/FileNameUtils/index.ts @@ -1 +1,2 @@ -export { FileNameUtils } from './FilenameUtils'; +export { FileNameUtils } from './FileNameUtils'; +export { FileNameValidationResult } from './FileNameUtils'; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/EditOptionList.tsx b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/EditOptionList.tsx index 101659260be..6f353fc068a 100644 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/EditOptionList.tsx +++ b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/EditOptionList.tsx @@ -9,9 +9,7 @@ import { altinnDocsUrl } from 'app-shared/ext-urls'; import { FormField } from '../../../../../FormField'; import { useStudioEnvironmentParams } from 'app-shared/hooks/useStudioEnvironmentParams'; import type { SelectionComponentType } from '../../../../../../types/FormComponent'; -import { FileNameUtils } from '@studio/pure-functions'; -import { findFileNameError } from './utils/findFileNameError'; -import type { FileNameError } from './utils/findFileNameError'; +import { FileNameUtils, FileNameValidationResult } from '@studio/pure-functions'; import type { AxiosError } from 'axios'; import type { ApiError } from 'app-shared/types/api/ApiError'; import { toast } from 'react-toastify'; @@ -42,12 +40,12 @@ export function EditOptionList({ }; const onSubmit = (file: File) => { - const fileNameError = findFileNameError(optionListIds, file.name); - if (fileNameError) { - handleInvalidFileName(fileNameError); - } else { - handleUpload(file); - } + const fileNameError = FileNameUtils.validateFileName( + FileNameUtils.removeExtension(file.name), + optionListIds, + ); + if (fileNameError !== FileNameValidationResult.Valid) handleInvalidFileName(fileNameError); + else handleUpload(file); }; const handleUpload = (file: File) => { @@ -58,18 +56,20 @@ export function EditOptionList({ }, onError: (error: AxiosError) => { if (!error.response?.data?.errorCode) { - toast.error(`${t('ux_editor.modal_properties_code_list_upload_generic_error')}`); + toast.error(t('ux_editor.modal_properties_code_list_upload_generic_error')); } }, }); }; - const handleInvalidFileName = (fileNameError: FileNameError) => { + const handleInvalidFileName = (fileNameError: FileNameValidationResult) => { switch (fileNameError) { - case 'invalidFileName': - return toast.error(t('ux_editor.modal_properties_code_list_filename_error')); - case 'fileExists': - return toast.error(t('ux_editor.modal_properties_code_list_upload_duplicate_error')); + case FileNameValidationResult.NoRegExMatch: + return toast.error(t('validation_errors.file_name_invalid')); + case FileNameValidationResult.FileExists: + return toast.error(t('validation_errors.upload_file_name_occupied')); + default: + return null; } }; diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.test.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.test.ts deleted file mode 100644 index 108f40ccc63..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.test.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { findFileNameError } from './findFileNameError'; - -const optionListIdOne = 'one'; -const optionLists: string[] = [optionListIdOne]; - -const validFilename = 'two.json'; -const invalidFilename = '_InvalidFileName.json'; - -describe('findFileNameError', () => { - it('should return null for valid filename', () => { - expect(findFileNameError(optionLists, validFilename)).toBe(null); - }); - - it('should return "invalidFileName" for invalid filename', () => { - expect(findFileNameError(optionLists, invalidFilename)).toBe('invalidFileName'); - }); - - it('should return "fileExists" for duplicate filename', () => { - expect(findFileNameError(optionLists, optionListIdOne + '.json')).toBe('fileExists'); - }); -}); diff --git a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.ts b/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.ts deleted file mode 100644 index f13182f7336..00000000000 --- a/frontend/packages/ux-editor/src/components/config/editModal/EditOptions/OptionTabs/EditOptionList/utils/findFileNameError.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { FileNameUtils } from '@studio/pure-functions'; - -export type FileNameError = 'invalidFileName' | 'fileExists'; - -export const findFileNameError = ( - optionListIds: string[], - fileName: string, -): FileNameError | null => { - const fileNameWithoutExtension = FileNameUtils.removeExtension(fileName); - - if (!isFilenameValid(fileNameWithoutExtension)) { - return 'invalidFileName'; - } else if (isFileNameDuplicate(optionListIds, fileNameWithoutExtension)) { - return 'fileExists'; - } else { - return null; - } -}; - -const isFilenameValid = (fileName: string): boolean => { - return Boolean(fileName.match(/^[a-zA-Z][a-zA-Z0-9_.\-æÆøØåÅ ]*$/)); -}; - -const isFileNameDuplicate = ( - optionListIds: string[], - fileNameWithoutExtension: string, -): boolean => { - return optionListIds.some((option) => option === fileNameWithoutExtension); -};