cap-creatives-ui 8.0.280 → 8.0.321
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/pr-title-check.yml +88 -0
- package/app/constants/unified.js +21 -1
- package/app/containers/App/constants.js +0 -1
- package/app/containers/Login/test/index.test.js +123 -0
- package/app/containers/Login/test/selectors.test.js +165 -0
- package/app/initialState.js +0 -2
- package/app/services/api.js +6 -0
- package/app/services/tests/api.test.js +7 -0
- package/app/services/tests/getSchema.test.js +95 -0
- package/app/utils/common.js +23 -9
- package/app/utils/commonUtils.js +64 -93
- package/app/utils/tagValidations.js +83 -219
- package/app/utils/templateVarUtils.js +172 -0
- package/app/utils/tests/common.test.js +265 -323
- package/app/utils/tests/commonUtil.test.js +461 -118
- package/app/utils/tests/commonUtils.test.js +581 -0
- package/app/utils/tests/messageUtils.test.js +95 -0
- package/app/utils/tests/smsCharCount.test.js +304 -0
- package/app/utils/tests/smsCharCountV2.test.js +213 -10
- package/app/utils/tests/tagValidations.test.js +474 -357
- package/app/utils/tests/templateVarUtils.test.js +160 -0
- package/app/v2Components/CapDeviceContent/index.js +10 -7
- package/app/v2Components/CapTagList/index.js +32 -24
- package/app/v2Components/CapTagList/style.scss +48 -0
- package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
- package/app/v2Components/CapTagListWithInput/index.js +8 -0
- package/app/v2Components/CapWhatsappCTA/index.js +2 -0
- package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
- package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
- package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
- package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
- package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
- package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
- package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
- package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
- package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
- package/app/v2Components/CommonTestAndPreview/index.js +878 -156
- package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
- package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
- package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
- package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
- package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
- package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
- package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
- package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
- package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
- package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
- package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
- package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
- package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
- package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
- package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
- package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
- package/app/v2Components/ErrorInfoNote/index.js +24 -26
- package/app/v2Components/FormBuilder/index.js +182 -204
- package/app/v2Components/FormBuilder/messages.js +4 -8
- package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
- package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
- package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
- package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
- package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
- package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
- package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
- package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
- package/app/v2Components/SmsFallback/constants.js +73 -0
- package/app/v2Components/SmsFallback/index.js +956 -0
- package/app/v2Components/SmsFallback/index.scss +265 -0
- package/app/v2Components/SmsFallback/messages.js +78 -0
- package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
- package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
- package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
- package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
- package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
- package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
- package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
- package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
- package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
- package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
- package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
- package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
- package/app/v2Containers/BeeEditor/index.js +3 -0
- package/app/v2Containers/BeePopupEditor/index.js +9 -2
- package/app/v2Containers/Cap/mockData.js +0 -14
- package/app/v2Containers/Cap/reducer.js +3 -55
- package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
- package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
- package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
- package/app/v2Containers/CommunicationFlow/constants.js +200 -0
- package/app/v2Containers/CommunicationFlow/index.js +102 -0
- package/app/v2Containers/CommunicationFlow/messages.js +346 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
- package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
- package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
- package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
- package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
- package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
- package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
- package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
- package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
- package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
- package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
- package/app/v2Containers/CreativesContainer/constants.js +24 -0
- package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
- package/app/v2Containers/CreativesContainer/index.js +346 -71
- package/app/v2Containers/CreativesContainer/index.scss +51 -1
- package/app/v2Containers/CreativesContainer/messages.js +12 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
- package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
- package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
- package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
- package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
- package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
- package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
- package/app/v2Containers/Email/index.js +2 -5
- package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
- package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
- package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
- package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
- package/app/v2Containers/EmailWrapper/index.js +4 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
- package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
- package/app/v2Containers/FTP/index.js +2 -51
- package/app/v2Containers/FTP/messages.js +0 -4
- package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
- package/app/v2Containers/InApp/index.js +297 -118
- package/app/v2Containers/InApp/tests/index.test.js +17 -6
- package/app/v2Containers/InApp/tests/mockData.js +1 -1
- package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
- package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
- package/app/v2Containers/InAppWrapper/index.js +3 -0
- package/app/v2Containers/InappAdvance/index.js +5 -104
- package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
- package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
- package/app/v2Containers/Line/Container/Text/index.js +0 -1
- package/app/v2Containers/MobilePush/Create/index.js +105 -28
- package/app/v2Containers/MobilePush/Create/messages.js +4 -0
- package/app/v2Containers/MobilePush/Edit/index.js +250 -68
- package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
- package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
- package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
- package/app/v2Containers/MobilePushNew/index.js +78 -35
- package/app/v2Containers/MobilePushNew/messages.js +8 -0
- package/app/v2Containers/MobilepushWrapper/index.js +11 -1
- package/app/v2Containers/Rcs/constants.js +32 -1
- package/app/v2Containers/Rcs/index.js +963 -916
- package/app/v2Containers/Rcs/index.scss +85 -6
- package/app/v2Containers/Rcs/messages.js +10 -1
- package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
- package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
- package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
- package/app/v2Containers/Rcs/tests/index.test.js +41 -38
- package/app/v2Containers/Rcs/tests/mockData.js +38 -0
- package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
- package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
- package/app/v2Containers/Rcs/utils.js +358 -10
- package/app/v2Containers/Sms/Create/index.js +122 -39
- package/app/v2Containers/Sms/Create/messages.js +4 -0
- package/app/v2Containers/Sms/Edit/index.js +37 -3
- package/app/v2Containers/Sms/commonMethods.js +3 -6
- package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
- package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
- package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
- package/app/v2Containers/SmsTrai/Create/index.js +9 -4
- package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
- package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
- package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
- package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
- package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
- package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
- package/app/v2Containers/SmsWrapper/index.js +41 -8
- package/app/v2Containers/TagList/index.js +63 -2
- package/app/v2Containers/TagList/messages.js +8 -0
- package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
- package/app/v2Containers/TagList/tests/mockdata.js +17 -0
- package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
- package/app/v2Containers/Templates/_templates.scss +61 -2
- package/app/v2Containers/Templates/actions.js +11 -0
- package/app/v2Containers/Templates/constants.js +2 -0
- package/app/v2Containers/Templates/index.js +90 -40
- package/app/v2Containers/Templates/reducer.js +3 -1
- package/app/v2Containers/Templates/sagas.js +57 -12
- package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
- package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
- package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
- package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
- package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
- package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
- package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
- package/app/v2Containers/TemplatesV2/index.js +147 -49
- package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
- package/app/v2Containers/Viber/index.js +9 -10
- package/app/v2Containers/Viber/index.scss +1 -1
- package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
- package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
- package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
- package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
- package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
- package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
- package/app/v2Containers/WebPush/Create/index.js +36 -6
- package/app/v2Containers/WebPush/Create/index.scss +5 -0
- package/app/v2Containers/WebPush/Create/messages.js +8 -1
- package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
- package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
- package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
- package/app/v2Containers/Whatsapp/index.js +28 -53
- package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
- package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
- package/app/v2Containers/Zalo/index.js +5 -11
- package/package.json +2 -2
- package/version +9 -0
|
@@ -50,9 +50,9 @@ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetai
|
|
|
50
50
|
import * as actions from "../../v2Containers/Cap/actions";
|
|
51
51
|
import './_formBuilder.scss';
|
|
52
52
|
import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
|
|
53
|
-
import {
|
|
53
|
+
import { preprocessHtml, validateTagsCore, hasUnsubscribeTag } from '../../utils/tagValidations';
|
|
54
54
|
import { containsBase64Images } from '../../utils/content';
|
|
55
|
-
import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,
|
|
55
|
+
import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
|
|
56
56
|
import globalMessages from '../../v2Containers/Cap/messages';
|
|
57
57
|
import { convert } from 'html-to-text';
|
|
58
58
|
import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE, GLOBAL_CONVERT_OPTIONS } from './constants';
|
|
@@ -60,10 +60,10 @@ import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
|
|
|
60
60
|
import moment from 'moment';
|
|
61
61
|
import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
|
|
62
62
|
import { REQUEST } from '../../v2Containers/Cap/constants'
|
|
63
|
-
import {
|
|
63
|
+
import { isEmailUnsubscribeTagOptional, isAiContentBotDisabled } from '../../utils/common';
|
|
64
64
|
import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
|
|
65
65
|
import { bindActionCreators } from 'redux';
|
|
66
|
-
import { getChannelData, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
|
|
66
|
+
import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
|
|
67
67
|
const TabPane = Tabs.TabPane;
|
|
68
68
|
const {Column} = Table;
|
|
69
69
|
const {TextArea} = CapInput;
|
|
@@ -72,10 +72,8 @@ const {CapRadioGroup} = CapRadio;
|
|
|
72
72
|
|
|
73
73
|
const tagsTypes = {
|
|
74
74
|
MISSING_TAGS: 'missingTags',
|
|
75
|
-
UNSUPPORTED_TAGS: 'unsupportedTags',
|
|
76
75
|
};
|
|
77
76
|
const errorMessageForTags = {
|
|
78
|
-
UNSUPPORTED_TAG_ERROR: 'unsupportedTagsError',
|
|
79
77
|
MISSING_TAG_ERROR: 'missingTagsError',
|
|
80
78
|
GENERIC_VALIDATION_ERROR: 'genericValidationError',
|
|
81
79
|
TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
|
|
@@ -138,7 +136,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
138
136
|
this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
|
|
139
137
|
this.formElements = [];
|
|
140
138
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
141
|
-
this.
|
|
139
|
+
this.isLiquidFlowSupportedByChannel = this.isLiquidFlowSupportedByChannel.bind(this);
|
|
142
140
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
143
141
|
|
|
144
142
|
// Performance optimization: Debounced functions for high-frequency updates
|
|
@@ -330,8 +328,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
330
328
|
return updatedFormData;
|
|
331
329
|
}
|
|
332
330
|
|
|
333
|
-
|
|
334
|
-
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase())
|
|
331
|
+
isLiquidFlowSupportedByChannel = () => {
|
|
332
|
+
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
|
|
335
333
|
}
|
|
336
334
|
|
|
337
335
|
componentWillMount() {
|
|
@@ -714,7 +712,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
714
712
|
if (channel && channel.toUpperCase() === SMS) {
|
|
715
713
|
for (let count = 0; count < this.state.tabCount; count += 1) {
|
|
716
714
|
if (_.isEmpty(errorData[count])) {
|
|
717
|
-
return
|
|
715
|
+
// Do not return early. An empty tab object can appear transiently and returning here
|
|
716
|
+
// prevents onFormValidityChange from firing, which makes Done appear unresponsive.
|
|
717
|
+
errorData[count] = {};
|
|
718
718
|
}
|
|
719
719
|
const index = count + 1;
|
|
720
720
|
if (!this.state.formData[count]) {
|
|
@@ -726,17 +726,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
726
726
|
|
|
727
727
|
let tagValidationResponse = false;
|
|
728
728
|
if (content) {
|
|
729
|
-
tagValidationResponse = this.validateTags(content, tags,
|
|
729
|
+
tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
730
730
|
}
|
|
731
|
-
|
|
732
|
-
|
|
731
|
+
|
|
732
|
+
const tagResult = tagValidationResponse && typeof tagValidationResponse === 'object'
|
|
733
|
+
? tagValidationResponse
|
|
734
|
+
: { valid: false, missingTags: [], isBraceError: false };
|
|
735
|
+
if (tagResult.valid) {
|
|
733
736
|
errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
|
|
734
737
|
} else {
|
|
735
|
-
|
|
736
|
-
const {
|
|
737
|
-
|
|
738
|
-
errorData[count][`
|
|
739
|
-
errorData[count][`bracket-error`] = tagValidationResponse.isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
738
|
+
const { MISSING_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
|
|
739
|
+
const { missingTags, isBraceError } = tagResult;
|
|
740
|
+
errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR);
|
|
741
|
+
errorData[count][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
740
742
|
isValid = false;
|
|
741
743
|
}
|
|
742
744
|
if(content !== '' && (ifUnicode && !unicodeCheck)) {
|
|
@@ -764,7 +766,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
764
766
|
if (this.state.formData['message-editor'] !== undefined ) {
|
|
765
767
|
const content = this.state.formData['0']['message-editor'] || '';
|
|
766
768
|
|
|
767
|
-
const tagValidationResponse = this.validateTags((content), tags,
|
|
769
|
+
const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
|
|
768
770
|
|
|
769
771
|
if (tagValidationResponse.valid) {
|
|
770
772
|
errorData = {
|
|
@@ -847,7 +849,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
847
849
|
errorData[index] = true;
|
|
848
850
|
isValid = false;
|
|
849
851
|
} else {
|
|
850
|
-
const tagValidationResponse = this.validateTags(content, tags,
|
|
852
|
+
const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
|
|
851
853
|
|
|
852
854
|
if (tagValidationResponse.valid) {
|
|
853
855
|
errorData[index] = false;
|
|
@@ -903,21 +905,23 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
903
905
|
if (errorData[parseInt(index)]) {
|
|
904
906
|
if (message) {
|
|
905
907
|
message = message.trim();
|
|
906
|
-
|
|
907
908
|
if (message === "") {
|
|
908
909
|
errorData[parseInt(index)][`message-editor${selector}`] = true;
|
|
909
910
|
isValid = false;
|
|
910
911
|
isCurrentTabValid = false;
|
|
912
|
+
} else if (this.props.restrictPersonalization && hasPersonalizationTags(message)) {
|
|
913
|
+
errorData[parseInt(index)][`message-editor${selector}`] = true;
|
|
914
|
+
isValid = false;
|
|
915
|
+
isCurrentTabValid = false;
|
|
911
916
|
} else {
|
|
912
917
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
913
|
-
const tagValidationResponse = this.validateTags(message, tags,
|
|
918
|
+
const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
|
|
914
919
|
|
|
915
920
|
if (tagValidationResponse.valid) {
|
|
916
921
|
errorData[parseInt(index)][`message-editor${selector}`] = false;
|
|
917
922
|
} else {
|
|
918
|
-
const {isBraceError} = tagValidationResponse;
|
|
923
|
+
const { isBraceError } = tagValidationResponse;
|
|
919
924
|
errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
|
|
920
|
-
errorData[parseInt(index)]['invalid-tags'] = tagValidationResponse.unsupportedTags;
|
|
921
925
|
errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
|
|
922
926
|
isValid = false;
|
|
923
927
|
}
|
|
@@ -937,9 +941,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
937
941
|
errorData[parseInt(index)][`message-title${selector}`] = true;
|
|
938
942
|
isValid = false;
|
|
939
943
|
isCurrentTabValid = false;
|
|
944
|
+
} else if (this.props.restrictPersonalization && hasPersonalizationTags(title)) {
|
|
945
|
+
errorData[parseInt(index)][`message-title${selector}`] = true;
|
|
946
|
+
isValid = false;
|
|
947
|
+
isCurrentTabValid = false;
|
|
940
948
|
} else {
|
|
941
949
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
942
|
-
const tagValidationResponse = this.validateTags(title, tags,
|
|
950
|
+
const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
|
|
943
951
|
|
|
944
952
|
if (tagValidationResponse.valid) {
|
|
945
953
|
errorData[parseInt(index)][`message-title${selector}`] = false;
|
|
@@ -1193,8 +1201,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1193
1201
|
if (!content) {
|
|
1194
1202
|
return false;
|
|
1195
1203
|
}
|
|
1196
|
-
const tagValidationResponse = this.validateTags(content, tags,
|
|
1197
|
-
|
|
1204
|
+
const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
|
|
1198
1205
|
// Check for base64 images in email content
|
|
1199
1206
|
isEmail && containsBase64Images({content, callback:()=>{
|
|
1200
1207
|
tagValidationResponse.valid = false;
|
|
@@ -1210,23 +1217,20 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1210
1217
|
errorData[index][currentLang]['template-content'] = true;
|
|
1211
1218
|
isValid = false;
|
|
1212
1219
|
isLiquidValid = false;
|
|
1213
|
-
if ((showMessages && !isNaN(index)) || this.
|
|
1214
|
-
if (tagValidationResponse?.missingTags?.length > 0
|
|
1220
|
+
if ((showMessages && !isNaN(index)) || this.isLiquidFlowSupportedByChannel()) {
|
|
1221
|
+
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1215
1222
|
errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
|
|
1216
1223
|
}
|
|
1217
1224
|
if (tagValidationResponse?.missingTags?.length > 0) {
|
|
1218
1225
|
errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
|
|
1219
1226
|
}
|
|
1220
|
-
if (tagValidationResponse?.unsupportedTags?.length > 0) {
|
|
1221
|
-
errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
|
|
1222
|
-
}
|
|
1223
1227
|
if (tagValidationResponse?.isBraceError){
|
|
1224
1228
|
errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
1225
1229
|
}
|
|
1226
1230
|
if (tagValidationResponse?.isContentEmpty) {
|
|
1227
1231
|
errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
|
|
1228
1232
|
// Adds a bypass for cases where content is initially empty in the creation flow.
|
|
1229
|
-
if(this.
|
|
1233
|
+
if(this.isLiquidFlowSupportedByChannel()){
|
|
1230
1234
|
errorString = "";
|
|
1231
1235
|
isLiquidValid = true;
|
|
1232
1236
|
}
|
|
@@ -1238,7 +1242,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1238
1242
|
}
|
|
1239
1243
|
}
|
|
1240
1244
|
if (errorString) {
|
|
1241
|
-
if (this.
|
|
1245
|
+
if (this.isLiquidFlowSupportedByChannel()) {
|
|
1242
1246
|
this.setState(
|
|
1243
1247
|
(prevState) => ({
|
|
1244
1248
|
liquidErrorMessage: {
|
|
@@ -1247,10 +1251,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1247
1251
|
},
|
|
1248
1252
|
}),
|
|
1249
1253
|
() => {
|
|
1250
|
-
// Callback after the state is updated
|
|
1251
1254
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
1252
1255
|
}
|
|
1253
1256
|
);
|
|
1257
|
+
// Footer shows the error; skip notification for Email CK/BEE (non-HTML) flow to avoid duplicate feedback
|
|
1258
|
+
const isEmailChannel = this.props?.schema?.channel?.toUpperCase() === EMAIL;
|
|
1259
|
+
if (!isEmailChannel) {
|
|
1260
|
+
this.openNotificationWithIcon('error', errorString, 'email-validation-error');
|
|
1261
|
+
}
|
|
1254
1262
|
} else {
|
|
1255
1263
|
this.openNotificationWithIcon('error', errorString, "email-validation-error");
|
|
1256
1264
|
}
|
|
@@ -1264,7 +1272,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1264
1272
|
});
|
|
1265
1273
|
}
|
|
1266
1274
|
|
|
1267
|
-
const isTemplateValid = this.
|
|
1275
|
+
const isTemplateValid = this.isLiquidFlowSupportedByChannel() ? isLiquidValid : isValid;
|
|
1268
1276
|
//Updating the state with the error data
|
|
1269
1277
|
this.setState((prevState) => ({
|
|
1270
1278
|
isFormValid: isTemplateValid,
|
|
@@ -1323,55 +1331,59 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1323
1331
|
}
|
|
1324
1332
|
onSubmitWrapper = (args) => {
|
|
1325
1333
|
const {singleTab = null} = args || {};
|
|
1326
|
-
|
|
1334
|
+
// Liquid validation (extractTags + Aira) only in library mode
|
|
1335
|
+
const runLiquidValidation = this.isLiquidFlowSupportedByChannel() && !this.props.isFullMode;
|
|
1336
|
+
if (runLiquidValidation) {
|
|
1327
1337
|
// For MPUSH, we need to validate both Android and iOS content separately
|
|
1328
1338
|
if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
|
|
1329
1339
|
this.validateFormBuilderMPush(this.state.formData, singleTab);
|
|
1330
1340
|
return;
|
|
1331
1341
|
}
|
|
1332
|
-
|
|
1333
|
-
// For other channels (EMAIL, SMS, INAPP)
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
(prevState) => ({
|
|
1340
|
-
liquidErrorMessage: {
|
|
1341
|
-
...prevState.liquidErrorMessage,
|
|
1342
|
-
STANDARD_ERROR_MSG: standardErrors,
|
|
1343
|
-
LIQUID_ERROR_MSG: liquidErrors,
|
|
1344
|
-
},
|
|
1345
|
-
}),
|
|
1346
|
-
() => {
|
|
1347
|
-
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
|
|
1348
|
-
this.props.stopValidation();
|
|
1349
|
-
}
|
|
1350
|
-
);
|
|
1351
|
-
};
|
|
1352
|
-
|
|
1353
|
-
const onSuccess = (contentToSubmit) => {
|
|
1354
|
-
const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
|
|
1355
|
-
if(channel === EMAIL) {
|
|
1356
|
-
const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
|
|
1357
|
-
this.handleLiquidTemplateSubmit(content);
|
|
1358
|
-
} else {
|
|
1359
|
-
this.handleLiquidTemplateSubmit(contentToSubmit);
|
|
1342
|
+
|
|
1343
|
+
// For other channels (EMAIL, SMS, INAPP): only call extractTags if there are no brace/empty errors already.
|
|
1344
|
+
// Run sync validation first; if it fails, block and show errors without calling the API.
|
|
1345
|
+
this.validateForm(null, null, true, false, () => {
|
|
1346
|
+
if (!this.state.isFormValid) {
|
|
1347
|
+
this.props.stopValidation();
|
|
1348
|
+
return;
|
|
1360
1349
|
}
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1350
|
+
const content = getChannelData(this.props.schema.channel || this.props.channel, this.state.formData, this.props.baseLanguage);
|
|
1351
|
+
|
|
1352
|
+
const onError = ({ standardErrors, liquidErrors }) => {
|
|
1353
|
+
this.setState(
|
|
1354
|
+
(prevState) => ({
|
|
1355
|
+
liquidErrorMessage: {
|
|
1356
|
+
...prevState.liquidErrorMessage,
|
|
1357
|
+
STANDARD_ERROR_MSG: standardErrors,
|
|
1358
|
+
LIQUID_ERROR_MSG: liquidErrors,
|
|
1359
|
+
},
|
|
1360
|
+
}),
|
|
1361
|
+
() => {
|
|
1362
|
+
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
|
|
1363
|
+
this.props.stopValidation();
|
|
1364
|
+
this.props.onFormValidityChange(false, this.state.errorData);
|
|
1365
|
+
}
|
|
1366
|
+
);
|
|
1367
|
+
};
|
|
1368
|
+
|
|
1369
|
+
const onSuccess = (contentToSubmit) => {
|
|
1370
|
+
const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
|
|
1371
|
+
if(channel === EMAIL) {
|
|
1372
|
+
const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
|
|
1373
|
+
this.handleLiquidTemplateSubmit(content);
|
|
1374
|
+
} else {
|
|
1375
|
+
this.handleLiquidTemplateSubmit(contentToSubmit);
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
|
|
1379
|
+
validateLiquidTemplateContent(content, {
|
|
1380
|
+
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1381
|
+
formatMessage: this.props.intl.formatMessage,
|
|
1382
|
+
messages,
|
|
1383
|
+
onError,
|
|
1384
|
+
onSuccess,
|
|
1385
|
+
skipTags: this.skipTags.bind(this)
|
|
1386
|
+
});
|
|
1375
1387
|
});
|
|
1376
1388
|
} else {
|
|
1377
1389
|
this.props.onSubmit(this.state.formData);
|
|
@@ -1398,7 +1410,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1398
1410
|
|
|
1399
1411
|
// Set up callbacks for error and success handling
|
|
1400
1412
|
const onLiquidError = ({ standardErrors, liquidErrors }) => {
|
|
1401
|
-
|
|
1402
1413
|
this.setState(
|
|
1403
1414
|
(prevState) => ({
|
|
1404
1415
|
liquidErrorMessage: {
|
|
@@ -1410,6 +1421,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1410
1421
|
() => {
|
|
1411
1422
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.state.currentTab);
|
|
1412
1423
|
this.props.stopValidation();
|
|
1424
|
+
// Block save: tell parent form is invalid so Done/submit is blocked
|
|
1425
|
+
this.props.onFormValidityChange(false, this.state.errorData);
|
|
1413
1426
|
}
|
|
1414
1427
|
);
|
|
1415
1428
|
};
|
|
@@ -1439,13 +1452,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1439
1452
|
getLiquidTags: this.props.actions.getLiquidTags,
|
|
1440
1453
|
formatMessage: this.props.intl.formatMessage,
|
|
1441
1454
|
messages: messages,
|
|
1442
|
-
tagLookupMap: this.props?.metaEntities?.tagLookupMap,
|
|
1443
|
-
eventContextTags: this.props?.eventContextTags,
|
|
1444
|
-
isLiquidFlow: this.liquidFlow(), // Use the method instead of props
|
|
1445
|
-
forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
|
|
1446
|
-
skipTags: this.skipTags.bind(this),
|
|
1447
|
-
extractNames,
|
|
1448
|
-
checkSupport,
|
|
1449
1455
|
singleTab: singleTab?.toUpperCase(),
|
|
1450
1456
|
});
|
|
1451
1457
|
}
|
|
@@ -1500,102 +1506,40 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
1500
1506
|
});
|
|
1501
1507
|
}
|
|
1502
1508
|
|
|
1503
|
-
validateTags(content, tagsParam,
|
|
1504
|
-
const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
|
|
1509
|
+
validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
|
|
1505
1510
|
let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
|
|
1506
1511
|
if (this.props.tagModule) {
|
|
1507
1512
|
currentModule = this.props.tagModule;
|
|
1508
1513
|
}
|
|
1509
1514
|
const tags = tagsParam ? tagsParam : this.props.tags;
|
|
1510
|
-
const
|
|
1511
|
-
const
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
response
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
response.missingTags.push(tag.definition.value);
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
});
|
|
1531
|
-
});
|
|
1532
|
-
const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
|
|
1533
|
-
let match = regex.exec(content);
|
|
1534
|
-
const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
|
|
1535
|
-
let matchImg = regexImgSrc.exec(content);
|
|
1536
|
-
const regexCustomerBarcode = /{{customer_barcode}}(?![^<]*>)/g;
|
|
1537
|
-
let matchCustomerBarcode = regexCustomerBarcode.exec(content);
|
|
1538
|
-
// \S matches anything other than a space, a tab, a newline, or a carriage return.
|
|
1539
|
-
const validString= /\S/.test(contentForValidation);
|
|
1540
|
-
if (isEmailUnsubscribeTagMandatory() && isEmail && this.props?.moduleType === OUTBOUND) {
|
|
1541
|
-
const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
|
|
1542
|
-
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1543
|
-
response?.missingTags?.splice(missingTagIndex, 1);
|
|
1544
|
-
}
|
|
1545
|
-
if (validString) {
|
|
1546
|
-
response.valid = true;
|
|
1547
|
-
} else {
|
|
1548
|
-
response.isContentEmpty = true;
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
while (match !== null ) {
|
|
1552
|
-
const tagValue = match[0].substring(this.indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
|
|
1553
|
-
const tagIndex = match?.index;
|
|
1554
|
-
match = regex.exec(content);
|
|
1555
|
-
let ifSupported = false;
|
|
1556
|
-
_.forEach(tags, (tag) => {
|
|
1557
|
-
if (tag.definition.value === tagValue) {
|
|
1558
|
-
ifSupported = true;
|
|
1559
|
-
}
|
|
1560
|
-
if(tagValue === CUSTOMER_BARCODE_TAG && (matchImg === null || matchCustomerBarcode !== null)){
|
|
1561
|
-
ifSupported = false;
|
|
1562
|
-
}
|
|
1563
|
-
});
|
|
1564
|
-
const ifSkipped = this.skipTags(tagValue);
|
|
1565
|
-
if (ifSkipped) {
|
|
1566
|
-
ifSupported = true;
|
|
1567
|
-
let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
|
|
1568
|
-
if (isUnsubscribeSkipped) {
|
|
1569
|
-
const missingTagIndex = response.missingTags.indexOf("unsubscribe");
|
|
1570
|
-
if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
|
|
1571
|
-
response.missingTags.splice(missingTagIndex, 1);
|
|
1572
|
-
}
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
|
|
1576
|
-
// Event Context Tags support
|
|
1577
|
-
this.props?.eventContextTags?.forEach((tag) => {
|
|
1578
|
-
if (tagValue === tag?.tagName) {
|
|
1579
|
-
ifSupported = true;
|
|
1580
|
-
}
|
|
1581
|
-
});
|
|
1582
|
-
|
|
1583
|
-
ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
|
|
1584
|
-
// Only add to unsupportedTags if not inside a {% ... %} block and not in liquid flow
|
|
1585
|
-
// Tags inside {% %} blocks can contain any dynamic tags and should not be validated
|
|
1586
|
-
if (!ifSupported && !this.liquidFlow() && !isInsideLiquidBlock(content, tagIndex)) {
|
|
1587
|
-
response.unsupportedTags.push(tagValue);
|
|
1588
|
-
response.valid = false;
|
|
1589
|
-
}
|
|
1515
|
+
const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content;
|
|
1516
|
+
const isOutboundModule = (currentModule || '').toUpperCase() === OUTBOUND;
|
|
1517
|
+
|
|
1518
|
+
const initialMissingTags = (tags && tags.length && !isFullMode && isEmail && isOutboundModule && !isEmailUnsubscribeTagOptional() && !hasUnsubscribeTag(content))
|
|
1519
|
+
? ['unsubscribe']
|
|
1520
|
+
: [];
|
|
1521
|
+
|
|
1522
|
+
const response = validateTagsCore({
|
|
1523
|
+
contentForBraceCheck: contentForValidation,
|
|
1524
|
+
contentForUnsubscribeScan: content,
|
|
1525
|
+
tags,
|
|
1526
|
+
currentModule,
|
|
1527
|
+
isFullMode,
|
|
1528
|
+
initialMissingTags, // [] or ['unsubscribe']; core uses this instead of definition-based when provided
|
|
1529
|
+
skipTagsFn: this.skipTags.bind(this),
|
|
1530
|
+
includeIsContentEmpty: true,
|
|
1531
|
+
});
|
|
1590
1532
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1533
|
+
// When unsubscribe tag is optional (isEmailUnsubscribeTagOptional): do not enforce unsubscribe (defensive splice); set isContentEmpty only when content is empty. Do not override response.valid so brace/format errors are preserved.
|
|
1534
|
+
const validString = /\S/.test(contentForValidation);
|
|
1535
|
+
if (isEmailUnsubscribeTagOptional() && isEmail && isOutboundModule) {
|
|
1536
|
+
const missingTagIndex = response.missingTags.indexOf('unsubscribe');
|
|
1537
|
+
if (missingTagIndex !== -1) {
|
|
1538
|
+
response.missingTags.splice(missingTagIndex, 1);
|
|
1539
|
+
}
|
|
1540
|
+
if (!validString) {
|
|
1541
|
+
response.isContentEmpty = true;
|
|
1594
1542
|
}
|
|
1595
|
-
}
|
|
1596
|
-
if(!validateIfTagClosed(contentForValidation)){
|
|
1597
|
-
response.isBraceError = true;
|
|
1598
|
-
response.valid = false;
|
|
1599
1543
|
}
|
|
1600
1544
|
return response;
|
|
1601
1545
|
}
|
|
@@ -2829,36 +2773,30 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2829
2773
|
|
|
2830
2774
|
|
|
2831
2775
|
getMissingOrUnsupportedTagsName = (content = '', type) => {
|
|
2832
|
-
const { MISSING_TAGS
|
|
2776
|
+
const { MISSING_TAGS } = tagsTypes;
|
|
2833
2777
|
const tagValidationResponse = this.validateTags(content);
|
|
2834
|
-
if (type &&
|
|
2835
|
-
return tagValidationResponse[type].join(', ').toString();
|
|
2778
|
+
if (type && type === MISSING_TAGS) {
|
|
2779
|
+
return (tagValidationResponse[type] || []).join(', ').toString();
|
|
2836
2780
|
}
|
|
2837
2781
|
return null;
|
|
2838
2782
|
};
|
|
2839
2783
|
|
|
2840
2784
|
renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
|
|
2841
2785
|
const { checkValidation, errorData, currentTab, formData } = this.state;
|
|
2842
|
-
const { MISSING_TAGS
|
|
2786
|
+
const { MISSING_TAGS } = tagsTypes;
|
|
2843
2787
|
const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
|
|
2844
2788
|
const ifError = checkValidation && errorType;
|
|
2845
2789
|
const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
|
|
2846
|
-
const { MISSING_TAG_ERROR,
|
|
2790
|
+
const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
2847
2791
|
const { formatMessage } = this.props.intl;
|
|
2848
2792
|
|
|
2849
|
-
const
|
|
2850
|
-
const isAiContentBotDisabled = accessibleFeatures?.includes(
|
|
2851
|
-
AI_CONTENT_BOT_DISABLED
|
|
2852
|
-
);
|
|
2793
|
+
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
2853
2794
|
|
|
2854
2795
|
let errorMessageText = false;
|
|
2855
2796
|
switch (errorType) {
|
|
2856
2797
|
case MISSING_TAG_ERROR:
|
|
2857
2798
|
errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
|
|
2858
2799
|
break;
|
|
2859
|
-
case UNSUPPORTED_TAG_ERROR:
|
|
2860
|
-
errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags: this.getMissingOrUnsupportedTagsName(messageContent, UNSUPPORTED_TAGS)});
|
|
2861
|
-
break;
|
|
2862
2800
|
case TAG_BRACKET_COUNT_MISMATCH_ERROR:
|
|
2863
2801
|
errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
|
|
2864
2802
|
break;
|
|
@@ -2868,8 +2806,17 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2868
2806
|
default:
|
|
2869
2807
|
break;
|
|
2870
2808
|
}
|
|
2809
|
+
|
|
2810
|
+
if (this.props.restrictPersonalization && hasPersonalizationTags(messageContent)) {
|
|
2811
|
+
errorMessageText = formatMessage(messages.personalizationTagsErrorMessage);
|
|
2812
|
+
}
|
|
2813
|
+
// Empty/required error: only show after user has triggered validation (ifError / "Done").
|
|
2814
|
+
// All other errors (brace, personalization, missing tags, generic): show in real time while typing.
|
|
2815
|
+
const isContentEmpty = !messageContent || !/\S/.test(String(messageContent).trim());
|
|
2816
|
+
const isEmptyError = errorType && isContentEmpty;
|
|
2817
|
+
const showError = errorType && (isEmptyError ? ifError : true);
|
|
2871
2818
|
const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
|
|
2872
|
-
if (prevErrorMessage !== errorMessageText && errorMessageText && this.
|
|
2819
|
+
if (prevErrorMessage !== errorMessageText && errorMessageText && this.isLiquidFlowSupportedByChannel()) {
|
|
2873
2820
|
this.setState((prevState) => ({
|
|
2874
2821
|
liquidErrorMessage: {
|
|
2875
2822
|
...prevState.liquidErrorMessage,
|
|
@@ -2880,15 +2827,25 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2880
2827
|
this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
|
|
2881
2828
|
});
|
|
2882
2829
|
}
|
|
2883
|
-
|
|
2830
|
+
// Always render the textarea regardless of the liquid error state update above.
|
|
2831
|
+
// Previously the textarea was inside the `else` branch, so it was not rendered during the
|
|
2832
|
+
// one render cycle when the liquid error message was first set – causing the input to lose
|
|
2833
|
+
// focus whenever a brace/tag error first appeared.
|
|
2884
2834
|
if (styling === 'semantic') {
|
|
2885
2835
|
columns.push(
|
|
2886
2836
|
<CapColumn key="input" span={val.width} offset={offset}>
|
|
2887
2837
|
<TextArea
|
|
2888
2838
|
id={val.id}
|
|
2889
2839
|
placeholder={val.placeholder ? val.placeholder : ''}
|
|
2890
|
-
className={`${
|
|
2891
|
-
errorMessage={
|
|
2840
|
+
className={`${showError ? 'error-form-builder' : ''}`}
|
|
2841
|
+
errorMessage={
|
|
2842
|
+
showError && errorMessageText && (
|
|
2843
|
+
!this.isLiquidFlowSupportedByChannel() ||
|
|
2844
|
+
[MOBILE_PUSH, INAPP].includes(this.props.schema?.channel?.toUpperCase())
|
|
2845
|
+
)
|
|
2846
|
+
? errorMessageText
|
|
2847
|
+
: ''
|
|
2848
|
+
}
|
|
2892
2849
|
label={val.label}
|
|
2893
2850
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2894
2851
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
@@ -2901,7 +2858,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2901
2858
|
cols={cols}
|
|
2902
2859
|
/>
|
|
2903
2860
|
{[SMS, MOBILE_PUSH].includes(this.props.schema?.channel)
|
|
2904
|
-
&& !
|
|
2861
|
+
&& !aiContentBotDisabled
|
|
2905
2862
|
&& (
|
|
2906
2863
|
<CapAskAira.ContentGenerationBot
|
|
2907
2864
|
text={messageContent || ""}
|
|
@@ -2919,7 +2876,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2919
2876
|
</CapColumn>
|
|
2920
2877
|
);
|
|
2921
2878
|
}
|
|
2922
|
-
}
|
|
2923
2879
|
};
|
|
2924
2880
|
|
|
2925
2881
|
|
|
@@ -3033,6 +2989,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3033
2989
|
userLocale={this.props.userLocale}
|
|
3034
2990
|
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
3035
2991
|
eventContextTags={this.props?.eventContextTags}
|
|
2992
|
+
restrictPersonalization={this.props.restrictPersonalization}
|
|
2993
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
3036
2994
|
/>
|
|
3037
2995
|
</CapColumn>
|
|
3038
2996
|
);
|
|
@@ -3062,11 +3020,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3062
3020
|
userLocale={this.props.userLocale}
|
|
3063
3021
|
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
3064
3022
|
eventContextTags={this.props?.eventContextTags}
|
|
3023
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
3065
3024
|
moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
|
|
3066
3025
|
containerStyle={val.style || {}}
|
|
3067
3026
|
inputProps={val.inputProps || {}}
|
|
3068
3027
|
showInput={val.showInput !== false}
|
|
3069
3028
|
showTagList={val.showTagList !== false}
|
|
3029
|
+
restrictPersonalization={this.props.restrictPersonalization}
|
|
3070
3030
|
/>
|
|
3071
3031
|
</CapColumn>
|
|
3072
3032
|
);
|
|
@@ -3436,15 +3396,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3436
3396
|
switch (type) {
|
|
3437
3397
|
case "input":
|
|
3438
3398
|
const errorType = isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id];
|
|
3439
|
-
const
|
|
3440
|
-
const isAiContentBotDisabled = accessibleFeatures?.includes(
|
|
3441
|
-
AI_CONTENT_BOT_DISABLED
|
|
3442
|
-
);
|
|
3399
|
+
const aiContentBotDisabled = isAiContentBotDisabled();
|
|
3443
3400
|
ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
|
|
3444
3401
|
const { TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
|
|
3445
3402
|
const { formatMessage } = this.props.intl;
|
|
3446
|
-
let errorMessageText = errorType === TAG_BRACKET_COUNT_MISMATCH_ERROR ? formatMessage(globalMessages.unbalanacedCurlyBraces) :(val.errorMessage && ifError ? val.errorMessage : '');
|
|
3447
3403
|
const value = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id];
|
|
3404
|
+
// Show personalization error for title field inline (same as message textarea) when restrictPersonalization is true
|
|
3405
|
+
const hasPersonalizationError = this.props.restrictPersonalization && hasPersonalizationTags(value);
|
|
3406
|
+
if (hasPersonalizationError) {
|
|
3407
|
+
ifError = true;
|
|
3408
|
+
}
|
|
3409
|
+
let errorMessageText = hasPersonalizationError
|
|
3410
|
+
? formatMessage(messages.personalizationTagsErrorMessage)
|
|
3411
|
+
: (errorType === TAG_BRACKET_COUNT_MISMATCH_ERROR ? formatMessage(globalMessages.unbalanacedCurlyBraces) : (val.errorMessage && ifError ? val.errorMessage : ''));
|
|
3448
3412
|
if (styling === 'semantic') {
|
|
3449
3413
|
columns.push(
|
|
3450
3414
|
<CapColumn key={val.id} span={val.width} offset={val.offset} style={val.style || {}}>
|
|
@@ -3465,7 +3429,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3465
3429
|
size={val.size || "default"}
|
|
3466
3430
|
/>
|
|
3467
3431
|
{this.props.schema?.channel === EMAIL &&
|
|
3468
|
-
!
|
|
3432
|
+
!aiContentBotDisabled && (
|
|
3469
3433
|
<CapAskAira.ContentGenerationBot
|
|
3470
3434
|
text={value || ""}
|
|
3471
3435
|
setText={this.handleSetText.bind(this, val)}
|
|
@@ -3695,6 +3659,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3695
3659
|
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
3696
3660
|
channel={channel}
|
|
3697
3661
|
eventContextTags={this.props?.eventContextTags}
|
|
3662
|
+
restrictPersonalization={this.props.restrictPersonalization}
|
|
3663
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
3664
|
+
getPopupContainer={this.props.tagListGetPopupContainer}
|
|
3665
|
+
popoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
|
|
3666
|
+
popoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
|
|
3698
3667
|
/>
|
|
3699
3668
|
</CapColumn>
|
|
3700
3669
|
);
|
|
@@ -3741,11 +3710,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3741
3710
|
userLocale={this.state.translationLang}
|
|
3742
3711
|
selectedOfferDetails={this.props.selectedOfferDetails}
|
|
3743
3712
|
eventContextTags={this.props?.eventContextTags}
|
|
3713
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
3744
3714
|
moduleFilterEnabled={moduleFilterEnabledForCapTagList}
|
|
3745
3715
|
containerStyle={val.style || {}}
|
|
3746
3716
|
inputProps={val.inputProps || {}}
|
|
3747
3717
|
showInput={val.showInput !== false}
|
|
3748
3718
|
showTagList={val.showTagList !== false}
|
|
3719
|
+
restrictPersonalization={this.props.restrictPersonalization}
|
|
3749
3720
|
/>
|
|
3750
3721
|
</CapColumn>
|
|
3751
3722
|
);
|
|
@@ -3971,7 +3942,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3971
3942
|
<CapColumn
|
|
3972
3943
|
style={val.colStyle ? val.colStyle : {border : ""}}
|
|
3973
3944
|
span={val.width}
|
|
3974
|
-
className={
|
|
3945
|
+
className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}
|
|
3975
3946
|
>
|
|
3976
3947
|
<CKEditor
|
|
3977
3948
|
id={val.id}
|
|
@@ -4015,7 +3986,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4015
3986
|
isModuleFilterEnabled = this.props.isFullMode;
|
|
4016
3987
|
}
|
|
4017
3988
|
columns.push(
|
|
4018
|
-
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={
|
|
3989
|
+
<CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}>
|
|
4019
3990
|
<BeeEditor
|
|
4020
3991
|
uid={uuid}
|
|
4021
3992
|
tokenData={beeToken}
|
|
@@ -4034,6 +4005,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4034
4005
|
onContextChange={this.props.onContextChange}
|
|
4035
4006
|
moduleFilterEnabled={isModuleFilterEnabled}
|
|
4036
4007
|
eventContextTags={this.props?.eventContextTags}
|
|
4008
|
+
waitEventContextTags={this.props?.waitEventContextTags}
|
|
4037
4009
|
/>
|
|
4038
4010
|
</CapColumn>
|
|
4039
4011
|
);
|
|
@@ -4301,7 +4273,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
4301
4273
|
|
|
4302
4274
|
|
|
4303
4275
|
return (
|
|
4304
|
-
<CapSpin spinning={Boolean((this.
|
|
4276
|
+
<CapSpin spinning={Boolean((this.isLiquidFlowSupportedByChannel() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
|
|
4305
4277
|
<CapRow>
|
|
4306
4278
|
{this.props.schema && this.renderForm()}
|
|
4307
4279
|
<SlideBox
|
|
@@ -4336,6 +4308,7 @@ FormBuilder.defaultProps = {
|
|
|
4336
4308
|
userLocale: localStorage.getItem('jlocale') || 'en',
|
|
4337
4309
|
showLiquidErrorInFooter: () => {},
|
|
4338
4310
|
metaDataStatus: "",
|
|
4311
|
+
waitEventContextTags: {},
|
|
4339
4312
|
isTestAndPreviewMode: false, // Default to false to maintain existing behavior
|
|
4340
4313
|
};
|
|
4341
4314
|
|
|
@@ -4384,11 +4357,16 @@ FormBuilder.propTypes = {
|
|
|
4384
4357
|
type: PropTypes.string.isRequired,
|
|
4385
4358
|
isEmailLoading: PropTypes.bool.isRequired,
|
|
4386
4359
|
moduleType: PropTypes.string.isRequired,
|
|
4387
|
-
showLiquidErrorInFooter: PropTypes.
|
|
4360
|
+
showLiquidErrorInFooter: PropTypes.func.isRequired,
|
|
4388
4361
|
eventContextTags: PropTypes.array.isRequired,
|
|
4362
|
+
waitEventContextTags: PropTypes.object,
|
|
4389
4363
|
forwardedTags: PropTypes.object.isRequired,
|
|
4390
4364
|
isLoyaltyModule: PropTypes.bool.isRequired,
|
|
4391
4365
|
isTestAndPreviewMode: PropTypes.bool, // Add new prop type
|
|
4366
|
+
restrictPersonalization: PropTypes.bool,
|
|
4367
|
+
tagListGetPopupContainer: PropTypes.func,
|
|
4368
|
+
tagListPopoverOverlayStyle: PropTypes.object,
|
|
4369
|
+
tagListPopoverOverlayClassName: PropTypes.string,
|
|
4392
4370
|
};
|
|
4393
4371
|
|
|
4394
4372
|
const mapStateToProps = createStructuredSelector({
|