@capillarytech/creatives-library 9.0.13-alpha.0 → 9.0.13-alpha.1

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