diff --git a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx
index 48e9c3a36..65b3f82c2 100644
--- a/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx
+++ b/Resources/Private/JavaScript/asset-collections/src/components/AddTagButton.tsx
@@ -16,7 +16,15 @@ const AddTagButton: React.FC = () => {
const selectedTagId = useRecoilValue(selectedTagIdState);
const onClickCreate = useCallback(() => {
- setCreateTagDialogState({ label: '', visible: true });
+ setCreateTagDialogState({
+ visible: true,
+ label: '',
+ tags: [],
+ validation: {
+ valid: false,
+ errors: [],
+ },
+ });
}, [setCreateTagDialogState]);
return (
diff --git a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx
index f2af832f0..3f22e7148 100644
--- a/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx
+++ b/Resources/Private/JavaScript/asset-tags/src/components/CreateTagDialog.tsx
@@ -2,11 +2,13 @@ import * as React from 'react';
import { useCallback } from 'react';
import { useRecoilState } from 'recoil';
-import { Button, Label, TextInput } from '@neos-project/react-ui-components';
+import { Button, Label, TextInput, Tooltip } from '@neos-project/react-ui-components';
+import TAGS from '../queries/tags';
+import { useQuery } from '@apollo/client';
import { useIntl, useNotify } from '@media-ui/core';
import { useSelectedAssetCollection } from '@media-ui/feature-asset-collections';
-import { useCreateTag } from '@media-ui/feature-asset-tags';
+import { useCreateTag, useTagsQuery } from '@media-ui/feature-asset-tags';
import { Dialog } from '@media-ui/core/src/components';
import createTagDialogState from '../state/createTagDialogState';
@@ -18,10 +20,22 @@ const CreateTagDialog: React.FC = () => {
const Notify = useNotify();
const selectedAssetCollection = useSelectedAssetCollection();
const [dialogState, setDialogState] = useRecoilState(createTagDialogState);
- const createPossible = !!(dialogState.label && dialogState.label.trim());
const { createTag } = useCreateTag();
+ const { tags } = useTagsQuery();
- const handleRequestClose = useCallback(() => setDialogState({ visible: false, label: '' }), [setDialogState]);
+ const handleRequestClose = useCallback(
+ () =>
+ setDialogState({
+ visible: false,
+ label: '',
+ tags: [],
+ validation: {
+ valid: false,
+ errors: [],
+ },
+ }),
+ [setDialogState]
+ );
const handleCreate = useCallback(() => {
setDialogState((state) => ({ ...state, visible: false }));
createTag(dialogState.label, selectedAssetCollection?.id)
@@ -32,7 +46,32 @@ const CreateTagDialog: React.FC = () => {
return;
});
}, [Notify, setDialogState, createTag, dialogState, translate, selectedAssetCollection]);
- const setLabel = useCallback((label) => setDialogState((state) => ({ ...state, label })), [setDialogState]);
+ const validate = (label) => {
+ const validationErrors = [];
+ const trimmedLabel = label.trim();
+ const tagWithLabelExist = tags?.some((tag) => tag.label === trimmedLabel);
+
+ if (trimmedLabel.length === 0) {
+ validationErrors.push(translate('tagActions.validation.emtpyTagLabl', 'Please provide a tag label'));
+ }
+
+ if (tagWithLabelExist) {
+ validationErrors.push(translate('tagActions.validation.tagExists', 'A tag with this label already exists'));
+ }
+
+ const validation = {
+ errors: validationErrors,
+ valid: validationErrors.length === 0,
+ };
+ setDialogState((state) => ({ ...state, validation }));
+ };
+ const setLabel = useCallback(
+ (label) => {
+ validate(label);
+ setDialogState((state) => ({ ...state, label }));
+ },
+ [setDialogState]
+ );
return (
);
diff --git a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts
index 5e9e1d1ce..b0b1b4691 100644
--- a/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts
+++ b/Resources/Private/JavaScript/asset-tags/src/state/createTagDialogState.ts
@@ -5,6 +5,11 @@ const createTagDialogState = atom({
default: {
visible: false,
label: '',
+ tags: [],
+ validation: {
+ valid: false,
+ errors: [],
+ },
},
});
diff --git a/Resources/Private/JavaScript/media-module/tests/tags.ts b/Resources/Private/JavaScript/media-module/tests/tags.ts
index 52272f1aa..2955b3a13 100644
--- a/Resources/Private/JavaScript/media-module/tests/tags.ts
+++ b/Resources/Private/JavaScript/media-module/tests/tags.ts
@@ -1,8 +1,11 @@
import page from './page-model';
+import { ReactSelector } from 'testcafe-react-selectors';
import { SERVER_NAME } from './helpers';
fixture('Tags').page(SERVER_NAME);
+const subSection = (name) => console.log('\x1b[33m%s\x1b[0m', ' - ' + name);
+
test('Clicking first tag updates list and only assets should be shown that are assigned to it', async (t) => {
await t
// Uncollapse the tag list
@@ -19,3 +22,34 @@ test('Clicking first tag updates list and only assets should be shown that are a
.expect(page.assetCount.innerText)
.eql('12 assets');
});
+
+test('Create a new tag and test validation', async (t) => {
+ subSection('Check existing tag label validation');
+ await t
+ .click(page.assetCollections.withText('All'))
+ .click(page.collectionTree.findReact('AddTagButton'))
+ .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), 'Example tag 1')
+ .expect(
+ ReactSelector('CreateTagDialog')
+ .findReact('TextInput')
+ .withProps({ validationerrors: ['This input is invalid'] }).exists
+ )
+ .ok('Text input should have validation errors')
+ .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists)
+ .ok('Create button should be disabled')
+ .expect(ReactSelector('CreateTagDialog').find('ul li').textContent)
+ .eql('A tag with this label already exists')
+ .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), '00')
+ .expect(ReactSelector('CreateTagDialog').find('ul li').exists)
+ .notOk('The tooltip should not be visible anymore')
+ .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: false }).exists)
+ .ok('Create button should be enabled');
+
+ subSection('Check emtpy tag label validation');
+ await t
+ .typeText(ReactSelector('CreateTagDialog').findReact('TextInput'), ' ', { replace: true })
+ .expect(ReactSelector('CreateTagDialog').findReact('Button').withProps({ disabled: true }).exists)
+ .ok('Create button should be disabled')
+ .expect(ReactSelector('CreateTagDialog').find('ul li').textContent)
+ .eql('Please provide a tag label');
+});
diff --git a/Resources/Private/Translations/de/Main.xlf b/Resources/Private/Translations/de/Main.xlf
index 1b985d9e1..39768f730 100644
--- a/Resources/Private/Translations/de/Main.xlf
+++ b/Resources/Private/Translations/de/Main.xlf
@@ -412,6 +412,14 @@
Create tag
Tag erstellen
+
+ Please provide a tag label
+ Bitte geben Sie einen Tag-Namen ein
+
+
+ This tag is already exists. Please choose a different one.
+ Dieser Tag existiert bereits. Bitte wählen Sie einen anderen aus.
+
Tag was created
Tag wurde erstellt
diff --git a/Resources/Private/Translations/en/Main.xlf b/Resources/Private/Translations/en/Main.xlf
index 2cbd5f036..43b3b31fa 100644
--- a/Resources/Private/Translations/en/Main.xlf
+++ b/Resources/Private/Translations/en/Main.xlf
@@ -316,6 +316,12 @@
Tag was created
+
+ Please provide a tag label
+
+
+ This tag is already exists. Please choose a different one.
+
Failed to create tag