@capillarytech/creatives-library 8.0.201 → 8.0.204
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/constants/unified.js +1 -0
- package/package.json +1 -1
- package/utils/common.js +7 -1
- package/utils/createMobilePushPayload.js +8 -29
- package/utils/tests/createMobilePushPayload.test.js +0 -53
- package/v2Components/FormBuilder/index.js +0 -300
- package/v2Components/TemplatePreview/_templatePreview.scss +4 -3
- package/v2Components/TemplatePreview/index.js +2 -2
- package/v2Containers/BeeEditor/index.js +1 -1
- package/v2Containers/CreativesContainer/SlideBoxContent.js +6 -2
- package/v2Containers/CreativesContainer/index.js +17 -62
- package/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +0 -317
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -727
- package/v2Containers/Email/index.js +0 -3
- package/v2Containers/MobilePushNew/components/MediaUploaders.js +6 -13
- package/v2Containers/MobilePushNew/components/tests/MediaUploaders.test.js +15 -3
- package/v2Containers/MobilePushNew/index.js +70 -172
- package/v2Containers/MobilePushNew/messages.js +0 -21
- package/v2Containers/MobilePushNew/sagas.js +0 -8
- package/v2Containers/MobilePushNew/tests/sagas.test.js +45 -11
- package/v2Containers/Templates/index.js +15 -11
package/constants/unified.js
CHANGED
|
@@ -44,6 +44,7 @@ export const REGISTRATION_CUSTOM_FIELD = 'Registration custom fields';
|
|
|
44
44
|
export const GIFT_CARDS = 'GIFT_CARDS';
|
|
45
45
|
export const PROMO_ENGINE = 'PROMO_ENGINE';
|
|
46
46
|
export const LIQUID_SUPPORT = 'ENABLE_LIQUID_SUPPORT';
|
|
47
|
+
export const ENABLE_NEW_MPUSH = 'ENABLE_NEW_MPUSH';
|
|
47
48
|
export const CUSTOM_TAG = 'CustomTagMessage';
|
|
48
49
|
export const CUSTOMER_EXTENDED_FIELD = 'Customer extended fields';
|
|
49
50
|
export const EXTENDED_TAG = 'ExtendedTagMessage';
|
package/package.json
CHANGED
package/utils/common.js
CHANGED
|
@@ -21,7 +21,8 @@ import {
|
|
|
21
21
|
EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
|
|
22
22
|
BADGES_ISSUE,
|
|
23
23
|
ENABLE_WECHAT,
|
|
24
|
-
LIQUID_SUPPORT
|
|
24
|
+
LIQUID_SUPPORT,
|
|
25
|
+
ENABLE_NEW_MPUSH
|
|
25
26
|
} from '../constants/unified';
|
|
26
27
|
import { apiMessageFormatHandler } from './commonUtils';
|
|
27
28
|
|
|
@@ -125,6 +126,11 @@ export const isEmailUnsubscribeTagMandatory = Auth.hasFeatureAccess.bind(
|
|
|
125
126
|
EMAIL_UNSUBSCRIBE_TAG_MANDATORY,
|
|
126
127
|
);
|
|
127
128
|
|
|
129
|
+
export const hasNewMobilePushFeatureEnabled = Auth.hasFeatureAccess.bind(
|
|
130
|
+
null,
|
|
131
|
+
ENABLE_NEW_MPUSH,
|
|
132
|
+
);
|
|
133
|
+
|
|
128
134
|
//filtering tags based on scope
|
|
129
135
|
export const filterTags = (tagsToFilter, tagsList) => tagsList?.filter(
|
|
130
136
|
(tag) => !tagsToFilter?.includes(tag?.definition?.value)
|
|
@@ -62,33 +62,12 @@ const createMobilePushPayload = ({
|
|
|
62
62
|
throw new Error(intl.formatMessage(messages.templateNameEmptyError));
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
// Validate content
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
|
|
72
|
-
}
|
|
73
|
-
if (isIosSupported && (!iosContent?.title || !iosContent?.message)) {
|
|
74
|
-
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
|
|
75
|
-
}
|
|
76
|
-
} else {
|
|
77
|
-
// Single platform mode: require at least one platform to have content
|
|
78
|
-
const hasAndroidContent = isAndroidSupported && androidContent?.title?.trim() && androidContent?.message?.trim();
|
|
79
|
-
const hasIosContent = isIosSupported && iosContent?.title?.trim() && iosContent?.message?.trim();
|
|
80
|
-
|
|
81
|
-
if (!hasAndroidContent && !hasIosContent) {
|
|
82
|
-
throw new Error(intl.formatMessage(messages.singlePlatformContentMissing));
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Validate individual platforms that are supported but have incomplete content
|
|
86
|
-
if (isAndroidSupported && !hasAndroidContent && androidContent && (androidContent.title || androidContent.message)) {
|
|
87
|
-
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
|
|
88
|
-
}
|
|
89
|
-
if (isIosSupported && !hasIosContent && iosContent && (iosContent.title || iosContent.message)) {
|
|
90
|
-
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
|
|
91
|
-
}
|
|
65
|
+
// Validate content
|
|
66
|
+
if (isAndroidSupported && (!androidContent?.title || !androidContent?.message)) {
|
|
67
|
+
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
|
|
68
|
+
}
|
|
69
|
+
if (isIosSupported && (!iosContent?.title || !iosContent?.message)) {
|
|
70
|
+
throw new Error(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
|
|
92
71
|
}
|
|
93
72
|
|
|
94
73
|
// Ensure imageSrc has the required properties
|
|
@@ -120,7 +99,7 @@ const createMobilePushPayload = ({
|
|
|
120
99
|
};
|
|
121
100
|
|
|
122
101
|
// Build Android content
|
|
123
|
-
if (isAndroidSupported
|
|
102
|
+
if (isAndroidSupported) {
|
|
124
103
|
payload.versions.base.ANDROID = buildPlatformContent(
|
|
125
104
|
androidContent,
|
|
126
105
|
safeImageSrc.androidImageSrc,
|
|
@@ -130,7 +109,7 @@ const createMobilePushPayload = ({
|
|
|
130
109
|
}
|
|
131
110
|
|
|
132
111
|
// Build iOS content
|
|
133
|
-
if (isIosSupported
|
|
112
|
+
if (isIosSupported) {
|
|
134
113
|
payload.versions.base.IOS = buildPlatformContent(
|
|
135
114
|
iosContent,
|
|
136
115
|
safeImageSrc.iosImageSrc,
|
|
@@ -261,59 +261,6 @@ describe('createMobilePushPayload', () => {
|
|
|
261
261
|
accountData: unsupportedAccountData,
|
|
262
262
|
})).not.toThrow();
|
|
263
263
|
});
|
|
264
|
-
|
|
265
|
-
// Single Platform Mode Tests
|
|
266
|
-
it('should allow single platform when allowSinglePlatform is true - Android only', () => {
|
|
267
|
-
expect(() => callWithIntl({
|
|
268
|
-
templateName: 'Test',
|
|
269
|
-
androidContent: { title: 'Title', message: 'Message' },
|
|
270
|
-
iosContent: null,
|
|
271
|
-
accountData: mockAccountData,
|
|
272
|
-
options: { allowSinglePlatform: true },
|
|
273
|
-
})).not.toThrow();
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
it('should allow single platform when allowSinglePlatform is true - iOS only', () => {
|
|
277
|
-
expect(() => callWithIntl({
|
|
278
|
-
templateName: 'Test',
|
|
279
|
-
androidContent: null,
|
|
280
|
-
iosContent: { title: 'Title', message: 'Message' },
|
|
281
|
-
accountData: mockAccountData,
|
|
282
|
-
options: { allowSinglePlatform: true },
|
|
283
|
-
})).not.toThrow();
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
it('should throw error when no platforms have content even with allowSinglePlatform', () => {
|
|
287
|
-
expect(() => callWithIntl({
|
|
288
|
-
templateName: 'Test',
|
|
289
|
-
androidContent: null,
|
|
290
|
-
iosContent: null,
|
|
291
|
-
accountData: mockAccountData,
|
|
292
|
-
options: { allowSinglePlatform: true },
|
|
293
|
-
})).toThrow(intl.formatMessage(messages.singlePlatformContentMissing));
|
|
294
|
-
});
|
|
295
|
-
|
|
296
|
-
it('should validate individual platforms in single platform mode - Android incomplete', () => {
|
|
297
|
-
// Test case where iOS has valid content but Android has incomplete content
|
|
298
|
-
expect(() => callWithIntl({
|
|
299
|
-
templateName: 'Test',
|
|
300
|
-
androidContent: { title: 'Valid Title' }, // Missing message property
|
|
301
|
-
iosContent: { title: 'Valid Title', message: 'Valid Message' }, // Complete content
|
|
302
|
-
accountData: mockAccountData,
|
|
303
|
-
options: { allowSinglePlatform: true },
|
|
304
|
-
})).toThrow(intl.formatMessage(messages.contentValidationError, { platform: 'Android' }));
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it('should validate individual platforms in single platform mode - iOS incomplete', () => {
|
|
308
|
-
// Test case where Android has valid content but iOS has incomplete content
|
|
309
|
-
expect(() => callWithIntl({
|
|
310
|
-
templateName: 'Test',
|
|
311
|
-
androidContent: { title: 'Valid Title', message: 'Valid Message' }, // Complete content
|
|
312
|
-
iosContent: { title: 'Valid Title' }, // Missing message property
|
|
313
|
-
accountData: mockAccountData,
|
|
314
|
-
options: { allowSinglePlatform: true },
|
|
315
|
-
})).toThrow(intl.formatMessage(messages.contentValidationError, { platform: 'iOS' }));
|
|
316
|
-
});
|
|
317
264
|
});
|
|
318
265
|
|
|
319
266
|
// Account Data Validation Tests
|
|
@@ -139,196 +139,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
139
139
|
// Check if the liquid flow feature is supported and the channel is in the supported list.
|
|
140
140
|
this.liquidFlow = this.isLiquidFlowSupported.bind(this);
|
|
141
141
|
this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
|
|
142
|
-
|
|
143
|
-
// Performance optimization: Debounced functions for high-frequency updates
|
|
144
|
-
this.debouncedUpdateFormData = _.debounce((data, val, event, skipStateUpdate) => {
|
|
145
|
-
this.performFormDataUpdate(data, val, event, skipStateUpdate);
|
|
146
|
-
}, 300);
|
|
147
|
-
this.debouncedValidation = _.debounce(this.validateForm.bind(this), 500);
|
|
148
|
-
|
|
149
|
-
// Memoized validation cache
|
|
150
|
-
this.validationCache = new Map();
|
|
151
|
-
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Helper function to generate unique tab ID
|
|
155
|
-
generateUniqueTabId(formData, tabIndex) {
|
|
156
|
-
let id = _.uniqueId();
|
|
157
|
-
let validId = false;
|
|
158
|
-
|
|
159
|
-
while (!validId) {
|
|
160
|
-
validId = true;
|
|
161
|
-
for (let idx = 0; idx < formData[tabIndex].selectedLanguages.length; idx += 1) {
|
|
162
|
-
if (!formData[tabIndex]) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
if (id === formData[tabIndex][formData[tabIndex].selectedLanguages[idx]].tabKey) {
|
|
166
|
-
validId = false;
|
|
167
|
-
break;
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
if (!validId) {
|
|
171
|
-
id = _.uniqueId();
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return id;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Performance optimized form data update function
|
|
179
|
-
performFormDataUpdate(data, val, event, skipStateUpdate = false) {
|
|
180
|
-
|
|
181
|
-
// Use optimized state update instead of deep cloning
|
|
182
|
-
const formData = this.optimizedFormDataUpdate(data, val, event);
|
|
183
|
-
|
|
184
|
-
const tabIndex = this.state.currentTab - 1;
|
|
185
|
-
let currentTab = this.state.currentTab;
|
|
186
|
-
|
|
187
|
-
if (this.state.usingTabContainer && !val.standalone) {
|
|
188
|
-
const data1 = data;
|
|
189
|
-
if (event === "addLanguage") {
|
|
190
|
-
const addLanguageType = this.props.addLanguageType;
|
|
191
|
-
if (addLanguageType === '') {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
const currentLang = formData[tabIndex].activeTab;
|
|
195
|
-
let baseTab;
|
|
196
|
-
|
|
197
|
-
switch (addLanguageType) {
|
|
198
|
-
case "upload":
|
|
199
|
-
baseTab = _.cloneDeep(formData[tabIndex][currentLang]);
|
|
200
|
-
|
|
201
|
-
baseTab.iso_code = data.iso_code;
|
|
202
|
-
baseTab.lang_id = data.lang_id;
|
|
203
|
-
baseTab.language = data.language;
|
|
204
|
-
baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
|
|
205
|
-
|
|
206
|
-
formData[tabIndex][data.iso_code] = baseTab;
|
|
207
|
-
formData[tabIndex].activeTab = data.iso_code;
|
|
208
|
-
formData[tabIndex].tabKey = baseTab.tabKey;
|
|
209
|
-
break;
|
|
210
|
-
case "copyPrimaryLanguage":
|
|
211
|
-
case "useEditor":
|
|
212
|
-
baseTab = _.cloneDeep(formData[tabIndex][this.props.baseLanguage]);
|
|
213
|
-
|
|
214
|
-
baseTab.iso_code = data.iso_code;
|
|
215
|
-
baseTab.lang_id = data.lang_id;
|
|
216
|
-
baseTab.language = data.language;
|
|
217
|
-
baseTab.tabKey = this.generateUniqueTabId(formData, tabIndex);
|
|
218
|
-
|
|
219
|
-
formData[tabIndex].selectedLanguages.push(data.iso_code);
|
|
220
|
-
formData[tabIndex][data.iso_code] = baseTab;
|
|
221
|
-
formData[tabIndex].activeTab = data.iso_code;
|
|
222
|
-
formData[tabIndex].tabKey = baseTab.tabKey;
|
|
223
|
-
break;
|
|
224
|
-
case '':
|
|
225
|
-
return;
|
|
226
|
-
default:
|
|
227
|
-
break;
|
|
228
|
-
}
|
|
229
|
-
const that = this;
|
|
230
|
-
setTimeout(() => {
|
|
231
|
-
that.setState({tabKey: baseTab.tabKey});
|
|
232
|
-
}, 0);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
if (!this.props.isNewVersionFlow) {
|
|
236
|
-
formData[tabIndex][val.id] = data1;
|
|
237
|
-
} else if (this.props.isNewVersionFlow && event !== "addLanguage" && event !== "onContentChange") {
|
|
238
|
-
formData[tabIndex][this.props.baseLanguage][val.id] = data1;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
if (formData[tabIndex].base) {
|
|
242
|
-
if (!this.props.isNewVersionFlow) {
|
|
243
|
-
formData.base[val.id] = data1;
|
|
244
|
-
} else {
|
|
245
|
-
formData.base[data1.iso_code] = formData[tabIndex][data1.iso_code];
|
|
246
|
-
formData.base.tabKey = formData[tabIndex].tabKey;
|
|
247
|
-
formData.base.activeTab = formData[tabIndex].activeTab;
|
|
248
|
-
formData.base.selectedLanguages = formData[tabIndex].selectedLanguages;
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
formData[val.id] = data;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
if (this.props.isNewVersionFlow) {
|
|
256
|
-
if (event === 'onSelect' && data === 'New Version') {
|
|
257
|
-
this.callChildEvent(data, val, 'addVersion', event);
|
|
258
|
-
} else if (event === 'onSelect' && data !== 'New Version') {
|
|
259
|
-
currentTab = _.findIndex(this.state.formData['template-version-options'], { key: data}) + 1;
|
|
260
|
-
this.setState({currentTab, tabKey: formData[`${currentTab - 1}`].tabKey}, () => {
|
|
261
|
-
val.injectedEvents[event].call(this, this.state.formData['template-version-options'][currentTab - 1].key, formData, val);
|
|
262
|
-
});
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (event === 'onContentChange') {
|
|
266
|
-
// Content change handling
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
142
|
|
|
270
|
-
// Only update state if not already updated (for immediate UI feedback)
|
|
271
|
-
if (!skipStateUpdate) {
|
|
272
|
-
this.setState({formData}, () => {
|
|
273
|
-
if (this.props.startValidation) {
|
|
274
|
-
this.debouncedValidation();
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
} else {
|
|
278
|
-
// Just trigger validation if state was already updated
|
|
279
|
-
if (this.props.startValidation) {
|
|
280
|
-
this.debouncedValidation();
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
if (event && val.injectedEvents[event]) {
|
|
285
|
-
if (event === "onRowClick") {
|
|
286
|
-
val.injectedEvents[event].call(this, data);
|
|
287
|
-
} else if (this.props.isNewVersionFlow && event !== 'onSelect') {
|
|
288
|
-
if (event === 'addLanguage') {
|
|
289
|
-
val.injectedEvents[event].call(this, data, formData, val);
|
|
290
|
-
this.setState({currentEventVal: {}, currentEvent: {}, currentEventData: {}});
|
|
291
|
-
} else {
|
|
292
|
-
val.injectedEvents[event].call(this, true, formData, val);
|
|
293
|
-
}
|
|
294
|
-
} else if (!this.props.isNewVersionFlow) {
|
|
295
|
-
val.injectedEvents[event].call(this, true, formData, val);
|
|
296
|
-
}
|
|
297
|
-
} else if (val.injectedEvents && val.injectedEvents.onChange) {
|
|
298
|
-
val.injectedEvents.onChange.call(this, true, formData, val);
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (!((event === 'onSelect' && data === 'New Version') || event === 'onContentChange')) {
|
|
302
|
-
this.props.onChange(formData, this.state.tabCount, currentTab, val);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Optimized form data update - only clone what's necessary
|
|
307
|
-
optimizedFormDataUpdate(data, val, event) {
|
|
308
|
-
const currentFormData = this.state.formData;
|
|
309
|
-
|
|
310
|
-
// For simple field updates, use spread operator instead of deep clone
|
|
311
|
-
if (!this.state.usingTabContainer || val.standalone) {
|
|
312
|
-
return {
|
|
313
|
-
...currentFormData,
|
|
314
|
-
[val.id]: data
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
// For tab container updates, only clone the affected tab
|
|
319
|
-
const tabIndex = this.state.currentTab - 1;
|
|
320
|
-
const updatedFormData = { ...currentFormData };
|
|
321
|
-
|
|
322
|
-
if (updatedFormData[tabIndex]) {
|
|
323
|
-
updatedFormData[tabIndex] = {
|
|
324
|
-
...updatedFormData[tabIndex],
|
|
325
|
-
[val.id]: data
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
return updatedFormData;
|
|
330
143
|
}
|
|
331
|
-
|
|
332
144
|
isLiquidFlowSupported = () => {
|
|
333
145
|
return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
|
|
334
146
|
}
|
|
@@ -2215,64 +2027,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2215
2027
|
}
|
|
2216
2028
|
|
|
2217
2029
|
updateFormData(data, val, event) {
|
|
2218
|
-
|
|
2219
|
-
// Check if this is a high-frequency input field that should be optimized
|
|
2220
|
-
const isHighFrequencyField = val && (
|
|
2221
|
-
val.id === 'template-name' ||
|
|
2222
|
-
val.id === 'template-subject' ||
|
|
2223
|
-
val.type === 'input' ||
|
|
2224
|
-
val.type === 'textarea'
|
|
2225
|
-
);
|
|
2226
|
-
|
|
2227
|
-
if (isHighFrequencyField && !event) {
|
|
2228
|
-
// For high-frequency fields: immediate UI update + debounced expensive operations
|
|
2229
|
-
this.updateFormDataOptimized(data, val, event);
|
|
2230
|
-
return;
|
|
2231
|
-
}
|
|
2232
|
-
|
|
2233
|
-
// For non-high-frequency fields or special events, use immediate update
|
|
2234
|
-
this.performFormDataUpdate(data, val, event);
|
|
2235
|
-
}
|
|
2236
|
-
|
|
2237
|
-
// Optimized update for high-frequency fields
|
|
2238
|
-
updateFormDataOptimized(data, val, event) {
|
|
2239
|
-
// 1. Immediate UI update - update the field value instantly
|
|
2240
|
-
this.updateFieldValueImmediately(data, val);
|
|
2241
|
-
|
|
2242
|
-
// 2. Debounce expensive operations (validation, parent updates) - skip state update since we already did it
|
|
2243
|
-
this.debouncedUpdateFormData(data, val, event, true);
|
|
2244
|
-
}
|
|
2245
|
-
|
|
2246
|
-
// Update field value immediately for UI feedback
|
|
2247
|
-
updateFieldValueImmediately(data, val) {
|
|
2248
|
-
const currentFormData = this.state.formData;
|
|
2249
|
-
let updatedFormData;
|
|
2250
|
-
|
|
2251
|
-
if (!this.state.usingTabContainer || val.standalone) {
|
|
2252
|
-
// Simple field update
|
|
2253
|
-
updatedFormData = {
|
|
2254
|
-
...currentFormData,
|
|
2255
|
-
[val.id]: data
|
|
2256
|
-
};
|
|
2257
|
-
} else {
|
|
2258
|
-
// Tab container update
|
|
2259
|
-
const tabIndex = this.state.currentTab - 1;
|
|
2260
|
-
updatedFormData = { ...currentFormData };
|
|
2261
|
-
|
|
2262
|
-
if (updatedFormData[tabIndex]) {
|
|
2263
|
-
updatedFormData[tabIndex] = {
|
|
2264
|
-
...updatedFormData[tabIndex],
|
|
2265
|
-
[val.id]: data
|
|
2266
|
-
};
|
|
2267
|
-
}
|
|
2268
|
-
}
|
|
2269
|
-
|
|
2270
|
-
// Update state immediately for UI feedback
|
|
2271
|
-
this.setState({ formData: updatedFormData });
|
|
2272
|
-
}
|
|
2273
|
-
|
|
2274
|
-
// Legacy updateFormData function - kept for backward compatibility
|
|
2275
|
-
updateFormDataLegacy(data, val, event) {
|
|
2276
2030
|
const formData = _.cloneDeep(this.state.formData);
|
|
2277
2031
|
|
|
2278
2032
|
const tabIndex = this.state.currentTab - 1;
|
|
@@ -2432,57 +2186,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2432
2186
|
hasClass(element, className) {
|
|
2433
2187
|
return (` ${element.className} `).indexOf(` ${className} `) > -1;
|
|
2434
2188
|
}
|
|
2435
|
-
|
|
2436
|
-
// Handle field blur for validation
|
|
2437
|
-
handleFieldBlur = (e, val) => {
|
|
2438
|
-
// Trigger validation on blur for high-frequency fields
|
|
2439
|
-
if (val && (val.id === 'template-name' || val.id === 'template-subject' || val.type === 'input' || val.type === 'textarea')) {
|
|
2440
|
-
this.debouncedValidation();
|
|
2441
|
-
}
|
|
2442
|
-
}
|
|
2443
|
-
|
|
2444
|
-
// Memoized validation for specific fields
|
|
2445
|
-
getMemoizedValidation = (fieldId, fieldValue) => {
|
|
2446
|
-
const cacheKey = `${fieldId}_${fieldValue}`;
|
|
2447
|
-
|
|
2448
|
-
if (this.validationCache.has(cacheKey)) {
|
|
2449
|
-
return this.validationCache.get(cacheKey);
|
|
2450
|
-
}
|
|
2451
|
-
|
|
2452
|
-
// Perform validation logic here
|
|
2453
|
-
const isValid = this.performFieldValidation(fieldId, fieldValue);
|
|
2454
|
-
this.validationCache.set(cacheKey, isValid);
|
|
2455
|
-
|
|
2456
|
-
// Clear cache if it gets too large
|
|
2457
|
-
if (this.validationCache.size > 100) {
|
|
2458
|
-
this.validationCache.clear();
|
|
2459
|
-
}
|
|
2460
|
-
|
|
2461
|
-
return isValid;
|
|
2462
|
-
}
|
|
2463
|
-
|
|
2464
|
-
// Perform validation for a specific field
|
|
2465
|
-
performFieldValidation = (fieldId, fieldValue) => {
|
|
2466
|
-
// Basic validation logic for template-name and template-subject
|
|
2467
|
-
if (fieldId === 'template-name' || fieldId === 'template-subject') {
|
|
2468
|
-
return fieldValue && fieldValue.trim().length > 0;
|
|
2469
|
-
}
|
|
2470
|
-
return true;
|
|
2471
|
-
}
|
|
2472
|
-
|
|
2473
|
-
// Cleanup debounced functions on component unmount
|
|
2474
|
-
componentWillUnmount() {
|
|
2475
|
-
if (this.debouncedUpdateFormData) {
|
|
2476
|
-
this.debouncedUpdateFormData.cancel();
|
|
2477
|
-
}
|
|
2478
|
-
if (this.debouncedValidation) {
|
|
2479
|
-
this.debouncedValidation.cancel();
|
|
2480
|
-
}
|
|
2481
|
-
// Clear validation cache
|
|
2482
|
-
if (this.validationCache) {
|
|
2483
|
-
this.validationCache.clear();
|
|
2484
|
-
}
|
|
2485
|
-
}
|
|
2486
2189
|
allowAddSecondaryCta = (val) => {
|
|
2487
2190
|
if (val.fieldsCount > 0) {
|
|
2488
2191
|
const errorData = _.cloneDeep(this.state.errorData);
|
|
@@ -2898,7 +2601,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2898
2601
|
label={val.label}
|
|
2899
2602
|
autosize={val.autosize ? val.autosizeParams : false}
|
|
2900
2603
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
2901
|
-
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
2902
2604
|
style={val.style ? val.style : {}}
|
|
2903
2605
|
defaultValue={messageContent || ''}
|
|
2904
2606
|
value={messageContent || ""}
|
|
@@ -2980,7 +2682,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
2980
2682
|
style={val.style ? val.style : {}}
|
|
2981
2683
|
placeholder={val.placeholder}
|
|
2982
2684
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
2983
|
-
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
2984
2685
|
defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
|
|
2985
2686
|
value={value || ""}
|
|
2986
2687
|
disabled={val.disabled}
|
|
@@ -3465,7 +3166,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
|
|
|
3465
3166
|
style={val.style ? val.style : {}}
|
|
3466
3167
|
placeholder={val.placeholder}
|
|
3467
3168
|
onChange={(e) => this.updateFormData(e.target.value, val)}
|
|
3468
|
-
onBlur={(e) => this.handleFieldBlur(e, val)}
|
|
3469
3169
|
value={value || ""}
|
|
3470
3170
|
defaultValue={isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id]}
|
|
3471
3171
|
disabled={val.disabled}
|
|
@@ -599,8 +599,9 @@
|
|
|
599
599
|
font-size: 10px;
|
|
600
600
|
.body-image{
|
|
601
601
|
img{
|
|
602
|
-
max-width:
|
|
602
|
+
max-width: 18.75rem;
|
|
603
603
|
max-height: 13.75rem;
|
|
604
|
+
margin-left: 1.571rem;
|
|
604
605
|
display: block;
|
|
605
606
|
}
|
|
606
607
|
}
|
|
@@ -619,7 +620,7 @@
|
|
|
619
620
|
box-shadow: 0 0 0.625rem 0 rgba(0,0,0,0.08); // 10px
|
|
620
621
|
|
|
621
622
|
.carousel-image {
|
|
622
|
-
width:
|
|
623
|
+
width: 100%;
|
|
623
624
|
height: 11.25rem; // 180px
|
|
624
625
|
object-fit: contain;
|
|
625
626
|
border-radius: 0.5rem 0.5rem 0 0; // 8px
|
|
@@ -695,7 +696,7 @@
|
|
|
695
696
|
padding: 0 $CAP_SPACE_08 0 $CAP_SPACE_08;
|
|
696
697
|
}
|
|
697
698
|
.body-image{
|
|
698
|
-
padding:
|
|
699
|
+
padding: 0 $CAP_SPACE_08 $CAP_SPACE_08 $CAP_SPACE_08;
|
|
699
700
|
img{
|
|
700
701
|
border-radius: 3%;
|
|
701
702
|
}
|
|
@@ -816,7 +816,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
816
816
|
<video
|
|
817
817
|
key={`android-video-${content.bodyVideo.videoSrc}`}
|
|
818
818
|
controls
|
|
819
|
-
style={{ maxWidth: '100%', maxHeight: '
|
|
819
|
+
style={{ maxWidth: '100%', maxHeight: '120px', marginLeft: '24px' }}
|
|
820
820
|
poster={content?.bodyVideo?.videoPreview || undefined}
|
|
821
821
|
>
|
|
822
822
|
<source src={content.bodyVideo.videoSrc} type="video/mp4" />
|
|
@@ -880,7 +880,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
|
|
|
880
880
|
<video
|
|
881
881
|
key={`ios-video-${content?.bodyVideo?.videoSrc}`}
|
|
882
882
|
controls
|
|
883
|
-
style={{ maxWidth: '100%', maxHeight: '
|
|
883
|
+
style={{ maxWidth: '100%', maxHeight: '120px', marginLeft: '24px' }}
|
|
884
884
|
poster={content?.bodyVideo?.videoPreview || undefined}
|
|
885
885
|
>
|
|
886
886
|
<source src={content?.bodyVideo?.videoSrc} type="video/mp4" />
|
|
@@ -349,4 +349,4 @@ const withConnect = connect(mapStateToProps, mapDispatchToProps);
|
|
|
349
349
|
const withSaga = injectSaga({ key: 'beeEditor', saga: v2BeeEditionSagas });
|
|
350
350
|
const withReducer = injectReducer({ key: 'beeEditor', reducer: v2BeeEditionReducer });
|
|
351
351
|
|
|
352
|
-
export default compose(withReducer, withSaga, withConnect)(injectIntl(
|
|
352
|
+
export default compose(withReducer, withSaga, withConnect)(injectIntl(BeeEditor));
|
|
@@ -668,7 +668,9 @@ export function SlideBoxContent(props) {
|
|
|
668
668
|
/>
|
|
669
669
|
)}
|
|
670
670
|
{isEditMPush && (
|
|
671
|
-
(
|
|
671
|
+
(isFullMode && !commonUtil.hasNewMobilePushFeatureEnabled()) ||
|
|
672
|
+
(!isFullMode && isLoyaltyModule) ||
|
|
673
|
+
(!isFullMode && !isLoyaltyModule && !commonUtil.hasNewMobilePushFeatureEnabled()) ? (
|
|
672
674
|
<MobliPushEdit
|
|
673
675
|
getFormLibraryData={getFormData}
|
|
674
676
|
setIsLoadingContent={setIsLoadingContent}
|
|
@@ -724,7 +726,9 @@ export function SlideBoxContent(props) {
|
|
|
724
726
|
)
|
|
725
727
|
)}
|
|
726
728
|
{isCreateMPush && (
|
|
727
|
-
(
|
|
729
|
+
(isFullMode && !commonUtil.hasNewMobilePushFeatureEnabled()) ||
|
|
730
|
+
(!isFullMode && isLoyaltyModule) ||
|
|
731
|
+
(!isFullMode && !isLoyaltyModule && !commonUtil.hasNewMobilePushFeatureEnabled()) ? (
|
|
728
732
|
<MobilepushWrapper
|
|
729
733
|
key="creatives-mobilepush-wrapper"
|
|
730
734
|
date={new Date().getMilliseconds()}
|