@capillarytech/creatives-library 9.0.13 → 9.0.14-beta.0

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.
Files changed (46) hide show
  1. package/constants/unified.js +3 -0
  2. package/package.json +1 -1
  3. package/utils/common.js +8 -0
  4. package/v2Components/FormBuilder/Classic.js +4487 -0
  5. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +371 -0
  6. package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
  7. package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
  8. package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
  9. package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
  10. package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
  11. package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
  12. package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
  13. package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
  14. package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
  15. package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
  16. package/v2Components/FormBuilder/Functional/constants.js +42 -0
  17. package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
  18. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
  19. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
  20. package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
  21. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
  22. package/v2Components/FormBuilder/Functional/index.js +26 -0
  23. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
  24. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +31 -0
  25. package/v2Components/FormBuilder/Functional/layout/Section.js +116 -0
  26. package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +258 -0
  27. package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
  28. package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
  29. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
  30. package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
  31. package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
  32. package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
  33. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
  34. package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
  35. package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
  36. package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
  37. package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
  38. package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
  39. package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
  40. package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +160 -0
  41. package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
  42. package/v2Components/FormBuilder/_formBuilder.scss +5 -0
  43. package/v2Components/FormBuilder/index.js +41 -4479
  44. package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
  45. package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
  46. package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
@@ -0,0 +1,4487 @@
1
+ /* eslint-disable no-case-declarations */
2
+
3
+ /**
4
+ *
5
+ * FormBuilder
6
+ *
7
+ */
8
+
9
+ import PropTypes from 'prop-types';
10
+
11
+ import React from 'react';
12
+ import _ from 'lodash';
13
+ import { Table, Modal} from 'antd';
14
+ import { connect } from 'react-redux';
15
+ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
16
+ import CapDrawer from '@capillarytech/cap-ui-library/CapDrawer';
17
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
18
+ import CapInput from '@capillarytech/cap-ui-library/CapInput';
19
+ import CapPopover from '@capillarytech/cap-ui-library/CapPopover';
20
+ import CapImage from '@capillarytech/cap-ui-library/CapImage';
21
+ import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
22
+ import CapRadio from '@capillarytech/cap-ui-library/CapRadio';
23
+ import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
24
+ import CapTable from '@capillarytech/cap-ui-library/CapTable';
25
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
26
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
27
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
28
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
29
+ import CapUploader from '@capillarytech/cap-ui-library/CapUploader';
30
+ import CapHeading from '@capillarytech/cap-ui-library/CapHeading';
31
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
32
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
33
+ import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
34
+
35
+ import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
36
+ import LabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
37
+ import { createStructuredSelector } from 'reselect';
38
+ import { CAP_SPACE_12, CAP_SPACE_08, FONT_COLOR_05, FONT_COLOR_04 } from '@capillarytech/cap-ui-library/styled/variables';
39
+ import UnifiedPreview from '../CommonTestAndPreview/UnifiedPreview';
40
+ import TemplatePreview from '../TemplatePreview';
41
+ import { ANDROID } from '../CommonTestAndPreview/constants';
42
+ import TagList from '../../v2Containers/TagList';
43
+ import CapTagListWithInput from '../CapTagListWithInput';
44
+ import SlideBox from '../SlideBox';
45
+ import CardGrid from '../CardGrid';
46
+ import CKEditor from "../Ckeditor/";
47
+ import EDMEditor from "../Edmeditor";
48
+ import BeeEditor from '../../v2Containers/BeeEditor';
49
+ import CustomPopOver from '../CustomPopOver';
50
+ import messages from './messages';
51
+ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetails, selectMetaDataStatus } from "../../v2Containers/Cap/selectors";
52
+ import * as actions from "../../v2Containers/Cap/actions";
53
+ import './_formBuilder.scss';
54
+ import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
55
+ import { preprocessHtml, validateTagsCore, hasUnsubscribeTag } from '../../utils/tagValidations';
56
+ import { containsBase64Images } from '../../utils/content';
57
+ import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
58
+ import globalMessages from '../../v2Containers/Cap/messages';
59
+ import { convert } from 'html-to-text';
60
+ import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE, GLOBAL_CONVERT_OPTIONS } from './constants';
61
+ import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
62
+ import moment from 'moment';
63
+ import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
64
+ import { REQUEST } from '../../v2Containers/Cap/constants'
65
+ import { isEmailUnsubscribeTagOptional, isAiContentBotDisabled } from '../../utils/common';
66
+ import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
67
+ import { bindActionCreators } from 'redux';
68
+ import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
69
+ const {Column} = Table;
70
+ const {TextArea} = CapInput;
71
+ const {CapRadioGroup} = CapRadio;
72
+ // import styled from 'styled-components';
73
+
74
+ const tagsTypes = {
75
+ MISSING_TAGS: 'missingTags',
76
+ };
77
+ const errorMessageForTags = {
78
+ MISSING_TAG_ERROR: 'missingTagsError',
79
+ GENERIC_VALIDATION_ERROR: 'genericValidationError',
80
+ TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
81
+ };
82
+
83
+ // Isolated input for EMAIL template-name: only this tiny component re-renders on each keystroke.
84
+ // formData is updated only on blur (onCommit), eliminating all re-renders during typing.
85
+ class HighFreqInput extends React.Component {
86
+ constructor(props) {
87
+ super(props);
88
+ this.state = { localValue: props.value || '' };
89
+ }
90
+
91
+ componentDidUpdate(prevProps) {
92
+ if (prevProps.value !== this.props.value && this.state.localValue !== this.props.value) {
93
+ this.setState({ localValue: this.props.value || '' });
94
+ }
95
+ }
96
+
97
+ handleChange = (e) => {
98
+ this.setState({ localValue: e.target.value });
99
+ };
100
+
101
+ handleBlur = (e) => {
102
+ this.props.onCommit(this.state.localValue);
103
+ if (this.props.onBlur) this.props.onBlur(e);
104
+ };
105
+
106
+ render() {
107
+ const { value: _v, onCommit: _oc, onBlur: _ob, ...rest } = this.props;
108
+ return <CapInput {...rest} value={this.state.localValue} onChange={this.handleChange} onBlur={this.handleBlur} />;
109
+ }
110
+ }
111
+
112
+ // Isolated wrapper for EMAIL template-subject: blur-only commit, same as HighFreqInput.
113
+ class HighFreqTagInput extends React.Component {
114
+ constructor(props) {
115
+ super(props);
116
+ this.state = { localInputValue: props.inputValue || '' };
117
+ }
118
+
119
+ componentDidUpdate(prevProps) {
120
+ if (prevProps.inputValue !== this.props.inputValue && this.state.localInputValue !== this.props.inputValue) {
121
+ this.setState({ localInputValue: this.props.inputValue || '' });
122
+ }
123
+ }
124
+
125
+ handleInputChange = (e) => {
126
+ this.setState({ localInputValue: e.target.value });
127
+ };
128
+
129
+ handleBlur = () => {
130
+ this.props.onCommit(this.state.localInputValue);
131
+ };
132
+
133
+ render() {
134
+ const { inputValue: _iv, onCommit: _oc, inputOnChange: _ic, ...rest } = this.props;
135
+ return (
136
+ <CapTagListWithInput
137
+ {...rest}
138
+ inputValue={this.state.localInputValue}
139
+ inputOnChange={this.handleInputChange}
140
+ inputProps={{ ...(this.props.inputProps || {}), onBlur: this.handleBlur }}
141
+ />
142
+ );
143
+ }
144
+ }
145
+
146
+ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-stateless-function
147
+ constructor(props) {
148
+ super(props);
149
+ this.state = {
150
+ formData: {},
151
+ errorData: {},
152
+ currentTab: 1,
153
+ currentLangTab: 'en',
154
+ usingTabContainer: false,
155
+ tabCount: 1,
156
+ tabKey: '',
157
+ isFormValid: false,
158
+ checkValidation: false,
159
+ showModal: false,
160
+ modal: {},
161
+ currentEvent: {},
162
+ currentEventData: {},
163
+ popoverVisible: false,
164
+ customPopoverVisible: false,
165
+ isDrawerVisible: false,
166
+ translationLang: 'en',
167
+ liquidErrorMessage: {
168
+ STANDARD_ERROR_MSG: [],
169
+ LIQUID_ERROR_MSG: [],
170
+ },
171
+ };
172
+ this.renderForm = this.renderForm.bind(this);
173
+ this.updateFormData = this.updateFormData.bind(this);
174
+ this.callChildEvent = this.callChildEvent.bind(this);
175
+ this.renderColLabelSection = this.renderColLabelSection.bind(this);
176
+ this.renderMultiColSection = this.renderMultiColSection.bind(this);
177
+ this.renderSection = this.renderSection.bind(this);
178
+ this.renderContainer = this.renderContainer.bind(this);
179
+ this.renderParentSection = this.renderParentSection.bind(this);
180
+ this.initializeStandAloneSections = this.initializeStandAloneSections.bind(this);
181
+ this.initializeMultiColSection = this.initializeMultiColSection.bind(this);
182
+ this.initializeParentSection = this.initializeParentSection.bind(this);
183
+ this.initializeColLabelSection = this.initializeColLabelSection.bind(this);
184
+ this.initializeContainers = this.initializeContainers.bind(this);
185
+ this.cloneVersionData = this.cloneVersionData.bind(this);
186
+ this.markFinalTabVersion = this.markFinalTabVersion.bind(this);
187
+ this.duplicateVersion = this.duplicateVersion.bind(this);
188
+ this.deleteVersion = this.deleteVersion.bind(this);
189
+ this.renameVersion = this.renameVersion.bind(this);
190
+ this.resetState = this.resetState.bind(this);
191
+ this.onEdit = this.onEdit.bind(this);
192
+ this.changeVersionName = this.changeVersionName.bind(this);
193
+ this.hasClass = this.hasClass.bind(this);
194
+ this.validateForm = this.validateForm.bind(this);
195
+ this.indexOfEnd = this.indexOfEnd.bind(this);
196
+ this.saveForm = this.saveForm.bind(this);
197
+ this.initializeEditErrorData = this.initializeEditErrorData.bind(this);
198
+ this.checkIfSupportedTag = this.checkIfSupportedTag.bind(this);
199
+ this.transformInjectedTags = this.transformInjectedTags.bind(this);
200
+ this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
201
+ this.formElements = [];
202
+ // Check if the liquid flow feature is supported and the channel is in the supported list.
203
+ this.isLiquidFlowSupportedByChannel = this.isLiquidFlowSupportedByChannel.bind(this);
204
+ this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
205
+
206
+ // Performance optimization: Debounced functions for high-frequency updates
207
+ this.debouncedUpdateFormData = _.debounce((data, val, event, skipStateUpdate) => {
208
+ this.performFormDataUpdate(data, val, event, skipStateUpdate);
209
+ }, 300);
210
+ this.debouncedValidation = _.debounce(this.validateForm.bind(this), 500);
211
+
212
+ // Memoized validation cache
213
+ this.validationCache = new Map();
214
+
215
+ }
216
+
217
+ // Helper function to generate unique tab ID
218
+ generateUniqueTabId(formData, tabIndex) {
219
+ let id = _.uniqueId();
220
+ let validId = false;
221
+
222
+ while (!validId) {
223
+ validId = true;
224
+ for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
225
+ if (!formData[tabIndex]) {
226
+ continue;
227
+ }
228
+ if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
229
+ validId = false;
230
+ break;
231
+ }
232
+ }
233
+ if (!validId) {
234
+ id = _.uniqueId();
235
+ }
236
+ }
237
+
238
+ return id;
239
+ }
240
+
241
+ // Performance optimized form data update function
242
+ performFormDataUpdate(data, val, event, skipStateUpdate = false) {
243
+
244
+ // Use optimized state update instead of deep cloning
245
+ const formData = this.optimizedFormDataUpdate(data, val, event);
246
+
247
+ const tabIndex = this.state.currentTab - 1;
248
+ let currentTab = this.state.currentTab;
249
+
250
+ if (this.state.usingTabContainer && !val.standalone) {
251
+ const data1 = data;
252
+ if (event === ADD_LANGUAGE) {
253
+ const addLanguageType = this.props.addLanguageType;
254
+ if (addLanguageType === '') {
255
+ return;
256
+ }
257
+ const currentLang = formData[tabIndex].activeTab;
258
+ let baseTab;
259
+
260
+ switch (addLanguageType) {
261
+ case UPLOAD:
262
+ baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
263
+
264
+ baseTab.iso_code = data.iso_code;
265
+ baseTab.lang_id = data.lang_id;
266
+ baseTab.language = data.language;
267
+ baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
268
+
269
+ formData[tabIndex][data.iso_code] = baseTab;
270
+ formData[tabIndex].activeTab = data.iso_code;
271
+ formData[tabIndex].tabKey = baseTab.tabKey;
272
+ break;
273
+ case COPY_PRIMARY_LANGUAGE:
274
+ case USE_EDITOR:
275
+ baseTab = _.cloneDeep(formData[tabIndex][this.props.baseLanguage]);
276
+
277
+ baseTab.iso_code = data.iso_code;
278
+ baseTab.lang_id = data.lang_id;
279
+ baseTab.language = data.language;
280
+ baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
281
+
282
+ formData[tabIndex].selectedLanguages.push(data.iso_code);
283
+ formData[tabIndex][data.iso_code] = baseTab;
284
+ formData[tabIndex].activeTab = data.iso_code;
285
+ formData[tabIndex].tabKey = baseTab.tabKey;
286
+ break;
287
+ case '':
288
+ return;
289
+ default:
290
+ break;
291
+ }
292
+ const that = this;
293
+ setTimeout(() => {
294
+ that.setState({tabKey: baseTab.tabKey});
295
+ }, 0);
296
+ }
297
+
298
+ if (!this.props.isNewVersionFlow) {
299
+ formData[tabIndex][val.id] = data1;
300
+ } else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
301
+ formData[tabIndex][this.props.baseLanguage][val.id] = data1;
302
+ }
303
+
304
+ if (formData[tabIndex].base) {
305
+ if (!this.props.isNewVersionFlow) {
306
+ formData.base[val.id] = data1;
307
+ } else {
308
+ formData.base[data1.iso_code] = formData[tabIndex][data1.iso_code];
309
+ formData.base.tabKey = formData[tabIndex].tabKey;
310
+ formData.base.activeTab = formData[tabIndex].activeTab;
311
+ formData.base.selectedLanguages = formData[tabIndex].selectedLanguages;
312
+ }
313
+ }
314
+ } else {
315
+ formData[val.id] = data;
316
+ }
317
+
318
+ if (this.props.isNewVersionFlow) {
319
+ if (event === 'onSelect' && data === 'New Version') {
320
+ this.callChildEvent(data, val, 'addVersion', event);
321
+ } else if (event === 'onSelect' && data !== 'New Version') {
322
+ currentTab = _.findIndex(this.state.formData['template-version-options'], { key: data}) + 1;
323
+ this.setState({currentTab, tabKey: formData[`${currentTab - 1}`].tabKey}, () => {
324
+ val.injectedEvents[event].call(this, this.state.formData['template-version-options'][currentTab - 1].key, formData, val);
325
+ });
326
+ }
327
+
328
+ if (event === 'onContentChange') {
329
+ // Content change handling
330
+ }
331
+ }
332
+
333
+ // Only update state if not already updated (for immediate UI feedback)
334
+ if (!skipStateUpdate) {
335
+ this.setState({formData}, () => {
336
+ if (this.props.startValidation) {
337
+ this.debouncedValidation();
338
+ }
339
+ });
340
+ } else {
341
+ // Just trigger validation if state was already updated
342
+ if (this.props.startValidation) {
343
+ this.debouncedValidation();
344
+ }
345
+ }
346
+
347
+ if (event && val.injectedEvents[event]) {
348
+ if (event === "onRowClick") {
349
+ val.injectedEvents[event].call(this, data);
350
+ } else if (this.props.isNewVersionFlow && event !== 'onSelect') {
351
+ if (event === ADD_LANGUAGE) {
352
+ val.injectedEvents[event].call(this, data, formData, val);
353
+ this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
354
+ } else {
355
+ val.injectedEvents[event].call(this, true, formData, val);
356
+ }
357
+ } else if (!this.props.isNewVersionFlow) {
358
+ val.injectedEvents[event].call(this, true, formData, val);
359
+ }
360
+ } else if (val.injectedEvents && val.injectedEvents.onChange) {
361
+ val.injectedEvents.onChange.call(this, true, formData, val);
362
+ }
363
+
364
+ if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
365
+ this.props.onChange(formData, this.state.tabCount, currentTab, val);
366
+ }
367
+ }
368
+
369
+ // Optimized form data update - only clone what's necessary
370
+ optimizedFormDataUpdate(data, val, event) {
371
+ const currentFormData = this.state.formData;
372
+
373
+ // For simple field updates, use spread operator instead of deep clone
374
+ if (!this.state.usingTabContainer || val.standalone) {
375
+ return {
376
+ ...currentFormData,
377
+ [val.id]: data
378
+ };
379
+ }
380
+
381
+ // For tab container updates, only clone the affected tab
382
+ const tabIndex = this.state.currentTab - 1;
383
+ const updatedFormData = { ...currentFormData };
384
+
385
+ if (updatedFormData[tabIndex]) {
386
+ updatedFormData[tabIndex] = {
387
+ ...updatedFormData[tabIndex],
388
+ [val.id]: data
389
+ };
390
+ }
391
+
392
+ return updatedFormData;
393
+ }
394
+
395
+ isLiquidFlowSupportedByChannel = () => {
396
+ return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
397
+ }
398
+
399
+ componentWillMount() {
400
+ this.setState({usingTabContainer: this.props.usingTabContainer ? this.props.usingTabContainer : false});
401
+ if (this.props.formData && (this.props.isEdit || (!this.props.isEdit && !_.isEmpty(this.props.formData)))) {
402
+ this.setState({formData: this.props.formData}, () => {
403
+ // if (!this.props.isNewVersionFlow) {
404
+ this.initialiseForm(this.props.schema, false, true);
405
+ // }
406
+ });
407
+ } else {
408
+ this.initialiseForm(this.props.schema, true);
409
+ }
410
+ }
411
+
412
+ componentWillReceiveProps(nextProps) {
413
+ const type = this.props.location.query.type;
414
+ const isLibraryModule = this.props.location.query.module === "library";
415
+ if (this.state.usingTabContainer && nextProps.tabCount !== this.props.tabCount) {
416
+ this.setState({tabCount: nextProps.tabCount});
417
+ }
418
+ if (nextProps.startValidation && nextProps.startValidation !== false && this.props.startValidation !== nextProps.startValidation) {
419
+ if (this.debouncedUpdateFormData) this.debouncedUpdateFormData.flush();
420
+ this.setState({checkValidation: true});
421
+ this.validateForm(null, null, true, true, () => {
422
+ //triggering the saveFormData or onSubmit when validation sets isFormValid to TRUE
423
+ if (this.state.isFormValid) {
424
+ this.saveForm(true);
425
+ } else {
426
+ this.props.stopValidation();
427
+ }
428
+ });
429
+ }
430
+ // if (nextProps.saveForm && this.props.saveForm !== nextProps.saveForm) {
431
+ // this.saveForm(saveForm);
432
+ // }
433
+ if (nextProps.isEdit) {
434
+ if (!_.isEqual(nextProps.formData, this.state.formData) && !_.isEmpty(nextProps.formData)) {
435
+ if (type === 'embedded' && !isLibraryModule) { //TODO: has to be checked and changed.
436
+ this.setState({checkValidation: true, formData: nextProps.formData}, () => {
437
+ this.validateForm();
438
+ });
439
+ // this.setState({formData: nextProps.formData}, () => {
440
+ // this.validateForm();
441
+ // });
442
+ }
443
+ //TODO change this structure
444
+ if (this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'WECHAT') {
445
+ this.setState({formData: nextProps.formData});
446
+ } else if ( !_.isEmpty(nextProps.formData) && !_.isEqual(this.state.formData, nextProps.formData) && this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'MOBILEPUSH') {
447
+ this.setState({formData: nextProps.formData, tabCount: nextProps.tabCount});
448
+ // this.resetTabKeys(nextProps.formData, nextProps.tabCount);
449
+ } else if (this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL') {
450
+ this.setState({formData: nextProps.formData});
451
+ }
452
+
453
+ if (this.state.usingTabContainer && this.state.tabKey === '') {
454
+ //this.setState({formData: nextProps.formData});
455
+ if (nextProps.isNewVersionFlow && !_.isEqual(this.state.tabKey, nextProps.tabKey)) {
456
+ this.setState({tabKey: nextProps.tabKey});
457
+ } else if (!nextProps.isNewVersionFlow) {
458
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount);
459
+ }
460
+ this.initializeEditErrorData(nextProps.formData, nextProps.tabCount);
461
+ }
462
+ }
463
+ if (_.isEmpty(this.props.formData) && !_.isEmpty(nextProps.formData)) {
464
+ if (this.state.usingTabContainer) {
465
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount);
466
+ }
467
+ this.initializeEditErrorData(nextProps.formData, nextProps.tabCount);
468
+ }
469
+ if (!_.isEmpty(this.props.formData) && this.props.formData !== this.state.formData && _.isEmpty(this.state.formData) && this.state.usingTabContainer) {
470
+ this.resetTabKeys(this.props.formData, nextProps.tabCount);
471
+ }
472
+ if ( this.state.usingTabContainer && nextProps.formData && !_.isEmpty(nextProps.formData) && _.isEmpty(this.props.formData) && nextProps.tabCount && this.state.tabCount !== nextProps.tabCount) {
473
+ this.setState({ formData: nextProps.formData, tabCount: nextProps.tabCount }, () => {
474
+ this.initializeEditErrorData(nextProps.formData, nextProps.tabCount);
475
+ if (this.state.usingTabContainer) {
476
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount);
477
+ }
478
+ });
479
+ if (nextProps.tabCount > 1) {
480
+ this.setState({usingTabContainer: true});
481
+ }
482
+ }
483
+ //In Context of MPUSH currentTab represents whether its Android or IOS tab , 1 for Android and 2 for IOS
484
+ if (nextProps.currentTab && _.get(this.props, 'schema.channel') === MOBILE_PUSH) {
485
+ this.setState({currentTab: nextProps.currentTab});
486
+ }
487
+ } else if (!_.isEmpty(nextProps.formData) &&
488
+ ( !this.state.usingTabContainer || (this.state.usingTabContainer && nextProps.tabKey !== ''))
489
+ && !_.isEqual(nextProps.formData, this.state.formData) &&
490
+ !_.isEqual(nextProps.formData, this.props.formData)) {
491
+ // Don't run validation if we're in Test & Preview mode
492
+ if (!nextProps.isTestAndPreviewMode) {
493
+ this.setState({formData: nextProps.formData, tabKey: nextProps.tabKey}, () => {
494
+ this.validateForm();
495
+ });
496
+ } else {
497
+ // Just update formData without validation
498
+ this.setState({formData: nextProps.formData, tabKey: nextProps.tabKey});
499
+ }
500
+ //this.resetTabKeys(nextProps.formData, nextProps.tabCount);
501
+ } else if ((_.isEmpty(this.props.formData) || !this.props.formData) && _.isEmpty(this.state.formData)) {
502
+ this.initialiseForm(nextProps.schema, true);
503
+ } else if (_.isEmpty(this.props.formData) && !_.isEmpty(nextProps.formData)) {
504
+ this.setState({formData: nextProps.formData});
505
+ }
506
+
507
+ let currentLangTab = _.cloneDeep(this.state.currentLangTab);
508
+ if (nextProps.isNewVersionFlow && nextProps.formData[`${this.state.currentTab - 1}`] && nextProps.formData[`${this.state.currentTab - 1}`].activeTab) {
509
+ currentLangTab = nextProps.formData[`${this.state.currentTab - 1}`].activeTab;
510
+ }
511
+
512
+ if (nextProps.isNewVersionFlow && nextProps.currentTab && nextProps.currentTab !== this.props.currentTab) {
513
+ this.setState({currentTab: nextProps.currentTab});
514
+ }
515
+
516
+ if (!_.isEmpty(nextProps.formData) && !_.isEqual(this.state.formData, nextProps.formData)) {
517
+ if (nextProps.isNewVersionFlow) {
518
+ const tabKey = (this.state.tabKey !== nextProps.formData[nextProps.currentTab - 1].tabKey) ? nextProps.formData[nextProps.currentTab - 1].tabKey : this.state.tabKey;
519
+
520
+ this.setState({tabKey});
521
+ }
522
+ this.setState({formData: nextProps.formData, currentLangTab}, () => {
523
+ if (nextProps?.isNewVersionFlow && !this.state?.formData[this.state?.currentTab - 1][this.state?.formData[this.state?.currentTab - 1]?.activeTab]?.tabKey) {
524
+ this.resetTabKeys(nextProps.formData, nextProps.tabCount, false, true);
525
+ }
526
+ if (type === 'embedded' || ( this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL')) {
527
+ // Don't run validation if we're in Test & Preview mode
528
+ if (!nextProps.isTestAndPreviewMode) {
529
+ this.validateForm();
530
+ }
531
+ }
532
+ if ((this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'MOBILEPUSH')) {
533
+ // Don't run validation if we're in Test & Preview mode
534
+ if (!nextProps.isTestAndPreviewMode) {
535
+ this.validateForm();
536
+ }
537
+ }
538
+ });
539
+ }
540
+
541
+
542
+ if (!_.isEqual(nextProps.schema, this.props.schema)) {
543
+ this.setState({ isFormValid: (this.props.isEdit ? this.props.isEdit : true) }, () => {
544
+ if (!this.props.isEdit || (this.props.isEdit && !_.isEmpty(nextProps.formData))) {
545
+ let resetTabKeys = (this.state.formData.tabKey === undefined);
546
+ if ((this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'MOBILEPUSH')) {
547
+ resetTabKeys = false;
548
+ }
549
+ if (this.props.isNewVersionFlow) {
550
+ resetTabKeys = false;
551
+ }
552
+
553
+ this.initialiseForm(nextProps.schema, false, resetTabKeys);
554
+ // Don't run validation if we're in Test & Preview mode
555
+ if (!nextProps.isTestAndPreviewMode) {
556
+ this.validateForm();
557
+ }
558
+ }
559
+ });
560
+ }
561
+ if (!_.isEqual(nextProps.checkValidation, this.props.checkValidation)) {
562
+ this.setState({ checkValidation: nextProps.checkValidation });
563
+ }
564
+
565
+ if (this.props.currentTab !== nextProps.currentTab && nextProps.currentTab) {
566
+ this.setState({currentTab: nextProps.currentTab});
567
+ }
568
+
569
+ if (!_.isEmpty(nextProps.injectedTags) && !_.isEqual(nextProps.injectedTags, this.props.injectedTags)) {
570
+ // Don't run validation if we're in Test & Preview mode
571
+ if (!nextProps.isTestAndPreviewMode) {
572
+ this.validateForm(nextProps.tags, nextProps.injectedTags);
573
+ }
574
+ }
575
+ if (!_.isEmpty(nextProps.tags) && !_.isEqual(nextProps.tags, this.props.tags)) {
576
+ // Don't run validation if we're in Test & Preview mode
577
+ if (!nextProps.isTestAndPreviewMode) {
578
+ this.validateForm(nextProps.tags, nextProps.injectedTags);
579
+ }
580
+ }
581
+ if (!_.isEqual(nextProps.showModal, this.props.showModal)) {
582
+ this.setState({showModal: nextProps.showModal});
583
+ }
584
+ if (nextProps.modal && (!_.isEqual(nextProps.modal, this.props.modal) || _.isEmpty(this.state.modal))) {
585
+ this.setState({modal: nextProps.modal});
586
+ }
587
+ if (nextProps.isDrawerRequired !== this.props.isDrawerRequired) {
588
+ this.setState({
589
+ isDrawerVisible: nextProps.isDrawerRequired,
590
+ });
591
+ }
592
+ }
593
+
594
+ onEdit() {
595
+
596
+ }
597
+ getModal = () => {
598
+ let modal;
599
+ if (this.state.modal) {
600
+ if (this.state.modal.type === 'confirm') {
601
+ modal = (<Modal
602
+ open={this.state.showModal}
603
+ title={this.state.modal.title}
604
+ onOk={this.handleOk}
605
+ onCancel={this.handleCancel}
606
+ footer={[
607
+ <CapButton key="back" onClick={this.handleCancel}><FormattedMessage {...messages.cancel} /></CapButton>,
608
+ <CapButton key="submit" type="primary" id={this.state.modal.id} onClick={this.handleOk}>
609
+ <FormattedMessage {...messages.yes} />
610
+ </CapButton>,
611
+ ]}
612
+ >
613
+ {this.state.modal.body}
614
+ </Modal>);
615
+ } else if (this.state.modal.type === "info") {
616
+ modal = (<Modal
617
+ open={this.state.showModal}
618
+ title={this.state.modal.title}
619
+ onOk={this.handleOk}
620
+ onCancel={this.handleCancel}
621
+ footer={[
622
+ <CapButton key="back" onClick={this.handleCancel}><FormattedMessage {...messages.ok} /></CapButton>,
623
+ ]}
624
+ >
625
+ {this.state.modal.body}
626
+ </Modal>);
627
+ } else if (this.state.modal.type === "action") {
628
+ modal = (<Modal
629
+ open={this.state.showModal}
630
+ title={this.state.modal.title}
631
+ onOk={this.handleOk}
632
+ onCancel={this.handleCancel}
633
+ footer={[
634
+ ]}
635
+ afterClose={this.handleCloseModal}
636
+ >
637
+ {this.state.modal.jsx}
638
+ </Modal>);
639
+ }
640
+ }
641
+ return modal;
642
+ };
643
+
644
+ openNotificationWithIcon = (type, message, key) => {
645
+ CapNotification.error({
646
+ key: key || type + message,
647
+ message: (`${type.toUpperCase()} ! ! ! `),
648
+ description: message, //'This is the content of the notification. This is the content of the notification. This is the content of the notification.',
649
+ });
650
+ }
651
+
652
+ handleCloseModal = () => {
653
+ const event = this.state.currentEvent;
654
+ if (!_.isEmpty(this.state.currentEvent)) {
655
+ switch (event) {
656
+ case ADD_LANGUAGE:
657
+ this.updateFormData(this.state.currentEventData, this.state.currentEventVal, this.state.currentEvent);
658
+ break;
659
+ default:
660
+ break;
661
+ }
662
+ }
663
+ }
664
+
665
+ handleVisibleChange = (popoverVisible) => {
666
+ this.setState({ popoverVisible });
667
+ };
668
+
669
+ handleCustomPopoverVisibleChange = (customPopoverVisible) => {
670
+ this.setState({ customPopoverVisible });
671
+ };
672
+
673
+ resetTabKeys(form, count, changeDefaultTab, setFormTabKey) {
674
+ const tabCount = count || this.state.tabCount;
675
+
676
+
677
+ const formData = _.cloneDeep(form);
678
+ let baseIndex = 0;
679
+ for (let idx = 0; idx < tabCount; idx += 1) {
680
+ if (this.props.isNewVersionFlow && formData[idx].selectedLanguages.length > 0) {
681
+ for (let langIndex = 0; langIndex < formData[idx].selectedLanguages.length; langIndex += 1 ) {
682
+ const id = _.uniqueId();
683
+ if (formData[idx][formData[idx].selectedLanguages[langIndex]]) {
684
+ if (!formData[idx][formData[idx].selectedLanguages[langIndex]].tabKey) {
685
+ formData[idx][formData[idx].selectedLanguages[langIndex]].tabKey = id;
686
+ }
687
+ if (formData[idx].base) {
688
+ formData.base[formData.base.selectedLanguages[langIndex]].tabKey = id;
689
+ }
690
+ }
691
+ }
692
+ if (setFormTabKey) {
693
+ if (!formData[idx].activeTab) {
694
+ const baseLanguage = this.props.baseLanguage;
695
+
696
+ formData[idx].tabKey = formData[idx][baseLanguage].tabKey;
697
+ } else {
698
+ formData[idx].tabKey = formData[idx][formData[idx].activeTab].tabKey;
699
+ }
700
+ if (formData[idx].base) {
701
+ if (!formData.base.activeTab) {
702
+ const baseLanguage = this.props.baseLanguage;
703
+ formData.base.tabKey = formData.base[baseLanguage].tabKey;
704
+ } else {
705
+ formData.base.tabKey = formData.base[formData[idx].activeTab].tabKey;
706
+ }
707
+ }
708
+ }
709
+
710
+ if (formData[idx].base && formData.base.activeTab) {
711
+ // if (!formData.base.tabKey) {
712
+
713
+ formData.base.tabKey = formData[idx][formData[idx].activeTab].tabKey;
714
+ //}
715
+ baseIndex = idx;
716
+ }
717
+ } else if (formData[idx]) {
718
+ if (!formData[idx].tabKey) {
719
+ formData[idx].tabKey = _.uniqueId();
720
+ }
721
+ if (formData[idx].base) {
722
+ formData.base = formData[idx];
723
+ baseIndex = idx;
724
+ }
725
+ }
726
+ }
727
+
728
+ if ((formData.base && formData.base.tabKey && formData.base.tabKey !== '' && !changeDefaultTab) || (formData.base && this.state.tabKey !== formData.base.tabKey)) {
729
+ if (this.state.currentTab === 1 && !this.props.isNewVersionFlow) {
730
+ this.setState({tabKey: formData.base.tabKey});
731
+ } else if (this.props.isNewVersionFlow && this.state.tabKey !== formData[this.state.currentTab - 1].tabKey) {
732
+ this.setState({tabKey: formData[this.state.currentTab - 1].tabKey});
733
+ }
734
+ }
735
+ if (setFormTabKey) {
736
+ this.setState({tabKey: formData.base.tabKey});
737
+ }
738
+
739
+
740
+ if (changeDefaultTab) {
741
+ this.setState({formData}, () => {
742
+ this.props.onChange(formData, this.state.tabCount);
743
+ this.validateForm();
744
+ });
745
+ } else {
746
+ this.setState({formData, currentTab: baseIndex + 1}, () => {
747
+ this.props.onChange(formData, this.state.tabCount);
748
+ this.validateForm();
749
+ });
750
+ }
751
+ }
752
+ isValidExternalLink = ({obj, key}) => {
753
+ if (key in obj) {
754
+ const url = _.get(obj, key);
755
+ return (url && url !== "" && isUrl(url));
756
+ }
757
+ return true;
758
+ }
759
+ errorFieldsPresent(errorData, channel) {
760
+ const formData = _.cloneDeep(this.state.formData);
761
+ let isPresent = true;
762
+ if (channel === SMS) {
763
+ const formDataKeys = Object.keys(formData[0]);
764
+ const errorDataKeys = Object.keys(errorData[0]);
765
+ isPresent = _.isEqualWith(formDataKeys, errorDataKeys, (formDataKey, errorDataKey) => formDataKeys.includes(errorDataKey));
766
+ }
767
+ return isPresent;
768
+ }
769
+ /* eslint-disable */
770
+ validateForm(tags, injectedTags, isSave, showMessages, onValidationComplete) {
771
+ const channel = _.get(this, "props.schema.channel");
772
+ let isValid = true;
773
+ let isLiquidValid = true;
774
+ const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
775
+ const currentModule = (this.props.location && this.props.location.query.module) ? this.props.location.query.module : 'default';
776
+ let errorData = _.cloneDeep(this.state.errorData);
777
+ if (channel && channel.toUpperCase() === SMS) {
778
+ for (let count = 0; count < this.state.tabCount; count += 1) {
779
+ if (_.isEmpty(errorData[count])) {
780
+ // Do not return early. An empty tab object can appear transiently and returning here
781
+ // prevents onFormValidityChange from firing, which makes Done appear unresponsive.
782
+ errorData[count] = {};
783
+ }
784
+ const index = count + 1;
785
+ if (!this.state.formData[count]) {
786
+ continue;
787
+ }
788
+ const content = this.state.formData[count][`sms-editor${index > 1 ? index : ''}`];
789
+ const unicodeCheck = this.state.formData[count][`unicode-validity${index > 1 ? index : ''}`];
790
+ const ifUnicode = content && content !== '' ? checkUnicode(content) : false;
791
+
792
+ let tagValidationResponse = false;
793
+ if (content) {
794
+ tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
795
+ }
796
+
797
+ const tagResult = tagValidationResponse && typeof tagValidationResponse === 'object'
798
+ ? tagValidationResponse
799
+ : { valid: false, missingTags: [], isBraceError: false };
800
+ if (tagResult.valid) {
801
+ errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
802
+ } else {
803
+ const { MISSING_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
804
+ const { missingTags, isBraceError } = tagResult;
805
+ errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR);
806
+ errorData[count][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
807
+ isValid = false;
808
+ }
809
+ if(content !== '' && (ifUnicode && !unicodeCheck)) {
810
+ errorData[count][`unicode-validity${index > 1 ? index : ''}`] = true;
811
+ isValid = false;
812
+ } else if (content === '') {
813
+ errorData[count][`sms-editor${index > 1 ? index : ''}`] = errorMessageForTags.GENERIC_VALIDATION_ERROR;
814
+ isValid = false;
815
+ } else {
816
+ errorData[count][`unicode-validity${index > 1 ? index : ''}`] = false;
817
+ }
818
+ }
819
+ if(type !== 'embedded' && (!this.state.formData['template-name'] || this.state.formData['template-name'] === '')) {
820
+ errorData['template-name'] = true;
821
+ isValid = false;
822
+ } else {
823
+ errorData['template-name'] = false;
824
+ }
825
+ isLiquidValid = isValid;
826
+ }
827
+
828
+ //validation for LINE
829
+ if (channel === LINE) {
830
+
831
+ if (this.state.formData['message-editor'] !== undefined ) {
832
+ const content = this.state.formData['0']['message-editor'] || '';
833
+
834
+ const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
835
+
836
+ if (tagValidationResponse.valid) {
837
+ errorData = {
838
+ ...errorData,
839
+ 0: {
840
+ ...errorData[0],
841
+ 'message-editor': false,
842
+ }
843
+ };
844
+ } else {
845
+ errorData = {
846
+ ...errorData,
847
+ 0: {
848
+ ...errorData[0],
849
+ 'message-editor': true,
850
+ }
851
+ };
852
+ isValid = false;
853
+ }
854
+ if (content.trim() === '') {
855
+ errorData = {
856
+ ...errorData,
857
+ 0: {
858
+ ...errorData[0],
859
+ 'message-editor': true,
860
+ }
861
+ };
862
+ isValid = false;
863
+ }
864
+ }
865
+
866
+ if (this.state.formData['image-upload-line'] !== undefined) {
867
+ const {
868
+ formData: {
869
+ 0: {
870
+ image,
871
+ imagePreview,
872
+ }
873
+ }
874
+ } = this.state;
875
+ const imgContentLength = image ? image.length : 0;
876
+ const imgContentLengthPreview = imagePreview ? imagePreview.length : 0;
877
+ if ( (imgContentLength < 1) && (imgContentLengthPreview < 1) ) {
878
+
879
+ errorData = {
880
+ ...errorData,
881
+ 0: {
882
+ ...errorData[0],
883
+ image: true,
884
+ imagePreview: true,
885
+ }
886
+ };
887
+ isValid = false;
888
+ }else{
889
+ errorData = {
890
+ ...errorData,
891
+ 0: {
892
+ ...errorData[0],
893
+ image: false,
894
+ imagePreview: false,
895
+ }
896
+ };
897
+ }
898
+ }
899
+ if(type !== 'embedded' && (!this.state.formData['template-name'] || this.state.formData['template-name'] === '')) {
900
+ errorData['template-name'] = true;
901
+ isValid = false;
902
+ } else {
903
+ errorData['template-name'] = false;
904
+ }
905
+ }
906
+
907
+ if (this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'WECHAT') {
908
+
909
+ _.forEach(this.state.formData, (val, index) => {
910
+
911
+ if (index.indexOf('template') > -1 && this.state.formData[index] !== undefined) {
912
+ const content = this.state.formData[index];
913
+ if (content.trim() === '') {
914
+ errorData[index] = true;
915
+ isValid = false;
916
+ } else {
917
+ const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
918
+
919
+ if (tagValidationResponse.valid) {
920
+ errorData[index] = false;
921
+ } else {
922
+ errorData[index] = true;
923
+ isValid = false;
924
+ }
925
+ }
926
+ } else if (index === 'wechat-template') {
927
+ const content = this.state.formData[index];
928
+ if (content.trim() === '') {
929
+ errorData[index] = true;
930
+ isValid = false;
931
+ }
932
+ }
933
+ });
934
+
935
+ }
936
+ if(this.props.schema && this.props.schema.channel && this.props.schema.channel.toUpperCase() === "MOBILEPUSH"){
937
+ let androidValid = true, iosValid = true;
938
+ let isOnlyAndroid = false, isOnlyIos = false;
939
+ const { TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
940
+ const androidData = {};
941
+ if (this.state.formData && this.state.formData[0]) {
942
+ for(let data in this.state.formData[0]){
943
+ if(!!this.state.formData[0][data] && data !== "tabKey" && data !== "base"){
944
+ androidData[data] = this.state.formData[0][data];
945
+ }
946
+ }
947
+ if(_.isEmpty(androidData)){
948
+ isOnlyIos = true;
949
+ }
950
+ const iosData = {};
951
+ for(let data in this.state.formData[1]){
952
+ if(!!this.state.formData[1][data] && data !== "tabKey" && data !== "base"){
953
+ iosData[data] = this.state.formData[1][data];
954
+ }
955
+ }
956
+ if(_.isEmpty(iosData)){
957
+ isOnlyAndroid = true;
958
+ }
959
+ _.forEach(this.state.formData, (tab, index) => {
960
+ let isCurrentTabValid = true;
961
+ let skipValidation = false;
962
+ if (type.toLowerCase() === 'embedded' && ((isOnlyAndroid && parseInt(index) === 1) || (isOnlyIos && parseInt(index) === 0)) && isOnlyAndroid !== isOnlyIos) {
963
+ // skip validation of the tab which is not open in embedded mode
964
+ skipValidation = true;
965
+ }
966
+ if (!isNaN(index) && tab && !skipValidation) {
967
+ const selector = index > 0 ? `${Number(index) + 1}` : '';
968
+ let message = tab[`message-editor${selector}`];
969
+
970
+ if (errorData[parseInt(index)]) {
971
+ if (message) {
972
+ message = message.trim();
973
+ if (message === "") {
974
+ errorData[parseInt(index)][`message-editor${selector}`] = true;
975
+ isValid = false;
976
+ isCurrentTabValid = false;
977
+ } else if (this.props.restrictPersonalization && hasPersonalizationTags(message)) {
978
+ errorData[parseInt(index)][`message-editor${selector}`] = true;
979
+ isValid = false;
980
+ isCurrentTabValid = false;
981
+ } else {
982
+ errorData[parseInt(index)][`message-editor${selector}`] = false;
983
+ const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
984
+
985
+ if (tagValidationResponse.valid) {
986
+ errorData[parseInt(index)][`message-editor${selector}`] = false;
987
+ } else {
988
+ const { isBraceError } = tagValidationResponse;
989
+ errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
990
+ errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
991
+ isValid = false;
992
+ }
993
+ }
994
+
995
+ } else {
996
+ errorData[parseInt(index)][`message-editor${selector}`] = true;
997
+ isValid = false;
998
+ }
999
+
1000
+
1001
+
1002
+ let title = tab[`message-title${selector}`]
1003
+ if(title){
1004
+ title = title.trim();
1005
+ if (title === "") {
1006
+ errorData[parseInt(index)][`message-title${selector}`] = true;
1007
+ isValid = false;
1008
+ isCurrentTabValid = false;
1009
+ } else if (this.props.restrictPersonalization && hasPersonalizationTags(title)) {
1010
+ errorData[parseInt(index)][`message-title${selector}`] = true;
1011
+ isValid = false;
1012
+ isCurrentTabValid = false;
1013
+ } else {
1014
+ errorData[parseInt(index)][`message-title${selector}`] = false;
1015
+ const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
1016
+
1017
+ if (tagValidationResponse.valid) {
1018
+ errorData[parseInt(index)][`message-title${selector}`] = false;
1019
+ } else {
1020
+ const {isBraceError} = tagValidationResponse;
1021
+ errorData[parseInt(index)][`message-title${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
1022
+ errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
1023
+ isValid = false;
1024
+ }
1025
+ }
1026
+ } else {
1027
+ errorData[parseInt(index)][`message-title${selector}`] = true;
1028
+ isValid = false;
1029
+ isCurrentTabValid = false;
1030
+ }
1031
+ // if(tab['primary-cta-link'].trim() === ""){
1032
+ // errorData[parseInt(index)][`primary-cta-link`] = true;
1033
+ // isValid = false;
1034
+ // }else {
1035
+ // errorData[parseInt(index)]['primary-cta-link'] = false;
1036
+ // }
1037
+ if(tab['cta-deeplink-select'] === ""){
1038
+ errorData[parseInt(index)][`cta-deeplink-select`] = true;
1039
+ isValid = false;
1040
+ isCurrentTabValid = false;
1041
+ }else {
1042
+ errorData[parseInt(index)]['cta-deeplink-select'] = false;
1043
+ }
1044
+ if(tab['cta-deeplink2-select'] === ""){
1045
+ errorData[parseInt(index)][`cta-deeplink2-select`] = true;
1046
+ isValid = false;
1047
+ isCurrentTabValid = false;
1048
+ }else {
1049
+ errorData[parseInt(index)]['cta-deeplink2-select'] = false;
1050
+ }
1051
+ // }
1052
+ if(!this.isValidExternalLink({obj: tab, key: 'cta-deeplink-text'})){
1053
+ errorData[parseInt(index)][`cta-deeplink-text`] = true;
1054
+ isValid = false;
1055
+ isCurrentTabValid = false;
1056
+ }else {
1057
+ errorData[parseInt(index)]['cta-deeplink-text'] = false;
1058
+ }
1059
+
1060
+ if(!this.isValidExternalLink({obj: tab, key: 'cta-deeplink2-text'})){
1061
+ errorData[parseInt(index)][`cta-deeplink2-text`] = true;
1062
+ isValid = false;
1063
+ isCurrentTabValid = false;
1064
+ }else {
1065
+ errorData[parseInt(index)]['cta-deeplink2-text'] = false;
1066
+ }
1067
+
1068
+
1069
+ if(tab['secondary-cta-0-link'] === ""){
1070
+ errorData[parseInt(index)][`secondary-cta-0-link`] = true;
1071
+ isValid = false;
1072
+ isCurrentTabValid = false;
1073
+ }else {
1074
+ errorData[parseInt(index)]['secondary-cta-0-link'] = false;
1075
+ }
1076
+ if(tab['secondary-cta-0-label'] === ""){
1077
+ errorData[parseInt(index)][`secondary-cta-0-label`] = true;
1078
+ isValid = false;
1079
+ isCurrentTabValid = false;
1080
+ }else {
1081
+ errorData[parseInt(index)]['secondary-cta-0-label'] = false;
1082
+ }
1083
+
1084
+
1085
+ if(!this.isValidExternalLink({obj: tab, key: 'cta-deeplink-secondary-cta-0-text'})){
1086
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-0-text`] = true;
1087
+ isValid = false;
1088
+ isCurrentTabValid = false;
1089
+ }else {
1090
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-0-text'] = false;
1091
+ }
1092
+ if(tab['cta-deeplink-secondary-cta-0-select'] === ""){
1093
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-0-select`] = true;
1094
+ isValid = false;
1095
+ isCurrentTabValid = false;
1096
+ }else {
1097
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-0-select'] = false;
1098
+ }
1099
+ if(tab['secondary-cta-1-label'] === ""){
1100
+ errorData[parseInt(index)][`secondary-cta-1-label`] = true;
1101
+ isValid = false;
1102
+ isCurrentTabValid = false;
1103
+ }else {
1104
+ errorData[parseInt(index)]['secondary-cta-1-label'] = false;
1105
+ }
1106
+
1107
+ if(!this.isValidExternalLink({obj: tab, key: 'cta-deeplink-secondary-cta-1-text'})){
1108
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-1-text`] = true;
1109
+ isValid = false;
1110
+ isCurrentTabValid = false;
1111
+ }else {
1112
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-1-text'] = false;
1113
+ }
1114
+ if(tab['cta-deeplink-secondary-cta-1-select'] === ""){
1115
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-1-select`] = true;
1116
+ isValid = false;
1117
+ isCurrentTabValid = false;
1118
+ }else {
1119
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-1-select'] = false;
1120
+ }
1121
+
1122
+ if(!this.isValidExternalLink({obj: tab, key: 'cta-deeplink-secondary-cta-1-text2'})){
1123
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-1-text2`] = true;
1124
+ isValid = false;
1125
+ isCurrentTabValid = false;
1126
+ }else {
1127
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-1-text2'] = false;
1128
+ }
1129
+ if(tab['cta-deeplink-secondary-cta-1-select2'] === ""){
1130
+ errorData[parseInt(index)][`cta-deeplink-secondary-cta-1-select2`] = true;
1131
+ isValid = false;
1132
+ isCurrentTabValid = false;
1133
+ }else {
1134
+ errorData[parseInt(index)]['cta-deeplink-secondary-cta-1-select2'] = false;
1135
+ }
1136
+
1137
+ if(typeof tab['image'] !== 'undefined' && tab['image'] === ""){
1138
+ errorData[parseInt(index)]['image'] = true;
1139
+ isValid = false;
1140
+ isCurrentTabValid = false;
1141
+ }else{
1142
+ errorData[parseInt(index)]['image'] = false;
1143
+ }
1144
+ _.forEach(Object.keys(tab), (key) => {
1145
+ if (key.indexOf('custom-value') > -1) {
1146
+ if (tab[key] === "") {
1147
+ errorData[parseInt(index)][key] = true;
1148
+ isValid = false;
1149
+ isCurrentTabValid = false;
1150
+ }else {
1151
+ errorData[parseInt(index)][key] = false;
1152
+ }
1153
+ }
1154
+ });
1155
+ }
1156
+ }
1157
+ if(index == 0){
1158
+ androidValid = isCurrentTabValid;
1159
+ } else if(index == 1){
1160
+ iosValid = isCurrentTabValid;
1161
+ }
1162
+ });
1163
+ if(type !== 'embedded' && (!this.state.formData['template-name'] || this.state.formData['template-name'] === '')) {
1164
+ errorData['template-name'] = true;
1165
+ isValid = false;
1166
+ androidValid = false;
1167
+ iosValid = false;
1168
+ } else {
1169
+ errorData['template-name'] = false;
1170
+ }
1171
+ if(isSave && (androidValid || iosValid)){
1172
+ const modal = this.state.modal;
1173
+ const androidData = {};
1174
+
1175
+ for(let data in this.state.formData[0]){
1176
+ if(!!this.state.formData[0][data] && data !== "tabKey" && data !== "base"){
1177
+ androidData[data] = this.state.formData[0][data];
1178
+ }
1179
+ }
1180
+ if(_.isEmpty(androidData) && this.state.currentTab == 2){
1181
+ this.setState({androidValid, iosValid, errorData}, () => {
1182
+ this.props.setModalContent('ios');
1183
+ });
1184
+ // modal.body = "Android template is not configured. Save without Android template";
1185
+ // modal.id = "ios";
1186
+ // modal.type = 'confirm';
1187
+ return; //returning from here will not call the liquid middleware
1188
+ }
1189
+ const iosData = {};
1190
+ for(let data in this.state.formData[1]){
1191
+ if(!!this.state.formData[1][data] && data !== "tabKey"){
1192
+ iosData[data] = this.state.formData[1][data];
1193
+ }
1194
+ }
1195
+ if(_.isEmpty(iosData) && this.state.currentTab == 1){
1196
+ this.setState({androidValid, iosValid, errorData}, () => {
1197
+ this.props.setModalContent('android');
1198
+ });
1199
+ // modal.body = "IOS template is not configured, Save without IOS template";
1200
+ // modal.id = 'android';
1201
+ // modal.type = 'confirm';
1202
+ // this.setState({modal, showModal: true, androidValid, iosValid});
1203
+ return; //returning from here will not call the liquid middleware
1204
+ }
1205
+ }
1206
+ if (!androidValid || !iosValid) {
1207
+ this.setState({androidValid, iosValid});
1208
+ }
1209
+ if (isOnlyAndroid && androidValid) {
1210
+ isValid = isValid && true;
1211
+ } else if (isOnlyIos && iosValid) {
1212
+ isValid = isValid && true;
1213
+ } else if (!isOnlyIos && !isOnlyAndroid && androidValid && iosValid) {
1214
+ isValid = isValid && true;
1215
+ }
1216
+ isLiquidValid = isValid;
1217
+ }
1218
+ }
1219
+
1220
+ // Form Data Validation for Email Channel
1221
+ if (this.props?.schema?.channel?.toUpperCase() === EMAIL) {
1222
+ // Validating Email By iterating all elements of FormBuilder
1223
+ const isEmail = true;
1224
+ _.forEach(this.state.formData,(data, index) => {
1225
+
1226
+ if (!this.state.formData["template-subject"] && type.toLowerCase() === 'embedded' && (currentModule.toLowerCase() === 'loyalty' || currentModule.toLowerCase() === 'dvs' || currentModule.toLowerCase() === 'timeline' || currentModule.toLowerCase() === 'library')) {
1227
+ errorData["template-subject"] = true;
1228
+ isValid = false;
1229
+ }
1230
+ // Check if template subject present in form data from Loyalty, DVS, Timeline
1231
+ if (index === 'template-subject' && type.toLowerCase() === 'embedded' && (currentModule.toLowerCase() === 'loyalty' || currentModule.toLowerCase() === 'dvs' || currentModule.toLowerCase() === 'timeline' || currentModule.toLowerCase() === 'library')) {
1232
+ //checking email subject name validation
1233
+ if (!data?.trim()) {
1234
+ errorData[index] = true;
1235
+ isValid = false;
1236
+ } else {
1237
+ errorData[index] = false;
1238
+ }
1239
+ }
1240
+
1241
+ // Check if Template name present in full mode
1242
+ if(type !== 'embedded' && index === 'template-name' && data === '') {
1243
+ errorData[index] = true;
1244
+ isValid = false;
1245
+ } else if(type !== 'embedded' && index === 'template-name' && data !== '') {
1246
+ errorData[index] = false;
1247
+ }
1248
+
1249
+ // Check formData if selected version present or not
1250
+ if(index === 'template-version' && data === '') {
1251
+ errorData[index] = true;
1252
+ isValid = false;
1253
+ } else if(index === 'template-version' && data !== '') {
1254
+ errorData[index] = false;
1255
+ }
1256
+ isLiquidValid = isValid; // Existing validations support for liquid enabled orgs
1257
+ // Check error for the versions
1258
+ if (!isNaN(index) || index === 'base') {
1259
+ if (errorData[index] === undefined) {
1260
+ errorData[index] = {};
1261
+ }
1262
+ let errorString = "";
1263
+ for (var i = 0; i < data.selectedLanguages.length; i+= 1) {
1264
+ const currentLang = data.selectedLanguages[i];
1265
+ const content = (data[currentLang] || {})['template-content'];
1266
+ if (!content) {
1267
+ return false;
1268
+ }
1269
+ const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
1270
+ // Check for base64 images in email content
1271
+ isEmail && containsBase64Images({content, callback:()=>{
1272
+ tagValidationResponse.valid = false;
1273
+ tagValidationResponse.hasBase64Images = true;
1274
+ }})
1275
+
1276
+ if (errorData[index][currentLang] === undefined) {
1277
+ errorData[index][currentLang] = {};
1278
+ }
1279
+ if (tagValidationResponse?.valid) {
1280
+ errorData[index][currentLang]['template-content'] = false;
1281
+ } else {
1282
+ errorData[index][currentLang]['template-content'] = true;
1283
+ isValid = false;
1284
+ isLiquidValid = false;
1285
+ if ((showMessages && !isNaN(index)) || this.isLiquidFlowSupportedByChannel()) {
1286
+ if (tagValidationResponse?.missingTags?.length > 0) {
1287
+ errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
1288
+ }
1289
+ if (tagValidationResponse?.missingTags?.length > 0) {
1290
+ errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
1291
+ }
1292
+ if (tagValidationResponse?.isBraceError){
1293
+ errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
1294
+ }
1295
+ if (tagValidationResponse?.isContentEmpty) {
1296
+ errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
1297
+ // Adds a bypass for cases where content is initially empty in the creation flow.
1298
+ if(this.isLiquidFlowSupportedByChannel()){
1299
+ errorString = "";
1300
+ isLiquidValid = true;
1301
+ }
1302
+ }
1303
+ if (tagValidationResponse?.hasBase64Images) {
1304
+ errorString += this.props.intl.formatMessage(messages.base64ImageError);
1305
+ }
1306
+ }
1307
+ }
1308
+ }
1309
+ if (errorString) {
1310
+ if (this.isLiquidFlowSupportedByChannel()) {
1311
+ this.setState(
1312
+ (prevState) => ({
1313
+ liquidErrorMessage: {
1314
+ ...prevState.liquidErrorMessage,
1315
+ STANDARD_ERROR_MSG: [errorString],
1316
+ },
1317
+ }),
1318
+ () => {
1319
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
1320
+ }
1321
+ );
1322
+ // Footer shows the error; skip notification for Email CK/BEE (non-HTML) flow to avoid duplicate feedback
1323
+ const isEmailChannel = this.props?.schema?.channel?.toUpperCase() === EMAIL;
1324
+ if (!isEmailChannel) {
1325
+ this.openNotificationWithIcon('error', errorString, 'email-validation-error');
1326
+ }
1327
+ } else {
1328
+ this.openNotificationWithIcon('error', errorString, "email-validation-error");
1329
+ }
1330
+ }
1331
+ _.forEach(errorData[index], (versionData, key) => {
1332
+ if (data.selectedLanguages.indexOf(key) === -1) {
1333
+ delete errorData[index][key];
1334
+ }
1335
+ });
1336
+ }
1337
+ });
1338
+ }
1339
+
1340
+ const isTemplateValid = this.isLiquidFlowSupportedByChannel() ? isLiquidValid : isValid;
1341
+ //Updating the state with the error data
1342
+ this.setState((prevState) => ({
1343
+ isFormValid: isTemplateValid,
1344
+ liquidErrorMessage: {
1345
+ //Do not update the LIQUID_ERROR_MSG validation error message
1346
+ ...prevState.liquidErrorMessage,
1347
+ //update Standard error message based on the updated content
1348
+ STANDARD_ERROR_MSG: isTemplateValid ? [] : prevState.liquidErrorMessage?.STANDARD_ERROR_MSG,
1349
+ },
1350
+ errorData,
1351
+ }), () => {
1352
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
1353
+
1354
+ if (onValidationComplete) {
1355
+
1356
+ onValidationComplete();
1357
+ }
1358
+ });
1359
+
1360
+
1361
+ this.props.onFormValidityChange(isTemplateValid, errorData);
1362
+ return isTemplateValid;
1363
+ }
1364
+
1365
+ indexOfEnd(targetString, string) {
1366
+ var io = targetString.indexOf(string);
1367
+ return io == -1 ? -1 : io + string.length;
1368
+ }
1369
+
1370
+ handleLiquidTemplateSubmit = (templateContent) => {
1371
+ const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
1372
+
1373
+ if (templateContent && channel === EMAIL) {
1374
+ this.setState((prevState) => {
1375
+ return {
1376
+ formData: {
1377
+ ...prevState?.formData,
1378
+ base: {
1379
+ ...prevState?.formData?.base,
1380
+ [this.props.baseLanguage]: {
1381
+ ...prevState?.formData?.base?.[this.props.baseLanguage],
1382
+ "template-content": preprocessHtml(templateContent)
1383
+ }
1384
+ }
1385
+ }
1386
+ };
1387
+ }, () => {
1388
+ if (this.props.onSubmit) {
1389
+ this.props.onSubmit(this.state.formData);
1390
+ }
1391
+ });
1392
+ } else {
1393
+ // For all other channels
1394
+ this.props.onSubmit(this.state.formData);
1395
+ }
1396
+ }
1397
+ onSubmitWrapper = (args) => {
1398
+ const {singleTab = null} = args || {};
1399
+ // Liquid validation (extractTags + Aira) only in library mode
1400
+ const runLiquidValidation = this.isLiquidFlowSupportedByChannel() && !this.props.isFullMode;
1401
+ if (runLiquidValidation) {
1402
+ // For MPUSH, we need to validate both Android and iOS content separately
1403
+ if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
1404
+ this.validateFormBuilderMPush(this.state.formData, singleTab);
1405
+ return;
1406
+ }
1407
+
1408
+ // For other channels (EMAIL, SMS, INAPP): only call extractTags if there are no brace/empty errors already.
1409
+ // Run sync validation first; if it fails, block and show errors without calling the API.
1410
+ this.validateForm(null, null, true, false, () => {
1411
+ if (!this.state.isFormValid) {
1412
+ this.props.stopValidation();
1413
+ return;
1414
+ }
1415
+ const content = getChannelData(this.props.schema.channel || this.props.channel, this.state.formData, this.props.baseLanguage);
1416
+
1417
+ const onError = ({ standardErrors, liquidErrors }) => {
1418
+ this.setState(
1419
+ (prevState) => ({
1420
+ liquidErrorMessage: {
1421
+ ...prevState.liquidErrorMessage,
1422
+ STANDARD_ERROR_MSG: standardErrors,
1423
+ LIQUID_ERROR_MSG: liquidErrors,
1424
+ },
1425
+ }),
1426
+ () => {
1427
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1428
+ this.props.stopValidation();
1429
+ this.props.onFormValidityChange(false, this.state.errorData);
1430
+ }
1431
+ );
1432
+ };
1433
+
1434
+ const onSuccess = (contentToSubmit) => {
1435
+ const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
1436
+ if(channel === EMAIL) {
1437
+ const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
1438
+ this.handleLiquidTemplateSubmit(content);
1439
+ } else {
1440
+ this.handleLiquidTemplateSubmit(contentToSubmit);
1441
+ }
1442
+ };
1443
+
1444
+ validateLiquidTemplateContent(content, {
1445
+ getLiquidTags: this.props.actions.getLiquidTags,
1446
+ formatMessage: this.props.intl.formatMessage,
1447
+ messages,
1448
+ onError,
1449
+ onSuccess,
1450
+ skipTags: this.skipTags.bind(this)
1451
+ });
1452
+ });
1453
+ } else {
1454
+ this.props.onSubmit(this.state.formData);
1455
+ }
1456
+ }
1457
+
1458
+ saveForm(saveForm) {
1459
+ if (this.props.isNewVersionFlow && !saveForm) {
1460
+ this.props.getValidationData();
1461
+ return;
1462
+ }
1463
+ if ( this.state.isFormValid ) {
1464
+ this.onSubmitWrapper();
1465
+ } else {
1466
+ this.setState({checkValidation: true});
1467
+ this.validateForm(null, null, true);
1468
+ }
1469
+ }
1470
+
1471
+
1472
+ // New function to handle MPUSH content validation for both Android and iOS
1473
+ validateFormBuilderMPush = (formData,singleTab) => {
1474
+ const currentTab = this.state.currentTab;
1475
+
1476
+ // Set up callbacks for error and success handling
1477
+ const onLiquidError = ({ standardErrors, liquidErrors }) => {
1478
+ this.setState(
1479
+ (prevState) => ({
1480
+ liquidErrorMessage: {
1481
+ ...prevState.liquidErrorMessage,
1482
+ STANDARD_ERROR_MSG: standardErrors,
1483
+ LIQUID_ERROR_MSG: liquidErrors,
1484
+ },
1485
+ }),
1486
+ () => {
1487
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.state.currentTab);
1488
+ this.props.stopValidation();
1489
+ // Block save: tell parent form is invalid so Done/submit is blocked
1490
+ this.props.onFormValidityChange(false, this.state.errorData);
1491
+ }
1492
+ );
1493
+ };
1494
+
1495
+ const onSuccess = (contentToSubmit) => {
1496
+ // Clear any previous errors
1497
+ this.setState(
1498
+ (prevState) => ({
1499
+ liquidErrorMessage: {
1500
+ ...prevState.liquidErrorMessage,
1501
+ STANDARD_ERROR_MSG: [],
1502
+ LIQUID_ERROR_MSG: [],
1503
+ },
1504
+ }),
1505
+ () => {
1506
+ // Format depends on the tabType
1507
+ this.handleLiquidTemplateSubmit(contentToSubmit);
1508
+ }
1509
+ );
1510
+ };
1511
+
1512
+ // Call the utility function with our callbacks
1513
+ validateMobilePushContent(formData, {
1514
+ currentTab,
1515
+ onError: onLiquidError,
1516
+ onSuccess,
1517
+ getLiquidTags: this.props.actions.getLiquidTags,
1518
+ formatMessage: this.props.intl.formatMessage,
1519
+ messages: messages,
1520
+ singleTab: singleTab?.toUpperCase(),
1521
+ });
1522
+ }
1523
+
1524
+ transformInjectedTags(tags) {
1525
+ _.forEach(tags, (tag) => {
1526
+ const temp = tag;
1527
+ let subKey = '';
1528
+ Object.keys(tag).map( (k) => {
1529
+ if (k.indexOf("subtags") !== -1) {
1530
+ subKey = k;
1531
+ }
1532
+ return true;
1533
+ });
1534
+ if (subKey !== '') {
1535
+ temp['tag-header'] = true;
1536
+ if (subKey !== 'subtags') {
1537
+ temp.subtags = _.cloneDeep(temp[subKey]);
1538
+ delete temp[subKey];
1539
+ }
1540
+ temp.subtags = this.transformInjectedTags(temp.subtags);
1541
+ }
1542
+ });
1543
+ return tags;
1544
+ }
1545
+
1546
+ checkIfSupportedTag(checkingTag, injectedTags) {
1547
+ let result = false;
1548
+ _.forEach(injectedTags, (tag, key) => {
1549
+ if (tag.subtags) {
1550
+ if (this.checkIfSupportedTag(checkingTag, tag.subtags)) {
1551
+ result = true;
1552
+ }
1553
+ } else if (checkingTag === key) {
1554
+ result = true;
1555
+ }
1556
+ });
1557
+
1558
+ return result;
1559
+ };
1560
+
1561
+ skipTags(tag) {
1562
+ // If the tag contains the word "entryTrigger.", then it's an event context tag and it should not be skipped.
1563
+ if (tag?.match(ENTRY_TRIGGER_TAG_REGEX)) {
1564
+ return false;
1565
+ }
1566
+ // Use some() to check if any pattern matches (stops on first match)
1567
+ return SKIP_TAGS_REGEX_GROUPS.some((group) => {
1568
+ // Create a new RegExp for each test to avoid state issues with global flag
1569
+ const groupRegex = new RegExp(group);
1570
+ return groupRegex.test(tag);
1571
+ });
1572
+ }
1573
+
1574
+ validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
1575
+ let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
1576
+ if (this.props.tagModule) {
1577
+ currentModule = this.props.tagModule;
1578
+ }
1579
+ const tags = tagsParam ? tagsParam : this.props.tags;
1580
+ const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content;
1581
+ const isOutboundModule = (currentModule || '').toUpperCase() === OUTBOUND;
1582
+
1583
+ const initialMissingTags = (tags && tags.length && !isFullMode && isEmail && isOutboundModule && !isEmailUnsubscribeTagOptional() && !hasUnsubscribeTag(content))
1584
+ ? ['unsubscribe']
1585
+ : [];
1586
+
1587
+ const response = validateTagsCore({
1588
+ contentForBraceCheck: contentForValidation,
1589
+ contentForUnsubscribeScan: content,
1590
+ tags,
1591
+ currentModule,
1592
+ isFullMode,
1593
+ initialMissingTags, // [] or ['unsubscribe']; core uses this instead of definition-based when provided
1594
+ skipTagsFn: this.skipTags.bind(this),
1595
+ includeIsContentEmpty: true,
1596
+ });
1597
+
1598
+ // 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.
1599
+ const validString = /\S/.test(contentForValidation);
1600
+ if (isEmailUnsubscribeTagOptional() && isEmail && isOutboundModule) {
1601
+ const missingTagIndex = response.missingTags.indexOf('unsubscribe');
1602
+ if (missingTagIndex !== -1) {
1603
+ response.missingTags.splice(missingTagIndex, 1);
1604
+ }
1605
+ if (!validString) {
1606
+ response.isContentEmpty = true;
1607
+ }
1608
+ }
1609
+ return response;
1610
+ }
1611
+ /* eslint-enable */
1612
+ initializeParentSection(section, formDataObj, isContainer, index, ifError) {
1613
+ let formData = formDataObj;
1614
+ _.forEach(section.childSections, (childSection) => {
1615
+ if (childSection.type === 'col-label') {
1616
+ formData = this.initializeColLabelSection(childSection, formData, isContainer, index, ifError);
1617
+ } else if (childSection.type === 'multicols') {
1618
+ formData = this.initializeMultiColSection(childSection, formData, isContainer, index, ifError);
1619
+ } else {
1620
+ formData = this.initializeParentSection(childSection, formData, isContainer, index, ifError);
1621
+ }
1622
+ });
1623
+ return formData;
1624
+ }
1625
+
1626
+ // eslint-disable-next-line consistent-return
1627
+ initializeEditErrorData(formData, tabCount, isReturnErrorData) {
1628
+ const errorData = {};
1629
+ for (let idx = 0; idx < tabCount; idx += 1 ) {
1630
+ _.forEach(formData[0], (val, key) => {
1631
+ if (!errorData[idx]) {
1632
+ errorData[idx] = {};
1633
+ }
1634
+ if (key === 'tabKey' || key === 'base' || key === 'selectedLanguages') {
1635
+ return true;
1636
+ }
1637
+ if (this.props.isNewVersionFlow && formData[0].selectedLanguages.indexOf(key) > -1) {
1638
+ _.forEach(formData[0].selectedLanguages, (lang) => {
1639
+ if (errorData[idx] === undefined) {
1640
+ errorData[idx] = {};
1641
+ }
1642
+ if (errorData[idx][lang] === undefined) {
1643
+ errorData[idx][lang] = {};
1644
+ }
1645
+ errorData[idx][lang]['template-content'] = false;
1646
+ });
1647
+ } else {
1648
+ errorData[idx][key] = false;
1649
+ }
1650
+ return true;
1651
+ });
1652
+ }
1653
+ if (isReturnErrorData) {
1654
+ return errorData;
1655
+ }
1656
+ this.setState({errorData});
1657
+ }
1658
+
1659
+ initializeMultiColSection(section, formDataObj, isContainer, index, ifError) {
1660
+ const formData = _.cloneDeep(formDataObj);
1661
+ const newFormData = {};
1662
+ newFormData[index] = {};
1663
+ _.forEach(section.inputFields, (inputField) => {
1664
+ _.forEach(inputField.cols, (col) => {
1665
+ if (isContainer && !formData[index]) {
1666
+ formData[index] = {};
1667
+ }
1668
+ if (isContainer && this.props.isNewVersionFlow) {
1669
+ // if (formData[index] && formData[index].selectedLanguages && formData[index].selectedLanguages.length > 0) {
1670
+ // for (let i = 0; i < formData[index].selectedLanguages.length; i += 1 ) {
1671
+ // formData[index][formData[index].selectedLanguages[i]] = {};
1672
+ // }
1673
+ // } else {
1674
+ // formData[index][`${this.props.baseLanguage ? this.props.baseLanguage : 'en'}`] = {};
1675
+ // }
1676
+ _.forEach(this.props.supportedLanguages, (language) => {
1677
+ if (!formData[index][language.iso_code] && this.props.baseLanguage === language.iso_code) {
1678
+ formData[index][language.iso_code] = {};
1679
+ }
1680
+ });
1681
+ }
1682
+ // if (!ifError && (col.id.indexOf('template-label-') === -1 && col.id.indexOf('tagList-') === -1)) {
1683
+ // this.formElements.push(col.id);
1684
+ // }
1685
+ if (!col.onlyDisplay && !ifError) {
1686
+ if (col.id && col.id.indexOf('tagList-') === -1) {
1687
+ this.formElements.push(col.id);
1688
+ }
1689
+
1690
+ if (!this.props.isNewVersionFlow) {
1691
+ if (isContainer && formData[index][col.id] === undefined) {
1692
+ if (ifError) {
1693
+ formData[index][col.id] = false;
1694
+ } else if (isContainer && formData[index][col.id] === undefined && col.value) {
1695
+ formData[index][col.id] = col.value;
1696
+ } else {
1697
+ formData[index][col.id] = '';
1698
+ }
1699
+ } else if (!isContainer && ifError && formData[col.id] === undefined) {
1700
+ formData[col.id] = false;
1701
+ } else if (!isContainer && !ifError && formData[col.id] === undefined) {
1702
+ formData[col.id] = '';
1703
+ }
1704
+ } else if (!(col.id.indexOf('template-content') > -1 && col.id !== 'template-content')) {
1705
+ if (isContainer && formData[index] && formData[index][this.props.baseLanguage] && formData[index][this.props.baseLanguage][col.id] === undefined) {
1706
+ if (ifError) {
1707
+ formData[index][this.props.baseLanguage][col.id] = false;
1708
+ } else if (isContainer && formData[index][this.props.baseLanguage][col.id] === undefined && col.value) {
1709
+ formData[index][this.props.baseLanguage][col.id] = col.value;
1710
+ } else {
1711
+ formData[index][this.props.baseLanguage][col.id] = '';
1712
+ }
1713
+ } else if (!isContainer && ifError && formData[col.id] === undefined) {
1714
+ formData[col.id] = false;
1715
+ } else if (!isContainer && !ifError && formData[col.id] === undefined) {
1716
+ formData[col.id] = '';
1717
+ }
1718
+ }
1719
+ }
1720
+ if (col.type === 'checkbox') {
1721
+ if (isContainer && !formData[index][col.id]) {
1722
+ formData[index][col.id] = false;
1723
+ } else if (!isContainer && formData[col.id] === undefined) {
1724
+ formData[col.id] = false;
1725
+ }
1726
+ }
1727
+ });
1728
+ });
1729
+ return formData;
1730
+ }
1731
+
1732
+ initializeColLabelSection(section, formDataObj, isContainer, index, ifError) {
1733
+ const formData = formDataObj;
1734
+ _.forEach(section.inputFields, (inputField) => {
1735
+ if (isContainer && !formData[index]) {
1736
+ formData[index] = {};
1737
+ }
1738
+ if (!ifError && (inputField.id.indexOf('template-label-') === -1 && inputField.id.indexOf('tagList-') === -1)) {
1739
+ this.formElements.push(inputField.id);
1740
+ }
1741
+ if (!inputField.onlyDisplay) {
1742
+ if (isContainer && !this.props.isNewVersionFlow && formData[index][inputField.id] === undefined) {
1743
+ if (ifError) {
1744
+ formData[index][inputField.id] = false;
1745
+ } else {
1746
+ formData[index][inputField.id] = '';
1747
+ }
1748
+ } else if (isContainer && this.props.isNewVersionFlow && formData[index][this.props.baseLanguage][inputField.id] === undefined) {
1749
+ if (ifError) {
1750
+ formData[index][this.props.baseLanguage][inputField.id] = false;
1751
+ } else {
1752
+ formData[index][this.props.baseLanguage][inputField.id] = '';
1753
+ }
1754
+ } else if (!isContainer && ifError && this.state.formData[inputField.id] === undefined) {
1755
+ formData[inputField.id] = false;
1756
+ } else if (!isContainer && !ifError && this.state.formData[inputField.id] === undefined) {
1757
+ formData[inputField.id] = '';
1758
+ }
1759
+ }
1760
+ if (inputField.type === 'checkbox') {
1761
+ if (isContainer) {
1762
+ formData[index][inputField.id] = false;
1763
+ } else {
1764
+ formData[inputField.id] = false;
1765
+ }
1766
+ }
1767
+ });
1768
+ return formData;
1769
+ }
1770
+
1771
+ initializeStandAloneSections(schema, formDataObj, ifError) {
1772
+ let formData = _.cloneDeep(formDataObj);
1773
+ const channel = _.get(this, "props.schema.channel");
1774
+ let isContainer = false;
1775
+ let index = -1;
1776
+ if (channel && channel.toUpperCase() === SMS) {
1777
+ // sms v2 scema does not have any containers section the input ant preview fields are in stand alon section and v1 FormBuilder did not have valiation support for stand alone input fields
1778
+ isContainer = true;
1779
+ index = 0;
1780
+ }
1781
+ if (schema.standalone) {
1782
+ _.forEach(schema.standalone.sections, (section) => {
1783
+ if (section) {
1784
+ if (section.type === 'multicols') {
1785
+ formData = this.initializeMultiColSection(section, formData, isContainer, index, ifError);
1786
+ } else if (section.type === 'col-label') {
1787
+ formData = this.initializeColLabelSection(section, formData, isContainer, index, ifError);
1788
+ } else if (section.type === 'parent') {
1789
+ formData = this.initializeParentSection(section, formData, isContainer, index, ifError);
1790
+ }
1791
+ }
1792
+ });
1793
+ }
1794
+ return formData;
1795
+ }
1796
+
1797
+ initializeContainers(schema, formDataObj, ifError) {
1798
+ let formData = _.cloneDeep(formDataObj);
1799
+ if (this.props.isNewVersionFlow) {
1800
+ _.forEach(schema.containers, (val, index) => {
1801
+ if (val.type === 'tabs') {
1802
+ this.setState({usingTabContainer: true});
1803
+ _.forEach(val.panes, (pane, paneIndex) => {
1804
+ _.forEach(pane.sections, (section) => {
1805
+ if (section.type === 'multicols') {
1806
+ formData = this.initializeMultiColSection(section, formData, true, index, ifError, paneIndex);
1807
+ } else if (section.type === 'col-label') {
1808
+ formData = this.initializeColLabelSection(section, formData, true, index, ifError);
1809
+ } else if (section.type === 'parent') {
1810
+ formData = this.initializeParentSection(section, formData, true, index, ifError);
1811
+ }
1812
+ });
1813
+ });
1814
+ if (ifError) {
1815
+ formData[val.id] = false;
1816
+ }
1817
+ formData[val.id] = '';
1818
+ }
1819
+ if (val.type === 'checkbox') {
1820
+ formData[val.id] = false;
1821
+ }
1822
+ });
1823
+ } else {
1824
+ _.forEach(schema.containers, (val) => {
1825
+ if (val.type === 'tabs') {
1826
+ this.setState({usingTabContainer: true});
1827
+ _.forEach(val.panes, (pane, index) => {
1828
+ _.forEach(pane.sections, (section) => {
1829
+ if (section.type === 'multicols') {
1830
+ formData = this.initializeMultiColSection(section, formData, true, index, ifError);
1831
+ } else if (section.type === 'col-label') {
1832
+ formData = this.initializeColLabelSection(section, formData, true, index, ifError);
1833
+ } else if (section.type === 'parent') {
1834
+ formData = this.initializeParentSection(section, formData, true, index, ifError);
1835
+ }
1836
+ });
1837
+ });
1838
+ if (ifError) {
1839
+ formData[val.id] = false;
1840
+ }
1841
+ formData[val.id] = '';
1842
+ }
1843
+ if (val.type === 'checkbox') {
1844
+ formData[val.id] = false;
1845
+ }
1846
+ });
1847
+ }
1848
+ return formData;
1849
+ }
1850
+
1851
+ initialiseForm(schema, makeFormEmpty, resetTabKeys) {
1852
+ let formData = makeFormEmpty ? {} : _.cloneDeep(this.state.formData);
1853
+ let errorData = {};
1854
+ this.formElements = [];
1855
+ if (_.isEmpty(schema)) {
1856
+ return;
1857
+ }
1858
+ errorData = this.initializeStandAloneSections(schema, errorData, true);
1859
+ formData = this.initializeStandAloneSections(schema, formData);
1860
+ formData = this.initializeContainers(schema, formData);
1861
+ errorData = this.initializeContainers(schema, errorData, true);
1862
+
1863
+
1864
+ if (!this.props.isEdit) {
1865
+ if (!formData[0]) {
1866
+ formData[0] = {};
1867
+ }
1868
+ if (!errorData[0]) {
1869
+ errorData[0] = {};
1870
+ }
1871
+ formData[0].base = true;
1872
+ formData[0].tabKey = formData[0].tabKey ? formData[0].tabKey : _.uniqueId();
1873
+ errorData.base = errorData[0];
1874
+ formData.base = formData[0];
1875
+ if (this.state.currentTab === 1) {
1876
+ this.setState({tabKey: formData.base.tabKey});
1877
+ }
1878
+ }
1879
+ const tempFormData = _.cloneDeep(formData);
1880
+ // Store the value of 'template-name' before the deletion process
1881
+ const templateNameValue = tempFormData['template-name'];
1882
+ if (!this.props.isNewVersionFlow) {
1883
+ _.forEach(formData, (element, key) => {
1884
+ // This code block is used to clean up the tempFormData object.
1885
+ // If the element is not an object and the key of the element is not found in the formElements array,
1886
+ // then the element is removed from the tempFormData object.
1887
+ if (typeof element !== 'object' && this.formElements.indexOf(key) === -1) {
1888
+ delete tempFormData[key];
1889
+ } else if (typeof element === "object") {
1890
+ // If the property key (innerKey) is not "tabKey", "base", "name", "imagePreview", "image"
1891
+ // and is not found in the formElements array, then the property is removed from the object in tempFormData.
1892
+ _.forEach(element, (innerElement, innerKey) => {
1893
+ if (innerKey !== "tabKey" && innerKey !== "base" && innerKey !== 'name' && innerKey !== 'imagePreview' && innerKey !== 'image' && this.formElements.indexOf(innerKey) === -1) {
1894
+ delete tempFormData[key][innerKey];
1895
+ }
1896
+ });
1897
+ }
1898
+ });
1899
+ }
1900
+ const channel = this.props.schema?.channel || "";
1901
+ // If 'template-name' was deleted, re-add it to tempFormData for mobilepush channel
1902
+ if (!('template-name' in tempFormData) && channel.toUpperCase() === MOBILE_PUSH) {
1903
+ tempFormData['template-name'] = templateNameValue;
1904
+ }
1905
+ formData = _.cloneDeep(tempFormData);
1906
+ this.setState({formData, errorData}, () => {
1907
+ this.validateForm();
1908
+ if (this.props.isSchemaChanged || resetTabKeys) {
1909
+ this.resetTabKeys(formData, this.props.tabCount, true);
1910
+ }
1911
+ });
1912
+ }
1913
+
1914
+ cloneVersionData(ifError) {
1915
+ if (this.state.tabCount === 0) {
1916
+ this.initialiseForm(this.props.schema, true);
1917
+ return;
1918
+ }
1919
+ let formData = {};
1920
+ if (ifError) {
1921
+ formData = Object.assign({}, this.state.errorData);
1922
+ } else {
1923
+ formData = Object.assign({}, this.state.formData);
1924
+ }
1925
+ let newTabIndex = 0;
1926
+ if (ifError) {
1927
+ newTabIndex = this.state.tabCount - 1;
1928
+ } else {
1929
+ newTabIndex = this.state.tabCount;
1930
+ }
1931
+ formData[newTabIndex] = {};
1932
+
1933
+ const newTabCount = newTabIndex + 1;
1934
+ _.forEach(formData[0], (val, key) => {
1935
+ if (key === 'base') {
1936
+ return true;
1937
+ }
1938
+ if (key === 'tabKey') {
1939
+ return true;
1940
+ }
1941
+ if (typeof val === 'string') {
1942
+ if (ifError) {
1943
+ formData[newTabIndex][`${key}${newTabCount}`] = false;
1944
+ } else if (this.props.isNewVersionFlow) {
1945
+ if (key.indexOf('activeTab') > -1) {
1946
+ formData[newTabIndex][`${key}`] = this.props.baseLanguage;
1947
+ } else {
1948
+ formData[newTabIndex][`${key}${newTabCount}`] = '';
1949
+ }
1950
+ } else {
1951
+ formData[newTabIndex][`${key}${newTabCount}`] = '';
1952
+ }
1953
+ } else if (typeof val === 'boolean') {
1954
+ formData[newTabIndex][`${key}${newTabCount}`] = false;
1955
+ } else if (typeof val === 'object') {
1956
+ if (this.props.isNewVersionFlow) {
1957
+ if (key.indexOf('selectedLanguages') > -1) {
1958
+ formData[newTabIndex][key] = [this.props.baseLanguage];
1959
+ } else if (key === this.props.baseLanguage) {
1960
+ formData[newTabIndex][key] = {};
1961
+ _.forEach(val, (subVal, index) => {
1962
+ if (index === 'tabKey') {
1963
+ formData[newTabIndex][key][index] = _.uniqueId();
1964
+ } else if (index === 'template-content') {
1965
+ formData[newTabIndex][key][index] = "";
1966
+ } else {
1967
+ formData[newTabIndex][key][index] = _.cloneDeep(formData[0][key][index]);
1968
+ }
1969
+ // }
1970
+ });
1971
+ }
1972
+ } else {
1973
+ formData[newTabIndex][key] = {};
1974
+ _.forEach(val, (subVal, index) => {
1975
+ formData[newTabIndex][key][index] = '';
1976
+ });
1977
+ }
1978
+ }
1979
+ return true;
1980
+ });
1981
+ let id = (this.props.isNewVersionFlow && formData[newTabIndex][this.props.baseLanguage].tabKey) ? formData[newTabIndex][this.props.baseLanguage].tabKey : _.uniqueId();
1982
+
1983
+ if (!ifError) {
1984
+ let validId = false;
1985
+ while (!validId) {
1986
+ validId = true;
1987
+ for (let idx = 0; idx < this.state.tabCount; idx += 1) {
1988
+ if (id === formData[idx].tabKey) {
1989
+ validId = false;
1990
+ }
1991
+ }
1992
+ if (validId) {
1993
+ break;
1994
+ }
1995
+ id = _.uniqueId();
1996
+ }
1997
+ formData[newTabIndex].tabKey = id;
1998
+ }
1999
+ // if (!this.props.isEdit) {
2000
+ if (ifError) {
2001
+ this.setState({errorData: formData});
2002
+ } else {
2003
+ this.props.onChange(formData, newTabCount, newTabIndex + 1);
2004
+
2005
+ this.setState({tabCount: newTabCount, formData, currentTab: newTabIndex + 1, tabKey: id}, () => {
2006
+ this.cloneVersionData(true);
2007
+ });
2008
+ }
2009
+ }
2010
+
2011
+ changeVersionName(data) {
2012
+ const formData = _.cloneDeep(this.state.formData);
2013
+ if (formData[this.state.currentTab - 1]) {
2014
+ formData[this.state.currentTab - 1].name = data;
2015
+ this.setState({formData});
2016
+ }
2017
+ }
2018
+
2019
+ markFinalTabVersion() {
2020
+ const formData = _.cloneDeep(this.state.formData);
2021
+ if (this.state.usingTabContainer) {
2022
+ for (let count = 0; count < this.state.tabCount; count += 1) {
2023
+ if (parseInt(this.state.currentTab - 1, 10) !== count) {
2024
+ formData[count].base = false;
2025
+ } else {
2026
+ formData[count].base = true;
2027
+ formData.base = formData[count];
2028
+ }
2029
+ }
2030
+ this.setState({formData});
2031
+
2032
+ this.props.onChange(formData, this.state.tabCount);
2033
+ }
2034
+ }
2035
+
2036
+ resetState(isEdit, usingTabContainer) {
2037
+ this.setState({
2038
+ formData: {},
2039
+ currentTab: 1,
2040
+ usingTabContainer: usingTabContainer || false,
2041
+ tabCount: 1,
2042
+ errorData: {},
2043
+ tabKey: '',
2044
+ }, () => {
2045
+ if (!isEdit) {
2046
+ this.initialiseForm(this.props.schema, true);
2047
+ }
2048
+ });
2049
+ }
2050
+
2051
+ duplicateVersion(ifError, currentTab) {
2052
+ let formData = {};
2053
+ let tabCount = 0;
2054
+ if (ifError) {
2055
+ formData = _.cloneDeep(this.state.errorData);
2056
+ tabCount = this.state.tabCount - 1;
2057
+ } else {
2058
+ formData = _.cloneDeep(this.state.formData);
2059
+ tabCount = this.state.tabCount;
2060
+ }
2061
+ const newTabCount = tabCount + 1;
2062
+ _.forEach(formData[0], (val, key) => {
2063
+ if (!formData[tabCount]) {
2064
+ formData[tabCount] = {};
2065
+ }
2066
+ if (key === 'base') {
2067
+ formData[tabCount].base = false;
2068
+ return true;
2069
+ }
2070
+ if (key === 'tabKey') {
2071
+ let validId = false;
2072
+ let id = _.uniqueId();
2073
+ while (!validId) {
2074
+ validId = true;
2075
+ for (let idx = 0; idx < this.state.tabCount; idx += 1) {
2076
+ if (id === formData[idx].tabKey) {
2077
+ validId = false;
2078
+ }
2079
+ }
2080
+ if (validId) {
2081
+ break;
2082
+ }
2083
+ id = _.uniqueId();
2084
+ }
2085
+ formData[tabCount].tabKey = id;
2086
+ return true;
2087
+ }
2088
+ const tempTab = ifError ? currentTab : this.state.currentTab;
2089
+ if (typeof val === 'string') {
2090
+ if (this.props.isNewVersionFlow) {
2091
+ formData[tabCount][`${key}`] = formData[tempTab - 1][`${key}`];
2092
+ } else {
2093
+ formData[tabCount][`${key}${newTabCount}`] = formData[tempTab - 1][`${key}${parseInt(tempTab, 10) === 1 ? '' : tempTab}`];
2094
+ }
2095
+ } else if (typeof val === 'boolean') {
2096
+ if (this.props.isNewVersionFlow) {
2097
+ formData[tabCount][`${key}`] = formData[tempTab - 1][`${key}`];
2098
+ } else {
2099
+ formData[tabCount][`${key}${newTabCount}`] = formData[tempTab - 1][`${key}${parseInt(tempTab, 10) === 1 ? '' : tempTab}`];
2100
+ }
2101
+ }
2102
+ return true;
2103
+ });
2104
+ if (ifError) {
2105
+ this.setState({errorData: formData});
2106
+ } else {
2107
+ const tempTab = ifError ? currentTab : this.state.currentTab;
2108
+ const version = `Version ${this.state.currentTab}`;
2109
+ formData[tabCount].name = `${COPY_OF} ${formData[tempTab - 1].name ? formData[tempTab - 1].name : version}`;
2110
+ formData[tabCount].base = false;
2111
+ const initialTab = this.state.currentTab;
2112
+ this.setState({formData, tabCount: tabCount + 1, currentTab: tabCount + 1, tabKey: formData[tabCount].tabKey}, () => {
2113
+ this.duplicateVersion(true, initialTab);
2114
+ });
2115
+
2116
+ this.props.onChange(formData, tabCount + 1, tabCount + 1);
2117
+ }
2118
+ }
2119
+
2120
+ deleteVersion(ifError, currentTab) {
2121
+ if (!ifError && this.state.tabCount === 1) {
2122
+ return;
2123
+ }
2124
+ let formData = {};
2125
+ let finalCurrentTab = 0;
2126
+ let tabCount = 0;
2127
+ if (ifError) {
2128
+ formData = _.cloneDeep(this.state.errorData);
2129
+ tabCount = this.state.tabCount + 1;
2130
+ finalCurrentTab = currentTab;
2131
+ } else {
2132
+ formData = _.cloneDeep(this.state.formData);
2133
+ tabCount = this.state.tabCount;
2134
+ finalCurrentTab = this.state.currentTab;
2135
+ }
2136
+ // delete formData[this.state.currentTab - 1];
2137
+ let finalActiveTabKey = '';
2138
+ if (tabCount > 1 && currentTab !== tabCount) {
2139
+ finalActiveTabKey = formData[currentTab].tabKey;
2140
+ }
2141
+
2142
+ if (tabCount > 1 && currentTab === tabCount) {
2143
+ finalActiveTabKey = formData[currentTab - 2].tabKey;
2144
+ finalCurrentTab = currentTab - 1;
2145
+ }
2146
+ // if (this.state.tabCount > 1 && this.state.currentTab === 1) {
2147
+ // finalActiveTabKey = this.state.formData[1].tabKey;
2148
+ // }
2149
+ if (tabCount === 1) {
2150
+ finalActiveTabKey = '';
2151
+ }
2152
+ const initialFormData = _.cloneDeep(formData[0]);
2153
+ for (let count = currentTab - 1; count < tabCount - 1; count += 1) {
2154
+ delete formData[count];
2155
+ _.forEach(initialFormData, (val, key) => {
2156
+ if (!formData[count]) {
2157
+ formData[count] = {};
2158
+ }
2159
+ if (key === 'base') {
2160
+ formData[count].base = formData[count + 1].base ? formData[count + 1].base : false;
2161
+ return true;
2162
+ }
2163
+ if (key === 'tabKey') {
2164
+ formData[count].tabKey = formData[count + 1].tabKey;
2165
+ return true;
2166
+ }
2167
+ formData[count][`${key}${count === 0 ? '' : count + 1}`] = formData[count + 1][`${key}${count + 2}`];
2168
+ return true;
2169
+ });
2170
+ }
2171
+ delete formData[tabCount - 1];
2172
+ if (ifError) {
2173
+ this.setState({errorData: formData});
2174
+ } else {
2175
+ const initialTab = currentTab;
2176
+ if (this.props.isNewVersionFlow) {
2177
+ formData['template-version-options'].splice(`${currentTab - 1}`, 1);
2178
+ formData['template-version'] = formData['template-version-options'][0].value;
2179
+ }
2180
+ this.setState({formData, tabCount: tabCount - 1, tabKey: finalActiveTabKey, currentTab: finalCurrentTab}, () => {
2181
+ this.deleteVersion(true, initialTab);
2182
+ });
2183
+
2184
+ this.props.onChange(formData, tabCount - 1);
2185
+ }
2186
+ }
2187
+
2188
+ deleteLanguage = () => {
2189
+ // if (this.state.formData[`${this.state.currentTab - 1}`].activeTab === this.props.baseLanguage) {
2190
+
2191
+ // } else {
2192
+ if (this.state.formData[`${this.state.currentTab - 1}`].activeTab !== this.props.baseLanguage) {
2193
+ const formData = _.cloneDeep(this.state.formData);
2194
+ const currentTab = `${this.state.currentTab - 1}`;
2195
+ const currentLang = formData[`${this.state.currentTab - 1}`].activeTab;
2196
+ const deleteLanguageIndex = formData[currentTab].selectedLanguages.indexOf(currentLang);
2197
+ const baseLangTabKey = formData[currentTab][this.props.baseLanguage].tabKey;
2198
+
2199
+ delete formData[currentTab][currentLang];
2200
+ formData[currentTab].activeTab = this.props.baseLanguage;
2201
+ formData[currentTab].selectedLanguages.splice(deleteLanguageIndex, 1);
2202
+ formData[currentTab].tabKey = baseLangTabKey;
2203
+ if (formData[currentTab].base) {
2204
+ delete formData.base[currentLang];
2205
+ delete formData.base.selectedLanguages.splice(deleteLanguageIndex, 1);
2206
+ formData.base.tabKey = baseLangTabKey;
2207
+ formData.base.activeTab = this.props.baseLanguage;
2208
+ }
2209
+
2210
+ this.setState({formData, currentLangTab: this.props.baseLanguage, tabKey: baseLangTabKey}, () => {
2211
+ this.state.currentEvent.call(this.props.parent, formData, deleteLanguageIndex);
2212
+ this.setState({currentEvent: {}, currentEventData: {}});
2213
+ });
2214
+ }
2215
+ };
2216
+
2217
+ renameVersion(currentTab) {
2218
+ document.getElementById(`tab-header${currentTab > 1 ? currentTab : ''}`).contentEditable = "true";
2219
+ document.getElementById(`tab-header${currentTab > 1 ? currentTab : ''}`).focus();
2220
+ }
2221
+
2222
+ updateFormData(data, val, event) {
2223
+
2224
+ // Check if this is a high-frequency input field that should be optimized
2225
+ const isHighFrequencyField = val && (
2226
+ val.id === 'template-name' ||
2227
+ val.id === 'template-subject'
2228
+ );
2229
+
2230
+ if (isHighFrequencyField && !event) {
2231
+ // For high-frequency fields: immediate UI update + debounced expensive operations
2232
+ this.updateFormDataOptimized(data, val, event);
2233
+ return;
2234
+ }
2235
+
2236
+ // For non-high-frequency fields or special events, use immediate update
2237
+ this.performFormDataUpdate(data, val, event);
2238
+ }
2239
+
2240
+ // Optimized update for high-frequency fields
2241
+ updateFormDataOptimized(data, val, event) {
2242
+ // 1. Immediate UI update - update the field value instantly
2243
+ this.updateFieldValueImmediately(data, val);
2244
+
2245
+ // 2. Debounce expensive operations (validation, parent updates) - skip state update since we already did it
2246
+ this.debouncedUpdateFormData(data, val, event, true);
2247
+ }
2248
+
2249
+ // Update field value immediately for UI feedback
2250
+ updateFieldValueImmediately(data, val) {
2251
+ const currentFormData = this.state.formData;
2252
+ let updatedFormData;
2253
+
2254
+ if (!this.state.usingTabContainer || val.standalone) {
2255
+ // Simple field update
2256
+ updatedFormData = {
2257
+ ...currentFormData,
2258
+ [val.id]: data
2259
+ };
2260
+ } else {
2261
+ // Tab container update
2262
+ const tabIndex = this.state.currentTab - 1;
2263
+ updatedFormData = { ...currentFormData };
2264
+
2265
+ if (updatedFormData[tabIndex]) {
2266
+ updatedFormData[tabIndex] = {
2267
+ ...updatedFormData[tabIndex],
2268
+ [val.id]: data
2269
+ };
2270
+ }
2271
+ }
2272
+
2273
+ // Update state immediately for UI feedback
2274
+ this.setState({ formData: updatedFormData });
2275
+ }
2276
+
2277
+ // Legacy updateFormData function - kept for backward compatibility
2278
+ updateFormDataLegacy(data, val, event) {
2279
+ const formData = _.cloneDeep(this.state.formData);
2280
+
2281
+ const tabIndex = this.state.currentTab - 1;
2282
+ let currentTab = _.cloneDeep(this.state.currentTab);
2283
+
2284
+ if (this.state.usingTabContainer && !val.standalone) {
2285
+ const data1 = data;
2286
+ if (event === ADD_LANGUAGE) {
2287
+ const addLanguageType = this.props.addLanguageType;
2288
+ if (addLanguageType === '') {
2289
+ return;
2290
+ }
2291
+ const currentLang = formData[tabIndex].activeTab;
2292
+ let baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
2293
+ let id = _.uniqueId();
2294
+ let validId = false;
2295
+ switch (addLanguageType) {
2296
+ case "upload":
2297
+ baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
2298
+
2299
+ baseTab.iso_code = data.iso_code;
2300
+ baseTab.lang_id = data.lang_id;
2301
+ baseTab.language = data.language;
2302
+ while (!validId) {
2303
+ validId = true;
2304
+ for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
2305
+ if (!formData[tabIndex]) {
2306
+ continue;
2307
+ }
2308
+
2309
+ if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
2310
+ validId = false;
2311
+ }
2312
+ }
2313
+ if (validId) {
2314
+ break;
2315
+ }
2316
+ id = _.uniqueId();
2317
+ }
2318
+ baseTab.tabKey = id;
2319
+ //formData[tabIndex].selectedLanguages.push(data.iso_code);
2320
+ formData[tabIndex][data.iso_code] = baseTab;
2321
+
2322
+ formData[tabIndex].activeTab = data.iso_code;
2323
+ formData[tabIndex].tabKey = baseTab.tabKey;
2324
+ break;
2325
+ case "copyPrimaryLanguage":
2326
+ case "useEditor":
2327
+ baseTab = _.cloneDeep(formData[tabIndex][this.props.baseLanguage]);
2328
+
2329
+ //baseTab['template-content'] = '';
2330
+ baseTab.iso_code = data.iso_code;
2331
+ baseTab.lang_id = data.lang_id;
2332
+ baseTab.language = data.language;
2333
+ validId = false;
2334
+ while (!validId) {
2335
+ validId = true;
2336
+ for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
2337
+ if (!formData[idx]) {
2338
+ continue;
2339
+ }
2340
+ if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
2341
+ validId = false;
2342
+ }
2343
+ }
2344
+ if (validId) {
2345
+ break;
2346
+ }
2347
+ id = _.uniqueId();
2348
+ }
2349
+ baseTab.tabKey = id;
2350
+ formData[tabIndex].selectedLanguages.push(data.iso_code);
2351
+ formData[tabIndex][data.iso_code] = baseTab;
2352
+
2353
+ formData[tabIndex].activeTab = data.iso_code;
2354
+ formData[tabIndex].tabKey = baseTab.tabKey;
2355
+ break;
2356
+ case '':
2357
+ return;
2358
+ default:
2359
+ break;
2360
+ }
2361
+ const that = this;
2362
+ setTimeout(() => {
2363
+ that.setState({tabKey: baseTab.tabKey});
2364
+ }, 0);
2365
+ }
2366
+
2367
+ if (!this.props.isNewVersionFlow) {
2368
+ formData[tabIndex][val.id] = data1;
2369
+ } else if (this.props.isNewVersionFlow && event !== ADD_LANGUAGE && event !== "onContentChange") {
2370
+ formData[tabIndex][this.props.baseLanguage][val.id] = data1;
2371
+ }
2372
+
2373
+ if (formData[tabIndex].base) {
2374
+ if (!this.props.isNewVersionFlow) {
2375
+ formData.base[val.id] = data1;
2376
+ } else {
2377
+ formData.base[data1.iso_code] = formData[tabIndex][data1.iso_code];
2378
+ formData.base.tabKey = formData[tabIndex].tabKey;
2379
+ formData.base.activeTab = formData[tabIndex].activeTab;
2380
+ formData.base.selectedLanguages = formData[tabIndex].selectedLanguages;
2381
+ }
2382
+ }
2383
+ } else {
2384
+ formData[val.id] = data;
2385
+ }
2386
+
2387
+ if (this.props.isNewVersionFlow) {
2388
+ if (event === 'onSelect' && data === 'New Version') {
2389
+ this.callChildEvent(data, val, 'addVersion', event);
2390
+ } else if (event === 'onSelect' && data !== 'New Version') {
2391
+ currentTab = _.findIndex(this.state.formData['template-version-options'], { key: data}) + 1;
2392
+ this.setState({currentTab, tabKey: formData[`${currentTab - 1}`].tabKey}, () => {
2393
+ val.injectedEvents[event].call(this, this.state.formData['template-version-options'][currentTab - 1].key, formData, val);
2394
+ });
2395
+ }
2396
+
2397
+ if (event === 'onContentChange') {
2398
+
2399
+ // formData[`${this.state.currentTab - 1}`][formData[`${this.state.currentTab - 1}`].activeTab]['template-content'] = data.editor.getData();
2400
+ // if (formData[`${this.state.currentTab - 1}`].tabKey === formData.base.tabKey) {
2401
+ // formData.base[formData[`${this.state.currentTab - 1}`].activeTab][`template-content`] = data.editor.getData();
2402
+ //}
2403
+ }
2404
+ }
2405
+
2406
+
2407
+ this.setState({formData}, () => {
2408
+ if (this.props.startValidation) {
2409
+ this.validateForm();
2410
+ }
2411
+ });
2412
+ if (event && val.injectedEvents[event]) {
2413
+ if (event === "onRowClick") {
2414
+ val.injectedEvents[event].call(this, data);
2415
+ } else if (this.props.isNewVersionFlow && event !== 'onSelect') {
2416
+ if (event === ADD_LANGUAGE) {
2417
+ val.injectedEvents[event].call(this, data, formData, val);
2418
+ this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
2419
+ } else {
2420
+ val.injectedEvents[event].call(this, true, formData, val);
2421
+ }
2422
+ } else if (!this.props.isNewVersionFlow) {
2423
+ val.injectedEvents[event].call(this, true, formData, val);
2424
+ }
2425
+ } else if (val.injectedEvents && val.injectedEvents.onChange) {
2426
+ val.injectedEvents.onChange.call(this, true, formData, val);
2427
+ }
2428
+
2429
+ if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
2430
+ this.props.onChange(formData, this.state.tabCount, currentTab, val);
2431
+ }
2432
+ }
2433
+
2434
+
2435
+ hasClass(element, className) {
2436
+ return (` ${element.className} `).indexOf(` ${className} `) > -1;
2437
+ }
2438
+
2439
+ // Handle field blur for validation
2440
+ handleFieldBlur = (e, val) => {
2441
+ // Trigger validation on blur for high-frequency fields
2442
+ if (val && (val.id === 'template-name' || val.id === 'template-subject')) {
2443
+ this.debouncedValidation();
2444
+ }
2445
+ }
2446
+
2447
+ // Memoized validation for specific fields
2448
+ getMemoizedValidation = (fieldId, fieldValue) => {
2449
+ const cacheKey = `${fieldId}_${fieldValue}`;
2450
+
2451
+ if (this.validationCache.has(cacheKey)) {
2452
+ return this.validationCache.get(cacheKey);
2453
+ }
2454
+
2455
+ // Perform validation logic here
2456
+ const isValid = this.performFieldValidation(fieldId, fieldValue);
2457
+ this.validationCache.set(cacheKey, isValid);
2458
+
2459
+ // Clear cache if it gets too large
2460
+ if (this.validationCache.size > 100) {
2461
+ this.validationCache.clear();
2462
+ }
2463
+
2464
+ return isValid;
2465
+ }
2466
+
2467
+ // Perform validation for a specific field
2468
+ performFieldValidation = (fieldId, fieldValue) => {
2469
+ // Basic validation logic for template-name and template-subject
2470
+ if (fieldId === 'template-name' || fieldId === 'template-subject') {
2471
+ return fieldValue && fieldValue.trim().length > 0;
2472
+ }
2473
+ return true;
2474
+ }
2475
+
2476
+ // Cleanup debounced functions on component unmount
2477
+ componentWillUnmount() {
2478
+ if (this.debouncedUpdateFormData) {
2479
+ this.debouncedUpdateFormData.cancel();
2480
+ }
2481
+ if (this.debouncedValidation) {
2482
+ this.debouncedValidation.cancel();
2483
+ }
2484
+ // Clear validation cache
2485
+ if (this.validationCache) {
2486
+ this.validationCache.clear();
2487
+ }
2488
+ }
2489
+ allowAddSecondaryCta = (val) => {
2490
+ if (val.fieldsCount > 0) {
2491
+ const errorData = _.cloneDeep(this.state.errorData);
2492
+ _.forEach(Object.keys(errorData), (key) => {
2493
+ if (typeof errorData[key] !== "object") {
2494
+ errorData[key] = false;
2495
+ } else {
2496
+ _.forEach(Object.keys(errorData[key]), (innerKey) => {
2497
+ errorData[key][innerKey] = false;
2498
+ });
2499
+ }
2500
+ });
2501
+ if (this.state.formData[this.state.currentTab - 1][`secondary-cta-${val.fieldsCount - 1}-label`] === "") {
2502
+ errorData[this.state.currentTab - 1][`secondary-cta-${val.fieldsCount - 1}-label`] = true;
2503
+ this.setState({errorData, checkValidation: true, isFormValid: false});
2504
+ return false;
2505
+ }
2506
+ if (this.state.formData[this.state.currentTab - 1][`cta-deeplink-secondary-cta-${val.fieldsCount - 1}`] === "") {
2507
+ errorData[this.state.currentTab - 1][`cta-deeplink-secondary-cta-${val.fieldsCount - 1}`] = true;
2508
+ this.setState({errorData, checkValidation: true, isFormValid: false});
2509
+ return false;
2510
+ }
2511
+ if (this.state.formData[this.state.currentTab - 1][`cta-deeplink-secondary-cta-${val.fieldsCount - 1}-select`] === "") {
2512
+ errorData[this.state.currentTab - 1][`cta-deeplink-secondary-cta--${val.fieldsCount - 1}-select`] = true;
2513
+ this.setState({errorData, checkValidation: true, isFormValid: false});
2514
+ return false;
2515
+ }
2516
+ }
2517
+ return true;
2518
+ };
2519
+ callChildEvent(data, val, event) {
2520
+ if (!event) {
2521
+ return;
2522
+ }
2523
+
2524
+ const tab = this.state.tabCount;
2525
+ // const parent = this._reactInternalInstance._currentElement._owner._instance;
2526
+ if (event === 'onTabChange') {
2527
+ const that = this;
2528
+ this.setState({tabKey: `${data}`});
2529
+ setTimeout(() => {
2530
+ let activeTabIndex = '1';
2531
+ let selector = ".ant-tabs-tab";
2532
+ if (this.props.schema.channel.toUpperCase() === "MOBILEPUSH") {
2533
+ selector = `.mobilepush-wrapper ${selector}`;
2534
+ }
2535
+ const elems = document.querySelectorAll(selector);
2536
+
2537
+ for (let i = 0; i < elems.length; i += 1) {
2538
+ if (elems.item(i).classList.contains("ant-tabs-tab-active")) {
2539
+ activeTabIndex = i + 1;
2540
+ }
2541
+ }
2542
+ if (this.props.isNewVersionFlow) {
2543
+ const formData = _.cloneDeep(this.state.formData);
2544
+ formData[(this.state.currentTab - 1)].activeTab = formData[(this.state.currentTab - 1)].selectedLanguages[activeTabIndex - 1];
2545
+ formData[(this.state.currentTab - 1)].tabKey = `${data}`;
2546
+ if (formData[(this.state.currentTab - 1)].base) {
2547
+ formData.base.activeTab = formData[(this.state.currentTab - 1)].selectedLanguages[activeTabIndex - 1];
2548
+ formData.base.tabKey = `${data}`;
2549
+ }
2550
+
2551
+ that.setState({formData, currentLangTab: formData[(this.state.currentTab - 1)].activeTab, tabKey: formData[(this.state.currentTab - 1)][formData[(this.state.currentTab - 1)].activeTab].tabKey}, () => {
2552
+ val.injectedEvents[event].call(that.props.parent, this.state.currentTab, formData);
2553
+ });
2554
+ } else {
2555
+ that.setState({currentTab: activeTabIndex});
2556
+ val.injectedEvents[event].call(that.props.parent, activeTabIndex);
2557
+ }
2558
+ }, 0);
2559
+ return;
2560
+ } else if (event === 'addVersion') {
2561
+ this.cloneVersionData();
2562
+ this.setState({ popoverVisible: false });
2563
+ const that = this;
2564
+ setTimeout(() => {
2565
+ val.injectedEvents[event].call(that.props.parent, tab);
2566
+ }, 0);
2567
+ return;
2568
+ } else if (event === 'onTagSelect') {
2569
+ val.injectedEvents[event].call(this.props.parent, data, this.state.currentTab, val);
2570
+ return;
2571
+ } else if (event === 'markFinalVersion' || event === 'duplicateVersion' || event === 'deleteVersion' || event === 'renameVersion') {
2572
+ this.setState({ popoverVisible: false });
2573
+ if ((event !== 'deleteVersion' && !this.props.isNewVersionFlow) || ((event === 'deleteVersion') && this.props.schema.channel && this.props.schema.channel.toUpperCase() === 'EMAIL')) {
2574
+ val.injectedEvents[event].call(this.props.parent, data, this.state.currentTab);
2575
+ } else {
2576
+ this.setState({currentEvent: val.injectedEvents[event], currentEventData: data});
2577
+ }
2578
+ if (event === 'markFinalVersion') {
2579
+ this.markFinalTabVersion();
2580
+ if (this.props.isNewVersionFlow) {
2581
+ val.injectedEvents[event].call(this.props.parent, data, this.state.currentTab);
2582
+ }
2583
+ } else if (event === 'duplicateVersion') {
2584
+ if (this.props.schema.channel.toUpperCase() === 'EMAIL') {
2585
+ const formData = _.cloneDeep(this.state.formData);
2586
+ formData[this.state.tabCount] = _.cloneDeep(formData[`${this.state.currentTab - 1}`]);
2587
+ _.forEach(formData[this.state.tabCount].selectedLanguages, (lang) => {
2588
+ const tmptabData = formData[this.state.tabCount][lang];
2589
+ _.forEach(formData[this.state.tabCount][lang], (tabData, index) => {
2590
+ if (index === 'tabKey') {
2591
+ tmptabData.tabKey = _.uniqueId();
2592
+ }
2593
+ });
2594
+ });
2595
+ formData[this.state.tabCount].tabKey = formData[this.state.tabCount][formData[this.state.tabCount].activeTab].tabKey;
2596
+
2597
+ this.setState({formData, tabKey: formData[this.state.tabCount].tabKey}, () => {
2598
+ val.injectedEvents[event].call(this.props.parent, formData, this.state.currentTab);
2599
+ });
2600
+ } else {
2601
+ this.duplicateVersion();
2602
+ }
2603
+ } else if (event === 'deleteVersion') {
2604
+ if (this.props.schema.channel.toUpperCase() === 'EMAIL') {
2605
+ // this.deleteVersion(false, this.state.currentTab);
2606
+ this.setState({currentEvent: val.injectedEvents[event], currentEventData: data }, () => {
2607
+ this.props.setModalContent('delete-version');
2608
+ });
2609
+ } else {
2610
+ this.handleConfirmations();
2611
+ }
2612
+ } else if (event === 'renameVersion') {
2613
+ this.renameVersion(this.state.currentTab);
2614
+ }
2615
+ return;
2616
+ } else if (event === 'discardValues') {
2617
+ val.injectedEvents[event].call(this.props.parent, data);
2618
+ if (this.props.isEdit) {
2619
+ this.resetState(true, this.state.usingTabContainer);
2620
+ } else {
2621
+ this.resetState(false, this.state.usingTabContainer);
2622
+ }
2623
+ return;
2624
+ } else if (event === 'onChange') {
2625
+ if (val.id === 'tab-header') {
2626
+ val.injectedEvents[event].call(this.props.parent, data, this.state.currentTab);
2627
+ this.changeVersionName(data);
2628
+ }
2629
+ return;
2630
+ } else if (event === "addSecondaryCta") {
2631
+ if (!this.allowAddSecondaryCta(val)) {
2632
+ return;
2633
+ }
2634
+ } else if (event === "onContentChange") {
2635
+ val.injectedEvents[event].call(this.props.parent, data, val.id);
2636
+ return;
2637
+ } else if (event === 'deleteLanguage') {
2638
+ this.setState({currentEvent: val.injectedEvents[event], currentEventData: data }, () => {
2639
+ this.props.setModalContent('delete-language');
2640
+ });
2641
+ // const modalContent = {
2642
+ // title: "Alert",
2643
+ // body: "Do you really want to delete this language?",
2644
+ // type: 'confirm',
2645
+ // id: 'email-language-delete-modal',
2646
+ // };
2647
+ // debugger;
2648
+ // if (this.state.formData[`${this.state.currentTab - 1}`].activeTab === this.props.baseLanguage) {
2649
+ //
2650
+ // } else {
2651
+ // const formData = _.cloneDeep(this.state.formData);
2652
+ // const currentTab = `${this.state.currentTab - 1}`;
2653
+ // const currentLang = formData[`${this.state.currentTab - 1}`].activeTab;
2654
+ // const deleteLanguageIndex = formData[currentTab].selectedLanguages.indexOf(currentLang);
2655
+ //
2656
+ // delete formData[currentTab][currentLang];
2657
+ // formData[currentTab].activeTab = this.props.baseLanguage;
2658
+ // formData[currentTab].selectedLanguages.splice(deleteLanguageIndex, 1);
2659
+ //
2660
+ // this.setState({formData, currentLangTab: this.props.baseLanguage}, () => {
2661
+ // val.injectedEvents[event].call(this.props.parent, formData, deleteLanguageIndex);
2662
+ // });
2663
+ // }
2664
+ return;
2665
+ }
2666
+
2667
+ // val.injectedEvents[event].call(this.props.parent, data);
2668
+ val.injectedEvents[event].call(this.props.parent, data, val.id);
2669
+ }
2670
+ openFileDialog = (e, parentID) => {
2671
+
2672
+ document.querySelector(`#${parentID} #fileName`).click();
2673
+ };
2674
+ handleOk = (ev) => {
2675
+ // antd v6 wraps button text in a <span>; use currentTarget so the button id resolves.
2676
+ const id = (ev.currentTarget && ev.currentTarget.id) || ev.target.id;
2677
+ this.setState({showModal: false});
2678
+ if (id === "android" && this.state.androidValid) {
2679
+ this.onSubmitWrapper({singleTab: id});
2680
+ } else if (id === "ios" && this.state.iosValid) {
2681
+ this.onSubmitWrapper({singleTab: id});
2682
+ } else if (id === "sms-version-modal") {
2683
+ this.state.currentEvent.call(this.props.parent, this.state.currentEventData, this.state.currentTab);
2684
+ this.setState({currentEvent: {}, currentEventData: {}});
2685
+ this.deleteVersion(false, this.state.currentTab);
2686
+ } else if (id === 'template-back-confirm-modal') {
2687
+ const type = this.props.location.query.type;
2688
+ const response = {
2689
+ action: 'moveToTemplates',
2690
+ value: 'ok',
2691
+ };
2692
+ this.props.iframeParent.postMessage(JSON.stringify(response), '*');
2693
+ const isLanguageSupport = this.props.location.query.isLanguageSupport || false;
2694
+ const isEdmSupport = (this.props.location.query.isEdmSupport !== "false") || false;
2695
+ const module = this.props.location.query.module ? this.props.location.query.module : 'default';
2696
+ this.props.router.push({
2697
+ pathname: `/${(this.props.schema.channel || '').toLowerCase()}/`,
2698
+ query: type === 'embedded' ? {type: 'embedded', module, isLanguageSupport, isEdmSupport} : {module, isLanguageSupport, isEdmSupport},
2699
+ });
2700
+ } else if (id === "email-language-delete-modal") {
2701
+ this.deleteLanguage();
2702
+ this.props.handleCancelModal();
2703
+ // this.setState({currentEvent: {}, currentEventData: {}});
2704
+ } else if (id === 'email-version-delete-modal') {
2705
+ this.deleteVersion(false, this.state.currentTab);
2706
+ } else if ( id === 'template-delete-confirm-modal') {
2707
+ this.props.handleDelete(this.props.modal);
2708
+ }
2709
+ };
2710
+ handleCancel = (ev) => {
2711
+ // antd v6 wraps button text in a <span>; use currentTarget so the button id resolves.
2712
+ const id = (ev.currentTarget && ev.currentTarget.id) || ev.target.id;
2713
+ this.setState({showModal: false});
2714
+ if (id === 'template-back-confirm-modal') {
2715
+ const response = {
2716
+ action: 'moveToTemplate',
2717
+ value: 'cancel',
2718
+ };
2719
+ this.props.iframeParent.postMessage(JSON.stringify(response), '*');
2720
+ }
2721
+ if (this.props.handleCancelModal) {
2722
+ this.props.handleCancelModal();
2723
+ }
2724
+ };
2725
+ handleConfirmations = () => {
2726
+ this.setState({showModal: true});
2727
+ };
2728
+
2729
+ handleSetRadioValue = (formData, currentTab, val) => {
2730
+ if (formData[currentTab - 1] && formData[currentTab - 1][val.id] && formData[currentTab - 1][val.id] !== "") {
2731
+ return formData[currentTab - 1][val.id];
2732
+ } else if (formData[val.id]) {
2733
+ return formData[val.id];
2734
+ }
2735
+ return val.value;
2736
+ }
2737
+ uploadImages = (e, {files}, val) => {
2738
+ if (e) {
2739
+ e.preventDefault();
2740
+ }
2741
+ const _URL = window.URL || window.webkitURL;
2742
+ const file = files[0];
2743
+ if (val.supportedExtensions) {
2744
+ const allowedExtensions = /(\.bmp|\.jpeg|\.png|\.gif|\.avif|\.jpg)$/i;
2745
+ if (!allowedExtensions.exec(file.name)) {
2746
+ this.callChildEvent({file, type: 'wrong file'}, val, val.submitAction);
2747
+ }
2748
+ }
2749
+ const img = new Image();
2750
+ img.src = _URL.createObjectURL(file);
2751
+ img.onload = () => {
2752
+ const fileParams = {
2753
+ width: this.width || img.width,
2754
+ height: this.height || img.height,
2755
+ error: file && (file.size / (1e+6) > 5), // Checking if file exists and its size is greater than 5MB (5 * 10^6 bytes)
2756
+ };
2757
+ this.callChildEvent({file, type: 'image', fileParams}, val, val.submitAction);
2758
+ };
2759
+ if (e) {
2760
+ const event = e;
2761
+ event.target.value = null;
2762
+ }
2763
+ };
2764
+
2765
+ capUploaderCustomRequest = (uploadData, val) => {
2766
+ this.uploadImages(undefined, {files: [uploadData.file]}, val);
2767
+ }
2768
+
2769
+ findBaseTab = () => {
2770
+ const activeTabKey = this.state.formData && this.state.formData.base ? this.state.formData.base.tabKey : -1;
2771
+ const formData = _.cloneDeep(this.state.formData);
2772
+ let baseTab = 0;
2773
+ for (let i = 0; i < this.state.tabCount; i += 1) {
2774
+ if (formData[i] && formData[i].tabKey === activeTabKey) {
2775
+ baseTab = i;
2776
+ }
2777
+ }
2778
+
2779
+ return baseTab;
2780
+ };
2781
+
2782
+ handleOnHoverItem = (isHovering, id) => {
2783
+ if (isHovering) {
2784
+ this.setState({hoveredItem: id});
2785
+ } else {
2786
+ this.setState({hoveredItem: ''});
2787
+ }
2788
+ }
2789
+
2790
+ handleOnItemClick = (isClicked, id) => {
2791
+ this.setState({clickedItem: id});
2792
+ }
2793
+
2794
+ populateTemplatesList(data) {
2795
+ if (!data) {
2796
+ return [];
2797
+ }
2798
+ const items = [];
2799
+ data.map( (template) => {
2800
+ //
2801
+ const temp = {};
2802
+ temp.id = template._id;
2803
+ if ((this.props.schema.channel || '').toLowerCase() === 'email') {
2804
+ //
2805
+ const imgSrc = template && template.versions && template.versions.base ? template.versions.base.preview_http_url : '';
2806
+ // const ifPreviewGenerated = template && template.versions && template.versions.base ? template.versions.base.isPreviewGenerated : 0;
2807
+ const content = <CapImage src={imgSrc || ''} alt="" />;
2808
+
2809
+ temp.content = (
2810
+ <div
2811
+ className="sms-template-content">
2812
+ <div className={`sms-text ${this.state.clickedItem === template._id ? 'sms-text-blurred' : ''}`}>
2813
+ {content}
2814
+ </div>
2815
+ {/*template._id === this.state.clickedItem ? <CapButton onClick={(e) => this.handleEditClick(e, template._id)} className="edit-button" type="secondary">{type === 'embedded' ? this.props.intl.formatMessage(messages.selectButton) : this.props.intl.formatMessage(messages.editButton)}</CapButton> : ''*/}
2816
+ {/*template._id === this.state.clickedItem ? <CapButton onClick={() => this.handlePreviewClick(template)} className="preview-button" type="cancel">{this.props.intl.formatMessage(messages.previewButton)}</CapButton> : ''*/}
2817
+ </div>);
2818
+ }
2819
+ // temp.footer = (
2820
+ // <div className="footer-container">
2821
+ // <div className="card-title">
2822
+ // <span className="template-name" style={{ fontWeight: `${this.props.schema.channel.toLowerCase() === 'wechat' ? '400' : '600'}` }}>
2823
+ // { template && template.versions && template.versions.history && template.versions.history.length > 1 && this.props.schema.channel.toLowerCase() !== 'mobilepush' && <i style={{fontSize: '16px', margin: '0 8px 0 0', verticalAlign: 'middle'}} className="material-icons">filter_none</i>}
2824
+ // {template.name}
2825
+ // </span>
2826
+ // </div>
2827
+ // </div>
2828
+ // );
2829
+ items.push(temp);
2830
+ return true;
2831
+ });
2832
+ return items;
2833
+ }
2834
+
2835
+ handleAddLanguageFlow(data, val, event) {
2836
+ this.setState({currentEventData: data, currentEventVal: val, currentEvent: event, customPopoverVisible: false}, () => {
2837
+ this.props.setModalContent('add-language', data);
2838
+ });
2839
+ }
2840
+
2841
+
2842
+ getMissingOrUnsupportedTagsName = (content = '', type) => {
2843
+ const { MISSING_TAGS } = tagsTypes;
2844
+ const tagValidationResponse = this.validateTags(content);
2845
+ if (type && type === MISSING_TAGS) {
2846
+ return (tagValidationResponse[type] || []).join(', ').toString();
2847
+ }
2848
+ return null;
2849
+ };
2850
+
2851
+ renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
2852
+ const { checkValidation, errorData, currentTab, formData } = this.state;
2853
+ const { MISSING_TAGS } = tagsTypes;
2854
+ const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
2855
+ const ifError = checkValidation && errorType;
2856
+ const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
2857
+ const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
2858
+ const { formatMessage } = this.props.intl;
2859
+
2860
+ const aiContentBotDisabled = isAiContentBotDisabled();
2861
+
2862
+ let errorMessageText = false;
2863
+ switch (errorType) {
2864
+ case MISSING_TAG_ERROR:
2865
+ errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
2866
+ break;
2867
+ case TAG_BRACKET_COUNT_MISMATCH_ERROR:
2868
+ errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
2869
+ break;
2870
+ case true:
2871
+ errorMessageText = formatMessage(messages.genericTagsValidationError);
2872
+ break;
2873
+ default:
2874
+ break;
2875
+ }
2876
+
2877
+ if (this.props.restrictPersonalization && hasPersonalizationTags(messageContent)) {
2878
+ errorMessageText = formatMessage(messages.personalizationTagsErrorMessage);
2879
+ }
2880
+ // Empty/required error: only show after user has triggered validation (ifError / "Done").
2881
+ // All other errors (brace, personalization, missing tags, generic): show in real time while typing.
2882
+ const isContentEmpty = !messageContent || !/\S/.test(String(messageContent).trim());
2883
+ const isEmptyError = errorType && isContentEmpty;
2884
+ const showError = errorType && (isEmptyError ? ifError : true);
2885
+ const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
2886
+ if (prevErrorMessage !== errorMessageText && errorMessageText && this.isLiquidFlowSupportedByChannel()) {
2887
+ this.setState((prevState) => ({
2888
+ liquidErrorMessage: {
2889
+ ...prevState.liquidErrorMessage,
2890
+ STANDARD_ERROR_MSG: [errorMessageText],
2891
+ }
2892
+ }), () => {
2893
+
2894
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
2895
+ });
2896
+ }
2897
+ // Always render the textarea regardless of the liquid error state update above.
2898
+ // Previously the textarea was inside the `else` branch, so it was not rendered during the
2899
+ // one render cycle when the liquid error message was first set – causing the input to lose
2900
+ // focus whenever a brace/tag error first appeared.
2901
+ if (styling === 'semantic') {
2902
+ columns.push(
2903
+ <CapColumn key="input" span={val.width} offset={offset}>
2904
+ <TextArea
2905
+ id={val.id}
2906
+ placeholder={val.placeholder ? val.placeholder : ''}
2907
+ className={`${showError ? 'error-form-builder' : ''}`}
2908
+ errorMessage={
2909
+ showError && errorMessageText && (
2910
+ !this.isLiquidFlowSupportedByChannel() ||
2911
+ [MOBILE_PUSH, INAPP].includes(this.props.schema?.channel?.toUpperCase())
2912
+ )
2913
+ ? errorMessageText
2914
+ : ''
2915
+ }
2916
+ label={val.label}
2917
+ autosize={val.autosize ? val.autosizeParams : false}
2918
+ onChange={(e) => this.updateFormData(e.target.value, val)}
2919
+ onBlur={(e) => this.handleFieldBlur(e, val)}
2920
+ style={val.style ? val.style : {}}
2921
+ defaultValue={messageContent || ''}
2922
+ value={messageContent || ""}
2923
+ rows={rows}
2924
+ disabled={val.disabled}
2925
+ cols={cols}
2926
+ />
2927
+ {[SMS, MOBILE_PUSH].includes(this.props.schema?.channel)
2928
+ && !aiContentBotDisabled
2929
+ && (
2930
+ <CapAskAira.ContentGenerationBot
2931
+ text={messageContent || ""}
2932
+ setText={(x) => {
2933
+ this.updateFormData(x, val);
2934
+ }}
2935
+ iconPlacement="float-br"
2936
+ rootStyle={{
2937
+ // 1rem is the margin-bottom of textarea
2938
+ bottom: 'calc(1rem + 0.2rem)',
2939
+ right: '0.2rem',
2940
+ }}
2941
+ />
2942
+ )}
2943
+ </CapColumn>
2944
+ );
2945
+ }
2946
+ };
2947
+
2948
+
2949
+ renderColLabelSection(section, childIndex) {
2950
+ // const schema = this.props.schema
2951
+ const fields = [];
2952
+ _.forEach(section.inputFields, (val) => {
2953
+ const row = [];
2954
+ const columns = [];
2955
+ const type = val.type;
2956
+ const styling = val.styling;
2957
+ if (!this.props.isNewVersionFlow && !val.standalone && !val.onlyDisplay && this.props.usingTabContainer && !this.state.formData[`${this.state.currentTab - 1}`]) {
2958
+ return true;
2959
+ }
2960
+ if (!val.onlyDisplay && !val.standalone && (!this.state.formData[`${this.state.currentTab - 1}`] || (!this.state.formData[`${this.state.currentTab - 1}`][val.id] && this.state.formData[`${this.state.currentTab - 1}`][val.id] === null))) {
2961
+ return true;
2962
+ }
2963
+ if (!val.onlyDisplay && !val.standalone && (!this.state.errorData[`${this.state.currentTab - 1}`] || (!this.state.errorData[`${this.state.currentTab - 1}`][val.id] && this.state.errorData[`${this.state.currentTab - 1}`][val.id] === null))) {
2964
+ return true;
2965
+ }
2966
+
2967
+ let ifError = false;
2968
+ const isVersionEnable = (this.state.usingTabContainer && !val.standalone);
2969
+ if (val.primitive) {
2970
+ if (val.id === "mark-final-version-label") {
2971
+ const baseTab = this.findBaseTab();
2972
+ if ((baseTab + 1) === childIndex) {
2973
+ return true;
2974
+ }
2975
+ }
2976
+ columns.push(
2977
+ <CapColumn key={val.id} span={val.width}>
2978
+ {this.renderPrimitiveElement(val, childIndex)}
2979
+ </CapColumn>
2980
+ );
2981
+ return true;
2982
+ }
2983
+ let content = "";
2984
+ let charList = {};
2985
+ switch (type) {
2986
+ case "input":
2987
+ if (styling === 'semantic') {
2988
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
2989
+ const value = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id];
2990
+ columns.push(
2991
+ <CapColumn key={val.id} span={val.width} offset={val.offset} style={val.style || {}}>
2992
+ <CapInput
2993
+ id={val.id}
2994
+ errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
2995
+ className={`input-primary chart-name-input${ifError ? ' error' : ''}`}
2996
+ // fluid={val.fluid}
2997
+ style={val.style ? val.style : {}}
2998
+ placeholder={val.placeholder}
2999
+ onChange={(e) => this.updateFormData(e.target.value, val)}
3000
+ onBlur={(e) => this.handleFieldBlur(e, val)}
3001
+ defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3002
+ value={value || ""}
3003
+ disabled={val.disabled}
3004
+ size="default"
3005
+ />
3006
+ </CapColumn>
3007
+ );
3008
+ }
3009
+ break;
3010
+ case "textarea":
3011
+ const rows = 20;
3012
+ const cols = 20;
3013
+ this.renderTextAreaContent(styling, columns, val, isVersionEnable, rows, cols, val.offset);
3014
+ break;
3015
+ case "contentPreview":
3016
+ const { formData , currentTab } = this.state || {};
3017
+ const tab = formData && formData[currentTab - 1];
3018
+ content = isVersionEnable ? tab && tab['sms-editor'] : formData && formData['sms-editor'];
3019
+ charList = updateCharCount(content).char_list;
3020
+ columns.push(<CapColumn id={val.id} className={"preview-chars"} span={val.width}>
3021
+ {charList.map((char) => char)}
3022
+ </CapColumn>);
3023
+ break;
3024
+ case "checkbox":
3025
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3026
+ if (styling === 'semantic') {
3027
+ columns.push(
3028
+ <CapColumn key="input" span={val.width} offset={val.offset}>
3029
+ <CapCheckbox
3030
+ className={`${ifError ? 'error' : ''}`}
3031
+ errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
3032
+ style={val.style ? val.style : {}}
3033
+ onChange={(e) => this.updateFormData(e, val)}
3034
+ checked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3035
+ defaultChecked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3036
+ disabled={val.disabled}
3037
+ />
3038
+ </CapColumn>
3039
+ );
3040
+ }
3041
+ break;
3042
+ case "tag-list":
3043
+ columns.push(
3044
+ <CapColumn key={`input-${val.id}`} span={10} offset={val.offset}>
3045
+ <TagList
3046
+ key={`input-${val.id}`}
3047
+ moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
3048
+ label={val.label ? val.label : ''}
3049
+ onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
3050
+ onContextChange={this.props.onContextChange}
3051
+ location={this.props.location}
3052
+ tags={this.props.tags ? this.props.tags : []}
3053
+ injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
3054
+ className={val.className ? val.className : ''}
3055
+ id={val.id}
3056
+ userLocale={this.props.userLocale}
3057
+ selectedOfferDetails={this.props.selectedOfferDetails}
3058
+ eventContextTags={this.props?.eventContextTags}
3059
+ restrictPersonalization={this.props.restrictPersonalization}
3060
+ waitEventContextTags={this.props?.waitEventContextTags}
3061
+ />
3062
+ </CapColumn>
3063
+ );
3064
+ break;
3065
+ case "cap-tag-list-with-input":
3066
+ ifError = this.state.checkValidation && this.state.errorData[val.id];
3067
+ columns.push(
3068
+ <CapColumn key={`input-${val.id}`} span={val.width || 10} offset={val.offset}>
3069
+ <CapTagListWithInput
3070
+ key={`input-${val.id}`}
3071
+ inputId={val.id}
3072
+ inputValue={this.state.formData[val.id] || ''}
3073
+ inputOnChange={(e) => this.updateFormData(e.target.value, val)}
3074
+ inputPlaceholder={val.placeholder || ''}
3075
+ inputErrorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
3076
+ inputRequired={val.required || false}
3077
+ inputDisabled={val.disabled || false}
3078
+ headingText={val.label || ''}
3079
+ headingStyle={val.headingStyle || { marginTop: '3%', marginRight: '79%' }}
3080
+ headingType="h4"
3081
+ onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
3082
+ onContextChange={this.props.onContextChange}
3083
+ location={this.props.location}
3084
+ tags={this.props.tags ? this.props.tags : []}
3085
+ injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
3086
+ className={val.className ? val.className : ''}
3087
+ userLocale={this.props.userLocale}
3088
+ selectedOfferDetails={this.props.selectedOfferDetails}
3089
+ eventContextTags={this.props?.eventContextTags}
3090
+ waitEventContextTags={this.props?.waitEventContextTags}
3091
+ moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
3092
+ containerStyle={val.style || {}}
3093
+ inputProps={val.inputProps || {}}
3094
+ showInput={val.showInput !== false}
3095
+ showTagList={val.showTagList !== false}
3096
+ restrictPersonalization={this.props.restrictPersonalization}
3097
+ />
3098
+ </CapColumn>
3099
+ );
3100
+ break;
3101
+ case "tabs":
3102
+ columns.push(
3103
+ <CapColumn key="input" span={10}>
3104
+ <TagList
3105
+ onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
3106
+ />
3107
+ </CapColumn>
3108
+ );
3109
+ break;
3110
+ case "sms-preview": {
3111
+ if (this.state.usingTabContainer && !this.state.formData[`${this.state.currentTab - 1}`]) {
3112
+ return true;
3113
+ }
3114
+ content = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][`sms-editor${this.state.currentTab > 1 ? this.state.currentTab : ''}`] : this.state.formData[val.id];
3115
+ const unicodeEnabled = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][`unicode-validity${this.state.currentTab > 1 ? this.state.currentTab : ''}`] : this.state.formData['unicode-validity'];
3116
+ columns.push(
3117
+ <CapColumn key="input" span={10}>
3118
+ <UnifiedPreview
3119
+ channel={val.channel ? val.channel : 'SMS'}
3120
+ content={content}
3121
+ device={ANDROID}
3122
+ showDeviceToggle={false}
3123
+ showHeader={false}
3124
+ formatMessage={this.props.intl.formatMessage}
3125
+ senderId={unicodeEnabled ? 'Unicode' : 'ASCII'}
3126
+ />
3127
+ </CapColumn>
3128
+ );
3129
+ break;
3130
+ }
3131
+ case "popover":
3132
+ columns.push(
3133
+ <CapPopover
3134
+ trigger={val.trigger}
3135
+ placement={val.placement}
3136
+ visible={this.state.popoverVisible && (this.props.isNewVersionFlow || this.state.currentTab === childIndex || !childIndex)}
3137
+ onVisibleChange={this.handleVisibleChange}
3138
+ content={
3139
+ _.forEach(val.content.sections, (contentSection) => {
3140
+ this.renderSection(contentSection);
3141
+ })
3142
+ }
3143
+ >
3144
+ {
3145
+ _.forEach(val.value.sections, (popoverSection) => {
3146
+ this.renderSection(popoverSection);
3147
+ })
3148
+ }
3149
+ </CapPopover>
3150
+ );
3151
+ break;
3152
+ case "select": {
3153
+ const options = (this.state.formData[`${val.id}-options`]) ? (this.state.formData[`${val.id}-options`]) : val.options;
3154
+ columns.push(
3155
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3156
+ <CapSelect
3157
+ id={val.id}
3158
+ placeholder={val.placeholder ? val.placeholder : ''}
3159
+ options={options}
3160
+ style={val.style ? val.style : {}}
3161
+ onSelect={(data) => this.updateFormData(data, val, 'onSelect')}
3162
+ value={(this.state.formData[val.id] && this.state.formData[val.id] !== '' ? this.state.formData[val.id] : undefined)}
3163
+ disabled={val.disabled}
3164
+ label={val.label}
3165
+ />
3166
+ </CapColumn>
3167
+ );
3168
+ }
3169
+ break;
3170
+ case "ckeditor":
3171
+
3172
+ columns.push(
3173
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3174
+ <CKEditor
3175
+ id={val.id}
3176
+ content={(this.state.formData[`${this.state.currentTab - 1}`][val.id] ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : '')}
3177
+ events={val.events}
3178
+ getEditorInstanse={val.getEditorInstanse}
3179
+ // this.callChildEvent(data, val, 'addVersion', event);
3180
+ handleContentChange={(event) => this.callChildEvent(event, val, 'onContentChange')}
3181
+ currentOrgDetails={this.props.currentOrgDetails}
3182
+ />
3183
+ </CapColumn>
3184
+ );
3185
+ break;
3186
+ case "edmeditor": {
3187
+ // if (!this.state.formData[`${this.state.currentTab - 1}`]) {
3188
+ // return true;
3189
+ // }
3190
+ let langTab = 1;
3191
+ if (val.id.match(/edmeditor/g)) {
3192
+ if (val.length > 9 && val.id.charAt(9)) {
3193
+ langTab = val.id.charAt(9);
3194
+ }
3195
+ }
3196
+ const edmSrc = this.state.formData[`${this.state.currentTab - 1}`][this.state.currentLangTab][`edmeditor${langTab > 1 ? langTab : ''}src`];
3197
+
3198
+ if (!edmSrc) {
3199
+ return false;
3200
+ }
3201
+
3202
+ columns.push(
3203
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
3204
+ <EDMEditor
3205
+ id={val.id}
3206
+ getEditorInstanse={this.getEditorInstanse}
3207
+ edmSrc={edmSrc}
3208
+ />
3209
+ </CapColumn>
3210
+ );
3211
+ }
3212
+ break;
3213
+ case "label": {
3214
+ columns.push(
3215
+ <CapHeading
3216
+ type={val.headingType || "h4"}
3217
+ style={val.style || {}}
3218
+ >
3219
+ {val.value}
3220
+ </CapHeading>
3221
+ );
3222
+ break;
3223
+ }
3224
+ default:
3225
+ break;
3226
+ }
3227
+ row.push(
3228
+ <CapRow useLegacy>
3229
+ {
3230
+ _.forEach(columns, (value) => {
3231
+ if (value) {
3232
+ return value;
3233
+ }
3234
+ return value;
3235
+ })
3236
+ }
3237
+ </CapRow>
3238
+ );
3239
+
3240
+ fields.push(row);
3241
+ return true;
3242
+ });
3243
+ _.forEach(section.actionFields, (val) => {
3244
+ const row = [];
3245
+ const columns = [];
3246
+ const type = val.type;
3247
+ const styling = val.styling;
3248
+ switch (type) {
3249
+ case "button":
3250
+ if (styling === 'semantic' && val.metaType === 'submit-button') {
3251
+ columns.push(
3252
+ <CapColumn>
3253
+ <CapButton
3254
+ onClick={(data) => this.callChildEvent(data, val, val.submitAction)}>
3255
+ {val.value}
3256
+ </CapButton>
3257
+ </CapColumn>
3258
+ );
3259
+ }
3260
+ break;
3261
+ default:
3262
+ break;
3263
+ }
3264
+ row.push(columns);
3265
+ fields.push(row);
3266
+ });
3267
+ return fields;
3268
+ }
3269
+
3270
+ renderParentSection(section, childIndex) {
3271
+ const cols = [];
3272
+ section.childSections.map( (childSection) => {
3273
+ const renderedChildSection = this.renderSection(childSection, childIndex);
3274
+ cols.push(<CapColumn style={childSection.colStyle ? childSection.colStyle : {}}offset={childSection.offset} span={childSection.width}>{renderedChildSection}</CapColumn>);
3275
+ return true;
3276
+ });
3277
+ const row = (<CapRow useLegacy style={section.rowStyle ? section.rowStyle : {}}>
3278
+ {
3279
+ _.forEach(cols, (value) => {
3280
+ if (value) {
3281
+ return value;
3282
+ }
3283
+ return value;
3284
+ })
3285
+ }
3286
+ </CapRow>);
3287
+ return row;
3288
+ }
3289
+
3290
+ renderSection(section, childIndex) {
3291
+ switch (section.type) {
3292
+ case "col-label":
3293
+ return this.renderColLabelSection(section, childIndex);
3294
+ case "multicols":
3295
+ return this.renderMultiColSection(section, childIndex);
3296
+ case "parent":
3297
+ return this.renderParentSection(section, childIndex);
3298
+ default:
3299
+ break;
3300
+ }
3301
+ return null;
3302
+ }
3303
+
3304
+ renderPrimitiveElement(val, childIndex) {
3305
+ const type = val.type;
3306
+ const tabName = this.state.formData[childIndex - 1] && this.state.formData[childIndex - 1].name ? this.state.formData[childIndex - 1].name : `${val.value} ${childIndex}`;
3307
+ const value = val.dynamicTab ? tabName : val.value;
3308
+ switch (type) {
3309
+ case "span":
3310
+ return <span id={`${val.id}${childIndex > 1 ? childIndex : ''}`}>{val.dynamicTab ? tabName : val.value}</span>;
3311
+ case "div":
3312
+ let children = _.get(this.state, `formData[${this.state.currentTab - 1}][${val.id}]`) || value;
3313
+ if (_.isEmpty(value) && !_.isEmpty(val.value)) {
3314
+ children = val.value;
3315
+ }
3316
+ const ref = _.get(this, `props.refs[${val.id}]`);
3317
+ return (<div
3318
+ ref={ref}
3319
+ className={val.className ? val.className : ''}
3320
+ id={`${val.id}${childIndex > 1 ? childIndex : ''}`}
3321
+ onClick={(data) => this.callChildEvent(data, val, val.submitAction)}
3322
+ style={val.style ? val.style : {}}
3323
+ onInput={(e) => { e.stopPropagation(); this.callChildEvent(e.target.textContent, val, 'onChange'); }}
3324
+ >{children}</div>);
3325
+ default:
3326
+ return '';
3327
+ }
3328
+ }
3329
+
3330
+ onGalleryClick = (event) => {
3331
+ event.stopPropagation();
3332
+ this.props.setDrawerVisibility(true);
3333
+ }
3334
+
3335
+ onReUpload = () => {
3336
+ const { formData } = this.state;
3337
+ this.props.onChange({
3338
+ ...formData,
3339
+ 0: {
3340
+ ...formData[0],
3341
+ image: undefined,
3342
+ imagePreview: undefined,
3343
+ },
3344
+ base: {
3345
+ ...formData.base,
3346
+ image: undefined,
3347
+ imagePreview: undefined,
3348
+ },
3349
+ reUpload: true,
3350
+ });
3351
+ }
3352
+
3353
+ getUploadVariants = (isImage, val) => {
3354
+ if (this.props.schema && this.props.schema.channel === 'LINE') {
3355
+ if (!isImage) {
3356
+ return (
3357
+ <>
3358
+ <CapUploader.CapDragger
3359
+ customRequest={(data) => this.capUploaderCustomRequest(data, val)}
3360
+ {...val.componentProps}
3361
+ className="form-builder-dragger"
3362
+ >
3363
+ <CapHeading className="dragger-title" type="h7">
3364
+ <FormattedMessage {...messages.dragAndDrop} />
3365
+ </CapHeading>
3366
+ <CapHeading className="dragger-or" type="label6">
3367
+ <FormattedMessage {...messages.or} />
3368
+ </CapHeading>
3369
+ <CapButton className="dragger-button" type="secondary" style={{marginRight: CAP_SPACE_08}}>
3370
+ <FormattedMessage {...messages.uploadComputer} />
3371
+ </CapButton>
3372
+ <CapButton
3373
+ className="dragger-button"
3374
+ type="secondary"
3375
+ style={{marginLeft: CAP_SPACE_08}}
3376
+ onClick={this.onGalleryClick}
3377
+ >
3378
+ <FormattedMessage {...messages.uploadGallery} />
3379
+ </CapButton>
3380
+ </CapUploader.CapDragger>
3381
+ <CapHeading type="h6" style={{marginTop: CAP_SPACE_12 }}>
3382
+ <FormattedMessage {...messages.imageDimenstionDescription} />
3383
+ </CapHeading>
3384
+ </>
3385
+ );
3386
+ }
3387
+ return (
3388
+ <CapButton
3389
+ className="dragger-button"
3390
+ type="link"
3391
+ style={{top: 0, position: 'absolute', right: 0, color: FONT_COLOR_05 }}
3392
+ onClick={this.onReUpload}
3393
+ >
3394
+ <FormattedMessage {...messages.imageReUpload} />
3395
+ </CapButton>
3396
+ );
3397
+ }
3398
+ return (
3399
+ <CapButton disabled={val.disabled ? val.disabled : false} type="link" prefix={<CapIcon size="s" type="add-photo" />} onClick={(e) => this.openFileDialog(e, val.id)} style={{float: 'right', color: `${val.disabled ? FONT_COLOR_04 : FONT_COLOR_05}`}}>
3400
+ {val.label}
3401
+ </CapButton>
3402
+ );
3403
+ }
3404
+
3405
+ checkBeeEditorAllowedForLibrary = () => {
3406
+ const { isFullMode = false, editor } = this.props || {};
3407
+ if ((editor === "BEE" && !isFullMode) || isFullMode) {
3408
+ return true;
3409
+ }
3410
+ return false;
3411
+ }
3412
+
3413
+ handleSetText = (val, newText) => {
3414
+ this.updateFormData(newText, val);
3415
+ };
3416
+
3417
+ renderMultiColSection(section, childIndex) {
3418
+ const fields = [];
3419
+ let contentSections = [];
3420
+ let popoverSections = [];
3421
+ const self = this;
3422
+ _.forEach(section.inputFields, (field) => {
3423
+ const columns = [];
3424
+ _.forEach(field.cols, (val) => {
3425
+ const type = val.type;
3426
+ const styling = val.styling;
3427
+ const isVersionEnable = (this.state.usingTabContainer && !val.standalone);
3428
+
3429
+
3430
+ if (!this.props.isNewVersionFlow && !val.standalone && !val.onlyDisplay && isVersionEnable && !this.state.formData[`${this.state.currentTab - 1}`]) {
3431
+ return true;
3432
+ }
3433
+ if (!val.standalone && !val.onlyDisplay &&
3434
+ (!this.state.formData[`${this.state.currentTab - 1}`] ||
3435
+ (!this.props.isNewVersionFlow && !this.state.formData[`${this.state.currentTab - 1}`] &&
3436
+ this.state.errorData[`${this.state.currentTab - 1}`][val.id] === null)) &&
3437
+ (this.props.isNewVersionFlow && !this.state.formData[`${this.state.currentTab - 1}`] &&
3438
+ this.state.errorData[`${this.state.currentTab - 1}`] && this.state.errorData[`${this.state.currentTab - 1}`][val.id] === null)) {
3439
+ return true;
3440
+ }
3441
+ if (!val.onlyDisplay && !val.standalone &&
3442
+ (!this.props.isNewVersionFlow && (!this.state.errorData[`${this.state.currentTab - 1}`] ||
3443
+ (!this.state.errorData[`${this.state.currentTab - 1}`] &&
3444
+ this.state.errorData[`${this.state.currentTab - 1}`][val.id] === null)))) {
3445
+ return true;
3446
+ }
3447
+
3448
+ let ifError = false;
3449
+ if (val.primitive) {
3450
+ if (val.id === "mark-final-version-label") {
3451
+ const baseTab = this.findBaseTab();
3452
+ if ((baseTab + 1) === childIndex) {
3453
+ return true;
3454
+ }
3455
+ }
3456
+ columns.push(
3457
+ <CapColumn style={val.colStyle ? val.colStyle : {}} offset={val.offset} key={val.id} span={val.width}>
3458
+ {this.renderPrimitiveElement(val, childIndex)}
3459
+ </CapColumn>
3460
+ );
3461
+ return true;
3462
+ }
3463
+ switch (type) {
3464
+ case "input":
3465
+ const errorType = isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id];
3466
+ const aiContentBotDisabled = isAiContentBotDisabled();
3467
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3468
+ const { TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
3469
+ const { formatMessage } = this.props.intl;
3470
+ const value = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id];
3471
+ // Show personalization error for title field inline (same as message textarea) when restrictPersonalization is true
3472
+ const hasPersonalizationError = this.props.restrictPersonalization && hasPersonalizationTags(value);
3473
+ if (hasPersonalizationError) {
3474
+ ifError = true;
3475
+ }
3476
+ let errorMessageText = hasPersonalizationError
3477
+ ? formatMessage(messages.personalizationTagsErrorMessage)
3478
+ : (errorType === TAG_BRACKET_COUNT_MISMATCH_ERROR ? formatMessage(globalMessages.unbalanacedCurlyBraces) : (val.errorMessage && ifError ? val.errorMessage : ''));
3479
+ if (styling === 'semantic') {
3480
+ const isEmailStandaloneHighFreq = val.standalone && this.props.schema?.channel?.toUpperCase() === EMAIL;
3481
+ if (isEmailStandaloneHighFreq) {
3482
+ columns.push(
3483
+ <CapColumn key={val.id} span={val.width} offset={val.offset} style={val.style || {}}>
3484
+ <HighFreqInput
3485
+ id={val.id}
3486
+ errorMessage={errorMessageText}
3487
+ label={val.label}
3488
+ inductiveText={val.inductiveText}
3489
+ className={`input-primary chart-name-input${ifError ? ' error' : ''}`}
3490
+ style={val.style ? val.style : {}}
3491
+ placeholder={val.placeholder}
3492
+ onCommit={(newValue) => this.performFormDataUpdate(newValue, val)}
3493
+ onBlur={(e) => this.handleFieldBlur(e, val)}
3494
+ value={value || ""}
3495
+ disabled={val.disabled}
3496
+ size={val.size || "default"}
3497
+ />
3498
+ {!aiContentBotDisabled && (
3499
+ <CapAskAira.ContentGenerationBot
3500
+ text={value || ""}
3501
+ setText={this.handleSetText.bind(this, val)}
3502
+ iconPlacement="float-br"
3503
+ iconSize="1.6rem"
3504
+ rootStyle={{
3505
+ bottom: "0.2rem",
3506
+ right: "0.2rem",
3507
+ left: "auto",
3508
+ }}
3509
+ />
3510
+ )}
3511
+ </CapColumn>
3512
+ );
3513
+ } else {
3514
+ columns.push(
3515
+ <CapColumn key={val.id} span={val.width} offset={val.offset} style={val.style || {}}>
3516
+ <CapInput
3517
+ id={val.id}
3518
+ errorMessage={errorMessageText}
3519
+ label={val.label}
3520
+ inductiveText={val.inductiveText}
3521
+ className={`input-primary chart-name-input${ifError ? ' error' : ''}`}
3522
+ // fluid={val.fluid}
3523
+ style={val.style ? val.style : {}}
3524
+ placeholder={val.placeholder}
3525
+ onChange={(e) => this.updateFormData(e.target.value, val)}
3526
+ onBlur={(e) => this.handleFieldBlur(e, val)}
3527
+ value={value || ""}
3528
+ defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3529
+ disabled={val.disabled}
3530
+ size={val.size || "default"}
3531
+ />
3532
+ {this.props.schema?.channel === EMAIL &&
3533
+ !aiContentBotDisabled && (
3534
+ <CapAskAira.ContentGenerationBot
3535
+ text={value || ""}
3536
+ setText={this.handleSetText.bind(this, val)}
3537
+ iconPlacement="float-br"
3538
+ iconSize="1.6rem"
3539
+ rootStyle={{
3540
+ bottom: "0.2rem",
3541
+ right: "0.2rem",
3542
+ left: "auto",
3543
+ }}
3544
+ />
3545
+ )}
3546
+ </CapColumn>
3547
+ );
3548
+ }
3549
+ }
3550
+ break;
3551
+
3552
+ case "textarea":
3553
+ const rows = 10;
3554
+ const cols = 2;
3555
+ this.renderTextAreaContent(styling, columns, val, isVersionEnable, rows, cols, val.offset);
3556
+ break;
3557
+ case "checkbox":
3558
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3559
+ const { disabled , hoverText } = val || {};
3560
+ if (styling === 'semantic') {
3561
+ columns.push(
3562
+ <CapColumn key="input" span={val.width} offset={val.offset}>
3563
+ <CapTooltip
3564
+ title={ disabled && hoverText ? hoverText : '' }
3565
+ placement="topLeft"
3566
+ >
3567
+ <CapCheckbox
3568
+ key={val.id}
3569
+ className={`${ifError ? 'error' : ''}`}
3570
+ errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
3571
+ onChange={(e) => this.updateFormData(e.target.checked, val, val.submitAction)}
3572
+ checked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3573
+ defaultChecked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3574
+ style={val.style ? val.style : {}}
3575
+ disabled={val.disabled}
3576
+ inductiveText={val.inductiveText}
3577
+ >
3578
+ {val.label}
3579
+ </CapCheckbox>
3580
+ </CapTooltip>
3581
+ </CapColumn>
3582
+ );
3583
+ }
3584
+ break;
3585
+ case "radio":
3586
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3587
+ if (styling === 'semantic') {
3588
+ columns.push(
3589
+ <CapColumn key={`input-${val.id}`} span={val.width} offset={val.offset}>
3590
+ <CapRadio
3591
+ className={`${ifError ? 'error' : ''}`}
3592
+ errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
3593
+ onChange={(e) => this.updateFormData(e.target.checked, val)}
3594
+ checked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3595
+ defaultChecked={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
3596
+ style={val.style ? val.style : {}}
3597
+ >
3598
+ {val.label}
3599
+ </CapRadio>
3600
+ </CapColumn>
3601
+ );
3602
+ }
3603
+ break;
3604
+ case 'radioGroup':
3605
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3606
+ if (styling === 'semantic') {
3607
+ columns.push(
3608
+ <CapColumn key={`input-${val.id}`} span={val.width} offset={val.offset}>
3609
+ <CapRadioGroup
3610
+ key={`${val.id}-radio-group`}
3611
+ className={`form-builder-radio-group ${ifError ? 'error' : ''}`}
3612
+ errorMessage={val.errorMessage && ifError ? val.errorMessage : ''}
3613
+ onChange={(e) => this.updateFormData(e.target.value, val)}
3614
+ style={val.style ? val.style : {}}
3615
+ value={this.handleSetRadioValue(this.state.formData, self.state.currentTab, val)}
3616
+ name={val.name ? val.name : `${val.id}-name`}
3617
+ disabled={val.disabled}
3618
+ >
3619
+ {val.options.map((option, index) => {
3620
+ const isMappedTemplateWechat = val.id === "template-redirect-options-radio";
3621
+ return (
3622
+ <CapRadio
3623
+ value={ isMappedTemplateWechat ? option.value : option }
3624
+ inductiveText={val.inductiveText && val.inductiveText[index]}>{ isMappedTemplateWechat ? option.label : option }
3625
+ </CapRadio>
3626
+ )
3627
+ }
3628
+ )}
3629
+
3630
+ </CapRadioGroup>
3631
+ {/*{ifError ?*/}
3632
+ {/*<div className="error"> {val.errorMessage}</div>*/}
3633
+ {/*:*/}
3634
+ {/*""*/}
3635
+ {/*}*/}
3636
+ </CapColumn>
3637
+ );
3638
+ }
3639
+ break;
3640
+ case 'image':
3641
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3642
+ columns.push(
3643
+ <CapColumn key={`input-${val.id}${self.state.currentTab - 1}`} style={val.style} span={val.width} offset={val.offset}>
3644
+ <div>
3645
+ <div className={`image-container ${ifError ? 'error' : ''}`}>
3646
+ {self.state && !!self.state.formData[`${self.state.currentTab - 1}`].image ?
3647
+ <CapImage src={self.state.formData[`${self.state.currentTab - 1}`].image} alt={val.alt} style={val.style}/>
3648
+ :
3649
+ <div style={{width: "100%", textAlign: "center"}}><span className="image-placeholder">{val.placeholder}</span></div>
3650
+ }
3651
+ </div>
3652
+ {ifError && <span className="error">{val.errorMessage}</span>}
3653
+ </div>
3654
+ </CapColumn>
3655
+ );
3656
+ break;
3657
+ case UPLOAD:
3658
+ const {
3659
+ formData: {
3660
+ [`${self.state.currentTab - 1}`]: {
3661
+ image,
3662
+ } = {},
3663
+ } = {},
3664
+ } = self.state;
3665
+ const isImage = !!image;
3666
+ ifError = (this.state.checkValidation || this.props.startValidation) && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3667
+ const ImageComponent = (props) => (
3668
+ <div key={`${val.id}-${this.state.currentTab}`}>
3669
+ <div className={`image-container ${props.ifError ? 'error' : ''}`}>
3670
+
3671
+ {isImage ?
3672
+ <CapImage src={self.state.formData[`${self.state.currentTab - 1}`].image} alt={props.alt} style={props.style}/>
3673
+ :
3674
+ <div style={{width: "100%", textAlign: "center"}}><span className="image-placeholder">{props.placeholder}</span></div>
3675
+ }
3676
+ </div>
3677
+ </div>
3678
+ );
3679
+ const WithLabel = LabelHOC(ImageComponent);
3680
+ const {errorMessage, ...rest} = val.previewProps || {};
3681
+ columns.push(
3682
+ <CapColumn span={val.width} offset={val.offset} style={val.style}>
3683
+ {val.showPreview && <WithLabel key={`${val.id}-with-label`} {...rest} errorMessage={ifError && errorMessage} ifError={ifError}/>}
3684
+ <form encType="multipart/form-data" id={val.id}>
3685
+ <input key={val.id} style={{ display: 'none'}} id="fileName" type="file" onChange={(e) => this.uploadImages(e, {files: e.target.files}, val)} accept={val.supportedExtensions ? val.supportedExtensions : "image/*"} />
3686
+ {this.getUploadVariants(isImage, val)}
3687
+ </form>
3688
+ </CapColumn>
3689
+ );
3690
+ break;
3691
+ case 'capUpload':
3692
+ columns.push(
3693
+ <CapUploader
3694
+ customRequest={(data) => this.capUploaderCustomRequest(data, val)}
3695
+ >
3696
+ <CapButton>
3697
+ <FormattedMessage {...messages.upload} />
3698
+ </CapButton>
3699
+ </CapUploader>
3700
+ );
3701
+ break;
3702
+
3703
+ case 'capDragger':
3704
+ columns.push(
3705
+ <CapUploader.CapDragger
3706
+ customRequest={(data) => this.capUploaderCustomRequest(data, val)}
3707
+ {...val.componentProps}
3708
+ className="form-builder-dragger"
3709
+ >
3710
+ <CapHeading className="dragger-title" type="h7">
3711
+ <FormattedMessage {...messages.dragAndDrop} />
3712
+ </CapHeading>
3713
+ <CapHeading className="dragger-or" type="label6">
3714
+ <FormattedMessage {...messages.or} />
3715
+ </CapHeading>
3716
+ <CapButton className="dragger-button" type="secondary">
3717
+ <FormattedMessage {...messages.selectAFile} />
3718
+ </CapButton>
3719
+ </CapUploader.CapDragger>
3720
+ );
3721
+ break;
3722
+
3723
+ case "table":
3724
+ columns.push(
3725
+ <CapTable dataSource={self.props.iosCtasData} loading={self.props.iosCtasData ? !self.props.iosCtasData.length : true} onRowClick={(row) => this.updateFormData(row, val, "onRowClick" )} pagination={val.pagination}>
3726
+ {_.map(val.columns, (col) => <Column {...col}/>)}
3727
+ </CapTable>
3728
+ );
3729
+ break;
3730
+ case "tag-list":
3731
+ let moduleFilterEnabled = this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded';
3732
+ const channel = _.get(this.props, 'schema.channel', "");
3733
+ if (channel === 'EMAIL') {
3734
+ moduleFilterEnabled = this.props.isFullMode;
3735
+ }
3736
+ const langIndex = 0;
3737
+ const currentLang = (!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex] : this.props.baseLanguage;
3738
+ const isBEEAppEnable = this.checkBeeEditorAllowedForLibrary();
3739
+
3740
+ // Always render Subject TagList (title-tagList) even when Bee editor is active for EMAIL channel
3741
+ if (
3742
+ val.id === 'title-tagList' ||
3743
+ !(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLang}].is_drag_drop`, false)) ||
3744
+ isBEEAppEnable === false ||
3745
+ channel !== 'EMAIL'
3746
+ ) {
3747
+ columns.push(
3748
+ <CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
3749
+ <TagList
3750
+ key={`input-${val.id}`}
3751
+ moduleFilterEnabled={moduleFilterEnabled}
3752
+ label={val.label ? val.label : ''}
3753
+ onTagSelect={(data) => this.callChildEvent(data, val, 'onTagSelect')}
3754
+ onContextChange={this.props.onContextChange}
3755
+ location={this.props.location}
3756
+ tags={this.props.tags ? this.props.tags : []}
3757
+ injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
3758
+ className={val.className ? val.className : ''}
3759
+ id={val.id}
3760
+ userLocale={this.state.translationLang}
3761
+ selectedOfferDetails={this.props.selectedOfferDetails}
3762
+ channel={channel}
3763
+ eventContextTags={this.props?.eventContextTags}
3764
+ restrictPersonalization={this.props.restrictPersonalization}
3765
+ waitEventContextTags={this.props?.waitEventContextTags}
3766
+ />
3767
+ </CapColumn>
3768
+ );
3769
+ }
3770
+ break;
3771
+ case "cap-tag-list-with-input":
3772
+ let moduleFilterEnabledForCapTagList = this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded';
3773
+ const channelForCapTagList = _.get(this.props, 'schema.channel', "");
3774
+ if (channelForCapTagList === 'EMAIL') {
3775
+ moduleFilterEnabledForCapTagList = this.props.isFullMode;
3776
+ }
3777
+ const langIndexForCapTagList = 0;
3778
+ const currentLangForCapTagList = (!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndexForCapTagList]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndexForCapTagList] : this.props.baseLanguage;
3779
+ const isBEEAppEnableForCapTagList = this.checkBeeEditorAllowedForLibrary();
3780
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3781
+
3782
+ // Always render Subject CapTagListWithInput even when Bee editor is active for EMAIL channel
3783
+ if (
3784
+ val.id === 'template-subject' ||
3785
+ !(_.get(this.state, `formData[${this.state.currentTab - 1}][${currentLangForCapTagList}].is_drag_drop`, false)) ||
3786
+ isBEEAppEnableForCapTagList === false ||
3787
+ channelForCapTagList !== 'EMAIL'
3788
+ ) {
3789
+ const isEmailStandaloneSubject = val.standalone && channelForCapTagList === EMAIL && val.id === 'template-subject';
3790
+ const tagListProps = {
3791
+ key: `input-${val.id}`,
3792
+ inputId: val.id,
3793
+ inputValue: this.state.formData[val.id] || '',
3794
+ inputPlaceholder: val.placeholder || '',
3795
+ inputErrorMessage: val.errorMessage && ifError ? val.errorMessage : '',
3796
+ inputRequired: val.required || false,
3797
+ inputDisabled: val.disabled || false,
3798
+ headingText: val.label || '',
3799
+ headingStyle: val.headingStyle || { marginTop: '3%', marginRight: '79%' },
3800
+ headingType: "h4",
3801
+ onTagSelect: (data) => this.callChildEvent(data, val, 'onTagSelect'),
3802
+ onContextChange: this.props.onContextChange,
3803
+ location: this.props.location,
3804
+ tags: this.props.tags ? this.props.tags : [],
3805
+ injectedTags: this.props.injectedTags ? this.props.injectedTags : {},
3806
+ className: val.className ? val.className : '',
3807
+ userLocale: this.state.translationLang,
3808
+ selectedOfferDetails: this.props.selectedOfferDetails,
3809
+ eventContextTags: this.props?.eventContextTags,
3810
+ waitEventContextTags: this.props?.waitEventContextTags,
3811
+ moduleFilterEnabled: moduleFilterEnabledForCapTagList,
3812
+ containerStyle: val.style || {},
3813
+ inputProps: val.inputProps || {},
3814
+ showInput: val.showInput !== false,
3815
+ showTagList: val.showTagList !== false,
3816
+ restrictPersonalization: this.props.restrictPersonalization,
3817
+ };
3818
+ columns.push(
3819
+ <CapColumn key={`input-${val.id}`} offset={val.offset} span={val.width ? val.width : ''} style={val.style ? val.style : {marginBottom: '16px'}}>
3820
+ {isEmailStandaloneSubject ? (
3821
+ <HighFreqTagInput
3822
+ {...tagListProps}
3823
+ onCommit={(newValue) => this.performFormDataUpdate(newValue, val)}
3824
+ />
3825
+ ) : (
3826
+ <CapTagListWithInput
3827
+ {...tagListProps}
3828
+ inputOnChange={(e) => this.updateFormData(e.target.value, val)}
3829
+ />
3830
+ )}
3831
+ </CapColumn>
3832
+ );
3833
+ }
3834
+ break;
3835
+ case "button":
3836
+ if (styling === 'semantic' && val.metaType === 'submit-button') {
3837
+ columns.push(
3838
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} offset={val.offset}>
3839
+ <CapButton
3840
+ type={val.buttonType}
3841
+ icon={val.icon}
3842
+ disabled={val.disabled ? val.disabled : false}
3843
+ onClick={(data) => this.callChildEvent(data, val, val.submitAction)}>
3844
+ {val.value}
3845
+ </CapButton>
3846
+ </CapColumn>
3847
+ );
3848
+ }
3849
+ break;
3850
+
3851
+ case "sms-preview": {
3852
+ let content = "";
3853
+ if (this.state.usingTabContainer && !this.state.formData[`${this.state.currentTab - 1}`]) {
3854
+ return true;
3855
+ }
3856
+ content = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][`sms-editor${this.state.currentTab > 1 ? this.state.currentTab : ''}`] : this.state.formData[val.id];
3857
+ const unicodeEnabled = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][`unicode-validity${this.state.currentTab > 1 ? this.state.currentTab : ''}`] : this.state.formData['unicode-validity'];
3858
+ columns.push(
3859
+ <CapColumn key="input" span={23} offset={1}>
3860
+ <UnifiedPreview
3861
+ key={val.id}
3862
+ style={val.customStyling ? val.customStyling : {}}
3863
+ channel={val.channel ? val.channel : 'SMS'}
3864
+ content={content}
3865
+ device={ANDROID}
3866
+ showDeviceToggle={false}
3867
+ showHeader={false}
3868
+ formatMessage={this.props.intl.formatMessage}
3869
+ senderId={unicodeEnabled ? 'Unicode' : 'ASCII'}
3870
+ />
3871
+ </CapColumn>
3872
+ );
3873
+ }
3874
+ break;
3875
+ case "line-preview": {
3876
+ const content = {
3877
+ bodyText: isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][`message-editor${this.state.currentTab > 1 ? this.state.currentTab : ''}`] : this.state.formData[val.id],
3878
+ bodyImage: this.state.formData[`${this.state.currentTab - 1}`].imagePreview,
3879
+ };
3880
+ if (!this.state.formData[`${this.state.currentTab - 1}`]) {
3881
+ return true;
3882
+ }
3883
+
3884
+ columns.push(
3885
+ <CapColumn key="input" span={23} offset={1}>
3886
+ <TemplatePreview
3887
+ key={val.id}
3888
+ style={val.customStyling ? val.customStyling : {}}
3889
+ channel={val.channel}
3890
+ content={content}
3891
+ >
3892
+ </TemplatePreview>
3893
+ </CapColumn>
3894
+ );
3895
+ }
3896
+ break;
3897
+ case "mobile-push-preview":
3898
+ if (!this.state.formData[`${this.state.currentTab - 1}`]) {
3899
+ return true;
3900
+ }
3901
+ // Transform FormBuilder content format to UnifiedPreview format
3902
+ // MobilePushPreviewContent expects: { androidContent: { header, bodyText, bodyImage, actions }, iosContent: {...} }
3903
+ const headerValue = this.state.formData[`${this.state.currentTab - 1}`][val.content.title] || '';
3904
+ const bodyTextValue = this.state.formData[`${this.state.currentTab - 1}`][val.content.message] || '';
3905
+ const bodyImageValue = this.state.formData[`${this.state.currentTab - 1}`].image || '';
3906
+ const actionsArray = [];
3907
+ if (this.state.formData[`${this.state.currentTab - 1}`][val.content.secondaryCta1]) {
3908
+ actionsArray.push({label: this.state.formData[`${this.state.currentTab - 1}`][val.content.secondaryCta1]});
3909
+ }
3910
+ if (this.state.formData[`${this.state.currentTab - 1}`][val.content.secondaryCta2]) {
3911
+ actionsArray.push({label: this.state.formData[`${this.state.currentTab - 1}`][val.content.secondaryCta2]});
3912
+ }
3913
+ const mobilePushContent = {
3914
+ androidContent: {
3915
+ header: headerValue,
3916
+ bodyText: bodyTextValue,
3917
+ bodyImage: bodyImageValue,
3918
+ actions: actionsArray,
3919
+ appName: val.content.appName || '',
3920
+ },
3921
+ iosContent: {
3922
+ header: headerValue,
3923
+ bodyText: bodyTextValue,
3924
+ bodyImage: bodyImageValue,
3925
+ actions: actionsArray,
3926
+ appName: val.content.appName || '',
3927
+ },
3928
+ };
3929
+ columns.push(
3930
+ <CapColumn key="input" span={23} offset={1}>
3931
+ <UnifiedPreview
3932
+ key={val.id}
3933
+ style={val.customStyling ? val.customStyling : {}}
3934
+ channel={val.channel || 'MOBILEPUSH'}
3935
+ content={mobilePushContent}
3936
+ device={ANDROID}
3937
+ showDeviceToggle={false}
3938
+ showHeader={false}
3939
+ formatMessage={this.props.intl.formatMessage}
3940
+ />
3941
+ </CapColumn>
3942
+ );
3943
+ break;
3944
+ case "popover":
3945
+ contentSections = val.content.sections.map( (contentSection) => {
3946
+ const result = this.renderSection(contentSection, childIndex);
3947
+ return result;
3948
+ });
3949
+ popoverSections = val.value.sections.map( (popoverSection) => {
3950
+ const result = this.renderSection(popoverSection, childIndex);
3951
+ return result;
3952
+ });
3953
+ columns.push(
3954
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} offset={val.offset}>
3955
+ <CapPopover
3956
+ trigger={val.trigger}
3957
+ placement={val.placement}
3958
+ visible={this.state.popoverVisible && (this.props.isNewVersionFlow || this.state.currentTab === childIndex || !childIndex)}
3959
+ onVisibleChange={this.handleVisibleChange}
3960
+ content={
3961
+ _.forEach(contentSections, (contentSection) => {
3962
+ if (contentSection) {
3963
+ return contentSection;
3964
+ }
3965
+ return contentSection;
3966
+ })
3967
+ }
3968
+ >
3969
+ {
3970
+ _.forEach(popoverSections, (popoverSection) => {
3971
+ if (popoverSection) {
3972
+ return popoverSection;
3973
+ }
3974
+ return popoverSection;
3975
+ })
3976
+ }
3977
+ </CapPopover>
3978
+ </CapColumn>
3979
+ );
3980
+ break;
3981
+
3982
+ case "icon":
3983
+ if (val.id === "base-version-icon") {
3984
+ const baseTab = this.findBaseTab();
3985
+ if ((baseTab + 1) !== childIndex) {
3986
+ return true;
3987
+ }
3988
+ }
3989
+ columns.push(
3990
+ <CapColumn
3991
+ key={`icon-${val.id}`}
3992
+ id={val.id}
3993
+ offset={val.offset}
3994
+ width={val.width ? val.width : 1}
3995
+ onClick={(data) => this.callChildEvent(data, val, val.submitAction)}
3996
+ className={val.className ? val.className : ''}
3997
+ style={val.colStyle ? val.colStyle : {}}
3998
+ >
3999
+ <i style={val.style ? val.style : {}} className="material-icons">{val.value ? val.value : '&#xE313;'}</i>
4000
+ </CapColumn>
4001
+ );
4002
+ break;
4003
+ case "select": {
4004
+ ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
4005
+ const options = (this.state.formData[`${val.id}-options`]) ? (this.state.formData[`${val.id}-options`]) : val.options;
4006
+ columns.push(
4007
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} offset={val.offset}>
4008
+ <CapSelect
4009
+ id={val.id}
4010
+ options={options}
4011
+ placeholder={val.placeholder ? val.placeholder : ''}
4012
+ style={val.style ? val.style : {}}
4013
+ onSelect={(data) => this.updateFormData(data, val, 'onSelect')}
4014
+ value={isVersionEnable ? this.state.formData[this.state.currentTab - 1][val.id] : this.state.formData[val.id]}
4015
+ disabled={val.disabled}
4016
+ label={val.label}
4017
+ />
4018
+ {ifError && val.errorMessage &&
4019
+ <span className={'error'}>{val.errorMessage}</span>
4020
+ }
4021
+ </CapColumn>
4022
+ );
4023
+ }
4024
+ break;
4025
+ case "ckeditor": {
4026
+ if (this.props.schema && (this.props.schema.channel || '').toLowerCase() === 'wechat' ) {
4027
+ const content = !_.isEmpty(this.state.formData) && this.state.formData['content-value'] ? this.state.formData['content-value'] : '';
4028
+ columns.push(
4029
+ <CKEditor
4030
+ id={val.id}
4031
+ content={content}
4032
+ events={val.events}
4033
+ getEditorInstanse={(evt, id) => val.injectedEvents.getEditorInstanse.call(this, evt, id)}
4034
+ handleContentChange={(event) => this.callChildEvent(event, val, 'onContentChange')}
4035
+ channel="wechat"
4036
+ onFocusOut={(event) => this.callChildEvent(event, val, 'onFocusOut')}
4037
+ onFocusIn={(event) => this.callChildEvent(event, val, 'onFocusIn')}
4038
+ currentOrgDetails={this.props.currentOrgDetails}
4039
+ /> );
4040
+ } else {
4041
+ if (_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`])) {
4042
+ return true;
4043
+ }
4044
+ let langIndex = 0;
4045
+ if (val.id.indexOf('template-content') > -1 && val.id !== 'template-content') {
4046
+ langIndex = parseInt(val.id.replace('template-content', ''), 10) - 1;
4047
+ }
4048
+
4049
+ const currentLang = ((!_.isEmpty(this.state.formData) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`]) && !_.isEmpty(this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages) && this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex]) ? this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages[langIndex] : this.props.baseLanguage) || 'en';
4050
+ const content = (!_.isEmpty(this.state.formData) && this.state.formData[`${this.state.currentTab - 1}`] && this.state.formData[`${this.state.currentTab - 1}`][currentLang] && this.state.formData[`${this.state.currentTab - 1}`][currentLang]['template-content'] || '');
4051
+ columns.push(
4052
+ <CapColumn
4053
+ style={val.colStyle ? val.colStyle : {border : ""}}
4054
+ span={val.width}
4055
+ className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}
4056
+ >
4057
+ <CKEditor
4058
+ id={val.id}
4059
+ content={content}
4060
+ events={val.events}
4061
+ getEditorInstanse={(evt, id) => val.injectedEvents.getEditorInstanse.call(this, evt, id)}
4062
+ handleContentChange={(event) => this.callChildEvent(event, val, 'onContentChange')}
4063
+ currentOrgDetails={this.props.currentOrgDetails}
4064
+ />
4065
+ </CapColumn>
4066
+ );
4067
+ }
4068
+ }
4069
+ break;
4070
+ case "BEEeditor": {
4071
+ let langTab = 1;
4072
+ if (val.id.match(/BEEeditor/g)) {
4073
+ if (val.id.length > 9 && val.id.charAt(9)) {
4074
+ langTab = val.id.charAt(9);
4075
+ }
4076
+ }
4077
+ const { currentTab, formData } = this.state;
4078
+ const supportedLanguages = (formData[currentTab - 1] || {}).selectedLanguages || {};
4079
+ const currentLang = supportedLanguages[langTab - 1];
4080
+
4081
+ let beeJson = '',
4082
+ beeToken = '',
4083
+ uuid = '';
4084
+ const data = formData[`${currentTab - 1}`][currentLang];
4085
+ if (data) {
4086
+ beeJson = data[`BEEeditor${currentTab > 1 ? currentTab : ''}json`];
4087
+ beeToken = data[`BEEeditor${currentTab > 1 ? currentTab : ''}token`];
4088
+ uuid = this.props.uuid;
4089
+ }
4090
+ if (!beeJson || !beeToken) {
4091
+ return false;
4092
+ }
4093
+ let isModuleFilterEnabled = _.get(this.props.location, 'query.type', '') !== 'embedded';
4094
+ const schemaChannel = _.get(this.props.schema, 'channel', "");
4095
+ if (schemaChannel === 'EMAIL') {
4096
+ isModuleFilterEnabled = this.props.isFullMode;
4097
+ }
4098
+ columns.push(
4099
+ <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" : ""}>
4100
+ <BeeEditor
4101
+ uid={uuid}
4102
+ tokenData={beeToken}
4103
+ id={val.id}
4104
+ beeJson={beeJson}
4105
+ showTagsPopover={this.showTagsPopover}
4106
+ location={this.props.location}
4107
+ label={val.label || ''}
4108
+ className={val.className || ''}
4109
+ userLocale={this.props.userLocale}
4110
+ selectedOfferDetails={this.props.selectedOfferDetails}
4111
+ tags={this.props.tags || []}
4112
+ injectedTags={this.props.injectedTags ? this.props.injectedTags : {}}
4113
+ saveBeeInstance={this.props.saveBeeInstance}
4114
+ saveBeeData={this.props.saveBeeData}
4115
+ onContextChange={this.props.onContextChange}
4116
+ moduleFilterEnabled={isModuleFilterEnabled}
4117
+ eventContextTags={this.props?.eventContextTags}
4118
+ waitEventContextTags={this.props?.waitEventContextTags}
4119
+ isGetBeeData={this.props?.isGetBeeData}
4120
+ getBEEData={this.props?.getBEEData}
4121
+ />
4122
+ </CapColumn>
4123
+ );
4124
+ }
4125
+ break;
4126
+ case "customPopover": {
4127
+ if (!this.state.formData[`${this.state.currentTab - 1}`]) {
4128
+ return true;
4129
+ }
4130
+ columns.push(
4131
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width}>
4132
+ <CustomPopOver
4133
+ id={val.id}
4134
+ popOverList={this.props.supportedLanguages}
4135
+ excludeList={this.state.formData[`${this.state.currentTab - 1}`].selectedLanguages}
4136
+ handlePopOverClick={(data) => this.handleAddLanguageFlow(data, val, ADD_LANGUAGE)}
4137
+ visible={this.state.customPopoverVisible}
4138
+ onVisibleChange={this.handleCustomPopoverVisibleChange}
4139
+ />
4140
+ </CapColumn>
4141
+ );
4142
+ }
4143
+ break;
4144
+ default:
4145
+ break;
4146
+ }
4147
+ return true;
4148
+ });
4149
+ const row = (
4150
+ <CapRow useLegacy style={field.rowStyle ? field.rowStyle : {}} className={field.rowClassName ? field.rowClassName : ''}>
4151
+ {
4152
+ _.forEach(columns, (value) => {
4153
+ if (value) {
4154
+ return value;
4155
+ }
4156
+ return value;
4157
+ })
4158
+ }
4159
+ </CapRow>
4160
+ );
4161
+ if (columns.length) {
4162
+ fields.push(row);
4163
+ }
4164
+ });
4165
+
4166
+ _.forEach(section.actionFields, (field) => {
4167
+ const columns = [];
4168
+ _.forEach(field.cols, (val) => {
4169
+ const type = val.type;
4170
+ const styling = val.styling;
4171
+ switch (type) {
4172
+ case "button":
4173
+ if (styling === 'semantic' && val.metaType === 'submit-button') {
4174
+ columns.push(
4175
+ <CapColumn span={3}>
4176
+ <CapButton
4177
+ type={val.buttonType}
4178
+ icon={val.icon}
4179
+ disabled={val.disabled ? val.disabled : false}
4180
+ onClick={(data) => val.id === 'save-button' ? this.saveForm() : this.callChildEvent(data, val, val.submitAction)}>
4181
+ {val.value}
4182
+ </CapButton>
4183
+ </CapColumn>
4184
+ );
4185
+ }
4186
+ break;
4187
+ default:
4188
+ break;
4189
+ }
4190
+ });
4191
+ const row = (
4192
+ <CapRow useLegacy>
4193
+ {
4194
+ _.forEach(columns, (value) => {
4195
+ if (value) {
4196
+ return value;
4197
+ }
4198
+ return value;
4199
+ })
4200
+ }
4201
+ </CapRow>
4202
+ );
4203
+ if (columns.length) {
4204
+ fields.push(row);
4205
+ }
4206
+ });
4207
+ if (!this.props.isEmailLoading && this.props.schema.channel.toUpperCase() === EMAIL && !fields.length) {
4208
+ return (
4209
+ <div style={{ textAlign: 'center' }}>
4210
+ <CapSpin spinning />
4211
+ </div>
4212
+ );
4213
+ }
4214
+ return fields;
4215
+ }
4216
+
4217
+ //To Find from which locale user is loggedin
4218
+ getTranslationMappedLocale(locale) {
4219
+ return GET_TRANSLATION_MAPPED?.[locale];
4220
+ }
4221
+ componentDidMount() {
4222
+ const user = localStorage.getItem('user');
4223
+ let locale = 'en';
4224
+ if (user) {
4225
+ locale = JSON.parse(user).lang || locale;
4226
+ }
4227
+ locale = this.getTranslationMappedLocale(locale);
4228
+ moment.locale(locale);
4229
+ this.setState({
4230
+ translationLang: locale,
4231
+ });
4232
+ }
4233
+
4234
+ renderContainer(container) {
4235
+ const containerType = container.type;
4236
+ const renderedPanes = [];
4237
+ let renderedSections = [];
4238
+ let renderedContentSections = [];
4239
+ let paneIndex = 1;
4240
+ let panes = [];
4241
+ let result = [];
4242
+ let extraActionSections = [];
4243
+ let activeTabKey;
4244
+ let paneTabKey;
4245
+ let stateTabKey = '';
4246
+ switch (containerType) {
4247
+ case "tabs":
4248
+ panes = container.panes;
4249
+ _.forEach(panes, (pane, index) => {
4250
+ renderedSections = pane.sections.map((section) => {
4251
+ result = this.renderSection(section, paneIndex);
4252
+ return result;
4253
+ });
4254
+
4255
+ const sectionHeadeers = pane.sectionsHeaders ? pane.sectionsHeaders : container.tabContent && container.tabContent.sections;
4256
+ renderedContentSections = sectionHeadeers.map( (tabContentSection) => {
4257
+ result = this.renderSection(tabContentSection, paneIndex);
4258
+ return result;
4259
+ });
4260
+ // let newTabKey = `${_.uniqueId()}`;
4261
+ if (!this.state.formData[this.state.currentTab - 1]) {
4262
+ return;
4263
+ }
4264
+ activeTabKey = this.state.formData.tabKey;
4265
+ if (this.props.isNewVersionFlow) {
4266
+ activeTabKey = this.state.tabKey;
4267
+ }
4268
+ paneTabKey = this.state.formData[paneIndex - 1] && this.state.formData[paneIndex - 1].tabKey;
4269
+ if (this.props.isNewVersionFlow) {
4270
+ const currentLang = this.state.formData[this.state.currentTab - 1].selectedLanguages[index];
4271
+ paneTabKey = this.state.formData[this.state.currentTab - 1] && this.state.formData[this.state.currentTab - 1][currentLang] && this.state.formData[this.state.currentTab - 1][currentLang].tabKey;
4272
+ }
4273
+ const isPaneSupported = typeof pane.isSupported === "boolean" ? pane.isSupported : true; // pane.isSupported field exists only in mpush schema, in other channels it is not
4274
+ if (isPaneSupported) {
4275
+ renderedPanes.push({
4276
+ forceRender: true,
4277
+ tab: (
4278
+ <div className="form-tab-header" onInput={(e) => e.stopPropagation()}>
4279
+ { this.state.translationLang !== 'ja-JP' && (
4280
+ _.forEach(renderedContentSections, (value) => {
4281
+ if (value) {
4282
+ return value;
4283
+ }
4284
+ return value;
4285
+ }))
4286
+ }
4287
+ </div>
4288
+ ),
4289
+ key: paneTabKey && paneTabKey !== '' ? `${paneTabKey}` : index,
4290
+ content: (
4291
+ _.forEach(renderedSections, (value) => {
4292
+ if (value) {
4293
+ return value;
4294
+ }
4295
+ return value;
4296
+ })
4297
+ ),
4298
+ });
4299
+ paneIndex += 1;
4300
+ }
4301
+ });
4302
+ extraActionSections = container.tabBarExtraContent && container.tabBarExtraContent.sections ? container.tabBarExtraContent.sections.map((section) => {
4303
+ result = this.renderSection(section);
4304
+ return result;
4305
+ }) : {};
4306
+ stateTabKey = this.state.tabKey;
4307
+ if (stateTabKey && stateTabKey !== '') {
4308
+ activeTabKey = stateTabKey;
4309
+ }
4310
+ return (
4311
+ <CapTab
4312
+ className={`cap-tabs-${container.id}`}
4313
+ activeKey={activeTabKey && activeTabKey !== '' ? `${activeTabKey}` : '1'}
4314
+ defaultActiveKey={activeTabKey && activeTabKey !== '' ? `${activeTabKey}` : '1'}
4315
+ onChange={(data, e) => { this.callChildEvent(data, container, 'onTabChange', e); }}
4316
+ onEdit={(data, e) => this.onEdit(data, e)}
4317
+ style={{width: '100%'}}
4318
+ tabBarExtraContent={
4319
+ !_.isEmpty(extraActionSections) && _.forEach(extraActionSections, (value) => {
4320
+ if (value) {
4321
+ return value;
4322
+ }
4323
+ return value;
4324
+ })
4325
+ }
4326
+ panes={renderedPanes}
4327
+ />
4328
+ );
4329
+
4330
+ default:
4331
+ break;
4332
+ }
4333
+ return null;
4334
+ }
4335
+
4336
+ renderForm() {
4337
+ const schema = this.props.schema;
4338
+ const allElements = [];
4339
+ let renderedStandAloneSections;
4340
+ let renderedContainers;
4341
+ if (schema.standalone) {
4342
+ const sections = schema.standalone.sections;
4343
+ renderedStandAloneSections = sections.map((section) => {
4344
+ const result = this.renderSection(section);
4345
+ return result;
4346
+ });
4347
+ allElements.push(renderedStandAloneSections);
4348
+ }
4349
+ if (schema.containers) {
4350
+ renderedContainers = schema.containers.map((container) => {
4351
+ if (container.isActive === undefined || (container.isActive)) {
4352
+ const result = this.renderContainer(container);
4353
+ return result;
4354
+ }
4355
+ return null;
4356
+ });
4357
+
4358
+ allElements.push(renderedContainers);
4359
+ }
4360
+ const modal = this.getModal();
4361
+ allElements.push(modal);
4362
+ return allElements;
4363
+ }
4364
+
4365
+ render() {
4366
+ const cmsTemplateSelectionContent = (
4367
+ <CardGrid
4368
+ className={""}
4369
+ listItem={this.populateTemplatesList(this.props.cmsTemplates)}
4370
+ // filterContent={filterContent}
4371
+ onHoverItem={this.handleOnHoverItem}
4372
+ onItemClick={this.props.handleEdmDefaultTemplateSelection}
4373
+ colNumber={2}
4374
+ >
4375
+ </CardGrid>
4376
+ );
4377
+
4378
+
4379
+ return (
4380
+ <CapSpin spinning={Boolean((this.isLiquidFlowSupportedByChannel() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
4381
+ <CapRow useLegacy>
4382
+ {this.props.schema && this.renderForm()}
4383
+ <SlideBox
4384
+ header={
4385
+ <h3>{this.props.intl.formatMessage(messages.layoutSelection)}</h3>
4386
+ }
4387
+ width={60}
4388
+ content={cmsTemplateSelectionContent}
4389
+ show={this.props.showEdmEmailTemplates}
4390
+ handleClose={this.props.toggleEdmEmailTemplateSelection}
4391
+ loadingText={this.props.intl.formatMessage(
4392
+ messages.loadingEDMTemplates
4393
+ )}
4394
+ isLoading={this.props.getCmsTemplatesInProgress}
4395
+ />
4396
+ {this.props.capDrawerContent && (
4397
+ <CapDrawer
4398
+ content={this.props.capDrawerContent}
4399
+ visible={this.state.isDrawerVisible}
4400
+ width={380}
4401
+ onClose={() => this.props.setDrawerVisibility(false)}
4402
+ />
4403
+ )}
4404
+ </CapRow>
4405
+ </CapSpin>
4406
+ );
4407
+ }
4408
+ }
4409
+
4410
+ FormBuilder.defaultProps = {
4411
+ isNewVersionFlow: false,
4412
+ userLocale: localStorage.getItem('jlocale') || 'en',
4413
+ showLiquidErrorInFooter: () => {},
4414
+ metaDataStatus: "",
4415
+ waitEventContextTags: {},
4416
+ isTestAndPreviewMode: false, // Default to false to maintain existing behavior
4417
+ };
4418
+
4419
+ FormBuilder.propTypes = {
4420
+ schema: PropTypes.object.isRequired,
4421
+ onSubmit: PropTypes.func.isRequired,
4422
+ onChange: PropTypes.func.isRequired,
4423
+ currentTab: PropTypes.number.isRequired,
4424
+ parent: PropTypes.object.isRequired,
4425
+ formData: PropTypes.object.isRequired,
4426
+ location: PropTypes.object.isRequired,
4427
+ tabKey: PropTypes.string.isRequired,
4428
+ tags: PropTypes.array.isRequired,
4429
+ tagModule: PropTypes.string.isRequired,
4430
+ injectedTags: PropTypes.object.isRequired,
4431
+ onFormValidityChange: PropTypes.func.isRequired,
4432
+ handleCancelModal: PropTypes.func.isRequired,
4433
+ usingTabContainer: PropTypes.bool.isRequired,
4434
+ checkValidation: PropTypes.bool.isRequired,
4435
+ onContextChange: PropTypes.func.isRequired,
4436
+ tabCount: PropTypes.number.isRequired,
4437
+ isNewVersionFlow: PropTypes.bool.isRequired,
4438
+ modal: PropTypes.object.isRequired,
4439
+ showModal: PropTypes.bool.isRequired,
4440
+ isEdit: PropTypes.bool.isRequired,
4441
+ iframeParent: PropTypes.object.isRequired,
4442
+ router: PropTypes.object.isRequired,
4443
+ baseLanguage: PropTypes.string.isRequired,
4444
+ supportedLanguages: PropTypes.array.isRequired,
4445
+ isSchemaChanged: PropTypes.bool.isRequired,
4446
+ cmsTemplates: PropTypes.array.isRequired,
4447
+ getCmsTemplatesInProgress: PropTypes.bool.isRequired,
4448
+ showEdmEmailTemplates: PropTypes.bool.isRequired,
4449
+ toggleEdmEmailTemplateSelection: PropTypes.func.isRequired,
4450
+ handleEdmDefaultTemplateSelection: PropTypes.func.isRequired,
4451
+ setModalContent: PropTypes.func.isRequired,
4452
+ addLanguageType: PropTypes.string.isRequired,
4453
+ startValidation: PropTypes.bool.isRequired,
4454
+ getValidationData: PropTypes.func.isRequired,
4455
+ saveForm: PropTypes.bool.isRequired,
4456
+ stopValidation: PropTypes.func.isRequired,
4457
+ selectedOfferDetails: PropTypes.object.isRequired,
4458
+ saveBeeInstance: PropTypes.func.isRequired,
4459
+ saveBeeData: PropTypes.func.isRequired,
4460
+ uuid: PropTypes.string.isRequired,
4461
+ type: PropTypes.string.isRequired,
4462
+ isEmailLoading: PropTypes.bool.isRequired,
4463
+ moduleType: PropTypes.string.isRequired,
4464
+ showLiquidErrorInFooter: PropTypes.bool.isRequired,
4465
+ eventContextTags: PropTypes.array.isRequired,
4466
+ waitEventContextTags: PropTypes.object,
4467
+ forwardedTags: PropTypes.object.isRequired,
4468
+ isLoyaltyModule: PropTypes.bool.isRequired,
4469
+ isTestAndPreviewMode: PropTypes.bool, // Add new prop type
4470
+ restrictPersonalization: PropTypes.bool,
4471
+ };
4472
+
4473
+ const mapStateToProps = createStructuredSelector({
4474
+ currentOrgDetails: selectCurrentOrgDetails(),
4475
+ liquidExtractionInProgress: selectLiquidStateDetails(),
4476
+ metaDataStatus: selectMetaDataStatus(),
4477
+ metaEntities: makeSelectMetaEntities(),
4478
+ });
4479
+
4480
+ function mapDispatchToProps(dispatch) {
4481
+ return {
4482
+ actions: bindActionCreators(actions, dispatch),
4483
+ };
4484
+ }
4485
+
4486
+ export default connect(mapStateToProps,mapDispatchToProps)(injectIntl(FormBuilder));
4487
+