@capillarytech/creatives-library 8.0.236-alpha.5 → 8.0.236-alpha.7

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.236-alpha.5",
4
+ "version": "8.0.236-alpha.7",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -13,7 +13,7 @@
13
13
  */
14
14
 
15
15
  import React, {
16
- useRef, useCallback, useMemo, useState,
16
+ useRef, useCallback, useMemo, useState, useEffect,
17
17
  } from 'react';
18
18
  import PropTypes from 'prop-types';
19
19
  import { injectIntl, intlShape } from 'react-intl';
@@ -38,6 +38,7 @@ import { useEditorContent } from './hooks/useEditorContent';
38
38
  import { useInAppContent } from './hooks/useInAppContent';
39
39
  import { useLayoutState } from './hooks/useLayoutState';
40
40
  import { useValidation } from './hooks/useValidation';
41
+ import { transformValidationToErrorInfo } from './utils/validationAdapter';
41
42
 
42
43
  // Constants
43
44
  import {
@@ -71,6 +72,7 @@ const HTMLEditor = ({
71
72
  onTagSelect = null,
72
73
  onContextChange = null,
73
74
  globalActions = null, // Redux actions for API calls
75
+ onValidationChange = null, // Callback to propagate validation errors to parent
74
76
  ...props
75
77
  }) => {
76
78
  // Separate refs for main and modal editors to avoid conflicts
@@ -275,7 +277,9 @@ const HTMLEditor = ({
275
277
  editor.insertText(label, cursor);
276
278
 
277
279
  // Focus the editor if focus method is available
278
- editor?.focus?.();
280
+ if (editor && typeof editor.focus === 'function') {
281
+ editor.focus();
282
+ }
279
283
 
280
284
  // Show success notification
281
285
  CapNotification.success({
@@ -305,13 +309,15 @@ const HTMLEditor = ({
305
309
  const handleSave = useCallback(() => {
306
310
  try {
307
311
  const { html, css, javascript } = content?.content || {};
308
- const currentContent = { html, css, javascript };
312
+ const contentToSave = { html, css, javascript };
309
313
 
310
314
  if (onSave) {
311
- onSave(currentContent);
315
+ onSave(contentToSave);
312
316
  }
313
317
 
314
- markAsSaved?.();
318
+ if (markAsSaved) {
319
+ markAsSaved();
320
+ }
315
321
 
316
322
  CapNotification.success({
317
323
  message: intl.formatMessage(messages.contentSaved),
@@ -337,13 +343,15 @@ const HTMLEditor = ({
337
343
  // Access the CodeMirror view through the exposed ref methods
338
344
  if (editorInstance?.navigateToLine) {
339
345
  editorInstance.navigateToLine(line, column);
340
- } else {
341
- editorInstance?.focus?.();
346
+ } else if (editorInstance && typeof editorInstance.focus === 'function') {
347
+ editorInstance.focus();
342
348
  // Fallback: just focus the editor if navigation isn't available
343
349
  }
344
350
  } catch (err) {
345
351
  // Fallback: just focus the editor
346
- editorInstance?.focus?.();
352
+ if (editorInstance && typeof editorInstance.focus === 'function') {
353
+ editorInstance.focus();
354
+ }
347
355
  }
348
356
  }
349
357
  }, [getActiveEditorRef]);
@@ -357,6 +365,63 @@ const HTMLEditor = ({
357
365
  setIsFullscreenModalOpen(false);
358
366
  }, []);
359
367
 
368
+ // Propagate validation errors to parent component
369
+ // Use refs to track previous values and prevent infinite loops
370
+ const lastValidationErrorsRef = useRef(null);
371
+ const lastActiveDeviceRef = useRef(variant === HTML_EDITOR_VARIANTS.INAPP ? content?.activeDevice : null);
372
+
373
+ useEffect(() => {
374
+ if (onValidationChange && validation && !validation.isValidating) {
375
+ const errorData = transformValidationToErrorInfo(validation, variant);
376
+
377
+ // Create error messages object
378
+ let errorMessages;
379
+ if (variant === HTML_EDITOR_VARIANTS.INAPP && content?.activeDevice) {
380
+ const currentActiveDevice = content.activeDevice;
381
+ const deviceKey = currentActiveDevice === 'android' ? 'ANDROID' : 'IOS';
382
+ errorMessages = {
383
+ STANDARD_ERROR_MSG: {
384
+ [deviceKey]: errorData.errorMessages.STANDARD_ERROR_MSG || [],
385
+ ANDROID: currentActiveDevice === 'android' ? (errorData.errorMessages.STANDARD_ERROR_MSG || []) : [],
386
+ IOS: currentActiveDevice === 'ios' ? (errorData.errorMessages.STANDARD_ERROR_MSG || []) : [],
387
+ GENERIC: [],
388
+ },
389
+ LIQUID_ERROR_MSG: {
390
+ [deviceKey]: errorData.errorMessages.LIQUID_ERROR_MSG || [],
391
+ ANDROID: currentActiveDevice === 'android' ? (errorData.errorMessages.LIQUID_ERROR_MSG || []) : [],
392
+ IOS: currentActiveDevice === 'ios' ? (errorData.errorMessages.LIQUID_ERROR_MSG || []) : [],
393
+ GENERIC: [],
394
+ },
395
+ };
396
+ } else {
397
+ // For Email variant or when no active device, use generic format
398
+ errorMessages = {
399
+ STANDARD_ERROR_MSG: {
400
+ ANDROID: [],
401
+ IOS: [],
402
+ GENERIC: errorData.errorMessages.STANDARD_ERROR_MSG || [],
403
+ },
404
+ LIQUID_ERROR_MSG: {
405
+ ANDROID: [],
406
+ IOS: [],
407
+ GENERIC: errorData.errorMessages.LIQUID_ERROR_MSG || [],
408
+ },
409
+ };
410
+ }
411
+
412
+ // Only call onValidationChange if errors actually changed
413
+ const errorsString = JSON.stringify(errorMessages);
414
+ const deviceForComparison = variant === HTML_EDITOR_VARIANTS.INAPP ? content?.activeDevice : null;
415
+ const deviceChanged = lastActiveDeviceRef.current !== deviceForComparison;
416
+
417
+ if (lastValidationErrorsRef.current !== errorsString || deviceChanged) {
418
+ lastValidationErrorsRef.current = errorsString;
419
+ lastActiveDeviceRef.current = deviceForComparison;
420
+ onValidationChange(errorMessages);
421
+ }
422
+ }
423
+ }, [validation?.summary?.totalErrors, validation?.summary?.totalWarnings, validation?.isValidating, variant, content?.activeDevice, onValidationChange]);
424
+
360
425
  // Context value for providers
361
426
  const contextValue = useMemo(() => ({
362
427
  variant,
@@ -566,6 +631,7 @@ HTMLEditor.propTypes = {
566
631
  onTagSelect: PropTypes.func,
567
632
  onContextChange: PropTypes.func, // Deprecated: use globalActions instead
568
633
  globalActions: PropTypes.object, // Redux actions for API calls
634
+ onValidationChange: PropTypes.func, // Callback to propagate validation errors to parent
569
635
  };
570
636
 
571
637
  HTMLEditor.defaultProps = {
@@ -587,6 +653,7 @@ HTMLEditor.defaultProps = {
587
653
  onTagSelect: null,
588
654
  onContextChange: null,
589
655
  globalActions: null,
656
+ onValidationChange: null,
590
657
  };
591
658
 
592
659
  // Export with forwardRef to allow direct access to CodeEditorPane via ref
@@ -54,16 +54,31 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
54
54
  const { ANDROID, IOS } = DEVICE_TYPES;
55
55
 
56
56
  // Device-specific content state with optional chaining
57
+ // FIX: If iOS content is empty but Android has content, use Android content for iOS
58
+ // This handles the case where "keepContentSame" was used during save
59
+ const initialAndroidContent = initialContent?.[ANDROID] || DEFAULT_INAPP_CONTENT[ANDROID];
60
+ const initialIosContent = initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS];
61
+ const iosContentToUse = (!initialIosContent || initialIosContent.trim() === '') && initialAndroidContent && initialAndroidContent.trim() !== ''
62
+ ? initialAndroidContent
63
+ : initialIosContent;
64
+
65
+ // Check if both devices have the same content initially
66
+ const initialHasSameContent = initialAndroidContent && iosContentToUse
67
+ && initialAndroidContent.trim() !== ''
68
+ && iosContentToUse.trim() !== ''
69
+ && initialAndroidContent.trim() === iosContentToUse.trim();
70
+
57
71
  const [deviceContent, setDeviceContent] = useState(() => ({
58
- [ANDROID]: initialContent?.[ANDROID] || DEFAULT_INAPP_CONTENT[ANDROID],
59
- [IOS]: initialContent?.[IOS] || DEFAULT_INAPP_CONTENT[IOS],
72
+ [ANDROID]: initialAndroidContent,
73
+ [IOS]: iosContentToUse,
60
74
  }));
61
75
 
62
76
  // Current active device
63
77
  const [activeDevice, setActiveDevice] = useState(ANDROID);
64
78
 
65
79
  // Content sync state
66
- const [keepContentSame, setKeepContentSame] = useState(false);
80
+ // FIX: Initialize keepContentSame to true if both devices have the same content initially
81
+ const [keepContentSame, setKeepContentSame] = useState(initialHasSameContent);
67
82
 
68
83
  // Dirty state tracking
69
84
  const [isDirty, setIsDirty] = useState(false);
@@ -105,6 +120,13 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
105
120
  && (!previousContentRef.current.ios || previousContentRef.current.ios.trim() === '');
106
121
  const isTransitioningToContent = wasEmpty && hasMeaningfulContent;
107
122
 
123
+ // FIX: Check if both devices have the same content (indicates "keepContentSame" was used)
124
+ // If they do, automatically enable content sync
125
+ const hasSameContent = newAndroidContent && newIosContent
126
+ && newAndroidContent.trim() !== ''
127
+ && newIosContent.trim() !== ''
128
+ && newAndroidContent.trim() === newIosContent.trim();
129
+
108
130
  // Only update if:
109
131
  // 1. We haven't loaded initial content yet (first load), OR
110
132
  // 2. We're transitioning from empty to meaningful content (library mode data fetch)
@@ -131,6 +153,24 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
131
153
  return updated ? updatedContent : prev;
132
154
  });
133
155
 
156
+ // FIX: If both devices have the same content, enable content sync
157
+ // This ensures the "keepContentSame" checkbox is checked when loading templates
158
+ // that were saved with the same content for both devices
159
+ // Also check if iOS is empty but Android has content (same content scenario)
160
+ const iosEmptyButAndroidHasContent = (!newIosContent || newIosContent.trim() === '')
161
+ && newAndroidContent && newAndroidContent.trim() !== '';
162
+
163
+ if ((hasSameContent || iosEmptyButAndroidHasContent) && !keepContentSame) {
164
+ setKeepContentSame(true);
165
+ // If iOS is empty but Android has content, sync iOS to Android
166
+ if (iosEmptyButAndroidHasContent) {
167
+ setDeviceContent((prev) => ({
168
+ ...prev,
169
+ [IOS]: newAndroidContent,
170
+ }));
171
+ }
172
+ }
173
+
134
174
  // Update previous content ref
135
175
  previousContentRef.current = {
136
176
  android: newAndroidContent || '',
@@ -138,7 +178,7 @@ export const useInAppContent = (initialContent = {}, options = {}) => {
138
178
  };
139
179
  }
140
180
  }
141
- }, [initialContent, ANDROID, IOS]);
181
+ }, [initialContent, ANDROID, IOS, keepContentSame]);
142
182
 
143
183
  // Get current active content
144
184
  const currentContent = useMemo(() => deviceContent?.[activeDevice] || '', [deviceContent, activeDevice]);
@@ -208,8 +208,14 @@ export const InApp = (props) => {
208
208
  }
209
209
  }
210
210
  //cleanup code
211
+ // FIX ISSUE #4: Reset edit flow state when component unmounts or id changes
212
+ // This ensures templates can be edited multiple times
211
213
  return () => {
212
214
  actions.resetEditTemplate();
215
+ setEditFlow(false);
216
+ // Reset content states to allow fresh edit
217
+ setHtmlContentAndroid('');
218
+ setHtmlContentIos('');
213
219
  };
214
220
  }, [paramObj.id]);
215
221
 
@@ -262,7 +268,10 @@ export const InApp = (props) => {
262
268
  setEditFlow(true);
263
269
  setTempName(name);
264
270
  setTemplateDate(createdAt);
265
- setTemplateLayoutType(editContent?.ANDROID?.bodyType);
271
+ // FIX ISSUE #3: When changing layout type, check both Android and iOS bodyType
272
+ // Prefer Android bodyType, but fall back to iOS if Android is not available
273
+ const layoutType = editContent?.ANDROID?.bodyType || editContent?.IOS?.bodyType;
274
+ setTemplateLayoutType(layoutType);
266
275
  // Call showTemplateName callback when in edit mode + full mode to show template name header
267
276
  if (showTemplateName && name) {
268
277
  showTemplateName({
@@ -288,7 +297,7 @@ export const InApp = (props) => {
288
297
  title: androidTitle = '',
289
298
  message: androidMessage = '',
290
299
  type: androidType = '',
291
- ctas: androidCta = {},
300
+ ctas: androidCta = null,
292
301
  expandableDetails: androidExpandableDetails = {},
293
302
  } = androidContent || {};
294
303
  const {
@@ -315,6 +324,9 @@ export const InApp = (props) => {
315
324
  // Initialize HTML content for HTMLEditor if feature is enabled and it's an HTML template
316
325
  if (androidIsHTML) {
317
326
  setHtmlContentAndroid(androidMessage);
327
+ // FIX: Also initialize iOS content with Android content if iOS is empty
328
+ // This handles the case where "keepContentSame" was used during save
329
+ // We'll check iOS content below, but pre-initialize here to ensure it's set
318
330
  }
319
331
  setTemplateMediaType(
320
332
  androidIsHTML ? INAPP_MEDIA_TYPES.HTML
@@ -323,59 +335,82 @@ export const InApp = (props) => {
323
335
  : INAPP_MEDIA_TYPES.IMAGE
324
336
  );
325
337
  setInAppImageSrcAndroid(androidImage);
326
- setDeepLinkValueAndroid(androidCta[0]?.actionLink || '');
338
+ setDeepLinkValueAndroid(androidCta && Array.isArray(androidCta) && androidCta[0]?.actionLink ? androidCta[0].actionLink : '');
327
339
  setAddActionLinkAndroid(!isEmpty(androidCta) && true);
328
340
  setButtonTypeAndroid(
329
341
  androidCtaLength ? INAPP_BUTTON_TYPES.CTA : INAPP_BUTTON_TYPES.NONE
330
342
  );
331
- if (androidCtaLength > 0) {
343
+ if (androidCtaLength > 0 && androidCtas && Array.isArray(androidCtas)) {
332
344
  setCtaDataAndroid(getCtaObject(androidCtas));
333
345
  }
334
346
  }
335
347
  const iosContent = editContent?.IOS;
336
- if (!isEmpty(iosContent)) {
337
- const {
338
- title: iosTitle = '',
339
- message: iosMessage = '',
340
- type: iosType = '',
341
- ctas: iosCta = {},
342
- expandableDetails: iosExpandableDetails = {},
343
- } = iosContent || {};
344
- const {
345
- style: iosStyle,
346
- image: iosImage,
347
- ctas: iosCtas,
348
- } = iosExpandableDetails || {};
349
- const iosCtaLength = iosCtas?.length;
350
-
351
- // Check if this is an HTML template (if Android wasn't HTML, check iOS)
352
- // Note: androidIsHTML is in the outer scope from the Android content check above
353
- if (!androidIsHTML) {
354
- // Check if this is a Bee editor template
355
- const isBEEeditor = get(iosContent, 'isBEEeditor');
356
- const isBeeFreeTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'bee free template';
357
- // Check if this is an HTML editor template (identified by special title)
358
- const isHTMLEditorTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'html editor template';
359
- // Check if this is an HTML template
360
- // Prioritize HTML editor template title, then check type/style
361
- // But exclude if it's a Bee editor template
362
- const iosIsHTML = isHTMLEditorTemplate || ((iosType === INAPP_MEDIA_TYPES.HTML || iosStyle === BIG_HTML)
363
- && !isBEEeditor
364
- && !isBeeFreeTemplate);
365
- setIsHTMLTemplate(iosIsHTML);
366
- // Initialize HTML content for HTMLEditor if feature is enabled and it's an HTML template
367
- if (iosIsHTML) {
368
- setHtmlContentIos(iosMessage);
369
- }
370
- } else {
371
- // If Android is HTML, also initialize iOS HTML content if available
372
- if (androidIsHTML) {
373
- setHtmlContentIos(iosMessage);
374
- }
348
+ // FIX ISSUE #3: Check for iOS content with deviceType === IOS || IOS_CAPITAL
349
+ const iosDeviceType = get(iosContent, 'deviceType');
350
+ const isIosDevice = iosDeviceType === IOS || iosDeviceType === IOS_CAPITAL;
351
+
352
+ // FIX: Always process iOS content initialization, even if iosContent is empty
353
+ // This handles the case where "keepContentSame" was used and iOS content might be empty
354
+ const {
355
+ title: iosTitle = '',
356
+ message: iosMessage = '',
357
+ type: iosType = '',
358
+ ctas: iosCta = null,
359
+ expandableDetails: iosExpandableDetails = {},
360
+ } = iosContent || {};
361
+ const {
362
+ style: iosStyle,
363
+ image: iosImage,
364
+ ctas: iosCtas,
365
+ } = iosExpandableDetails || {};
366
+ const iosCtaLength = iosCtas?.length;
367
+
368
+ // FIX: If iOS content is empty but Android has content, use Android content for iOS
369
+ // This handles the case where "keepContentSame" was used during save
370
+ const androidMsg = androidContent?.message || '';
371
+ const iosMessageToUse = (iosMessage && iosMessage.trim() !== '') ? iosMessage : androidMsg;
372
+
373
+ // Check if this is an HTML template (if Android wasn't HTML, check iOS)
374
+ // Note: androidIsHTML is in the outer scope from the Android content check above
375
+ if (!androidIsHTML) {
376
+ // Check if this is a Bee editor template
377
+ const isBEEeditor = get(iosContent, 'isBEEeditor');
378
+ const isBeeFreeTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'bee free template';
379
+ // Check if this is an HTML editor template (identified by special title)
380
+ const isHTMLEditorTemplate = !isEmpty(iosTitle) && iosTitle.toLowerCase() === 'html editor template';
381
+ // Check if this is an HTML template
382
+ // Prioritize HTML editor template title, then check type/style
383
+ // But exclude if it's a Bee editor template
384
+ const iosIsHTML = isHTMLEditorTemplate || ((iosType === INAPP_MEDIA_TYPES.HTML || iosStyle === BIG_HTML)
385
+ && !isBEEeditor
386
+ && !isBeeFreeTemplate);
387
+ setIsHTMLTemplate(iosIsHTML);
388
+ // Initialize HTML content for HTMLEditor if feature is enabled and it's an HTML template
389
+ if (iosIsHTML) {
390
+ setHtmlContentIos(iosMessageToUse);
375
391
  }
376
-
392
+ } else {
393
+ // If Android is HTML, also initialize iOS HTML content
394
+ if (androidIsHTML) {
395
+ // Use iOS message if available, otherwise use Android message
396
+ setHtmlContentIos(iosMessageToUse);
397
+ }
398
+ }
399
+
400
+ // FIX: Also handle the case where iOS content doesn't exist in backend but Android is HTML
401
+ // This ensures iOS content is set even when iOS content object is empty
402
+ // This is critical for "keepContentSame" scenario where iOS might be empty in backend
403
+ if (androidIsHTML && androidContent && (!iosContent || isEmpty(iosContent) || !iosMessage || iosMessage.trim() === '')) {
404
+ const androidMsgForIos = androidContent?.message || '';
405
+ if (androidMsgForIos && androidMsgForIos.trim() !== '') {
406
+ setHtmlContentIos(androidMsgForIos);
407
+ }
408
+ }
409
+
410
+ // Only set other iOS properties if iOS content exists or is a valid iOS device
411
+ if (!isEmpty(iosContent) || isIosDevice) {
377
412
  setTitleIos(iosTitle);
378
- setTemplateMessageIos(iosMessage);
413
+ setTemplateMessageIos(iosMessageToUse);
379
414
  // Update templateMediaType if iOS is HTML and Android wasn't
380
415
  if (!androidIsHTML) {
381
416
  // Check if this is a Bee editor template
@@ -397,8 +432,8 @@ export const InApp = (props) => {
397
432
  setInAppImageSrcIos(iosImage);
398
433
  setButtonTypeIos(iosCtaLength ? INAPP_BUTTON_TYPES.CTA : INAPP_BUTTON_TYPES.NONE);
399
434
  setAddActionLinkIos(!isEmpty(iosCta) && true);
400
- setDeepLinkValueIos(iosCta[0]?.actionLink || '');
401
- if (iosCtaLength > 0) {
435
+ setDeepLinkValueIos(iosCta && Array.isArray(iosCta) && iosCta[0]?.actionLink ? iosCta[0].actionLink : '');
436
+ if (iosCtaLength > 0 && iosCtas && Array.isArray(iosCtas)) {
402
437
  setCtaDataIos(getCtaObject(iosCtas));
403
438
  }
404
439
  }
@@ -501,8 +536,12 @@ export const InApp = (props) => {
501
536
  // tag Code end
502
537
 
503
538
  const onTemplateLayoutTypeChange = ({ target: { value } }) => {
539
+ // FIX ISSUE #3: When changing layout type, preserve iOS content
504
540
  // Update layout type and force HTMLEditor to remount with latest content
505
541
  // Increment version to force remount - this ensures editor displays current content
542
+ // Store current content in refs before remounting to preserve it
543
+ htmlContentAndroidRef.current = htmlContentAndroid;
544
+ htmlContentIosRef.current = htmlContentIos;
506
545
  setTemplateLayoutType(value);
507
546
  setHtmlEditorContentVersion(prev => prev + 1);
508
547
  };
@@ -626,8 +665,8 @@ export const InApp = (props) => {
626
665
  const iosMediaValid = !iosSupported || (templateMediaType === INAPP_MEDIA_TYPES.TEXT || inAppImageSrcIos !== '');
627
666
 
628
667
  // Check CTA requirements
629
- const androidCtaValid = !isBtnTypeCtaAndroid || (ctaDataAndroid[0]?.isSaved);
630
- const iosCtaValid = !isBtnTypeCTaIos || (ctaDataIos[0]?.isSaved);
668
+ const androidCtaValid = !isBtnTypeCtaAndroid || (ctaDataAndroid && Array.isArray(ctaDataAndroid) && ctaDataAndroid[0]?.isSaved);
669
+ const iosCtaValid = !isBtnTypeCTaIos || (ctaDataIos && Array.isArray(ctaDataIos) && ctaDataIos[0]?.isSaved);
631
670
 
632
671
  // Check action link requirements
633
672
  const androidActionLinkValid = !addActionLinkAndroid || deepLinkValueAndroid;
@@ -687,7 +726,7 @@ export const InApp = (props) => {
687
726
  }
688
727
  //cta
689
728
  if (isBtnTypeCtaAndroid) {
690
- if (ctaDataAndroid[0]?.isSaved) {
729
+ if (ctaDataAndroid && Array.isArray(ctaDataAndroid) && ctaDataAndroid[0]?.isSaved) {
691
730
  //if button type is cta and 1st button is saved
692
731
  return false;
693
732
  }
@@ -695,7 +734,7 @@ export const InApp = (props) => {
695
734
  return true;
696
735
  }
697
736
  if (isBtnTypeCTaIos) {
698
- if (ctaDataIos[0]?.isSaved) {
737
+ if (ctaDataIos && Array.isArray(ctaDataIos) && ctaDataIos[0]?.isSaved) {
699
738
  //if button type is cta and 1st button is saved
700
739
  return false;
701
740
  }
@@ -714,14 +753,20 @@ export const InApp = (props) => {
714
753
  return false;
715
754
  };
716
755
 
717
- const getCtaPayload = (type) => (type === ANDROID ? ctaDataAndroid : ctaDataIos).map((cta) => {
718
- const { text, urlType, url } = cta;
719
- return {
720
- type: urlType,
721
- actionText: text,
722
- actionLink: url,
723
- };
724
- });
756
+ const getCtaPayload = (type) => {
757
+ const ctaData = type === ANDROID ? ctaDataAndroid : ctaDataIos;
758
+ if (!ctaData || !Array.isArray(ctaData)) {
759
+ return [];
760
+ }
761
+ return ctaData.map((cta) => {
762
+ const { text, urlType, url } = cta;
763
+ return {
764
+ type: urlType,
765
+ actionText: text,
766
+ actionLink: url,
767
+ };
768
+ });
769
+ };
725
770
 
726
771
  const createPayload = () => {
727
772
  let androidMediaParams = {};
@@ -765,11 +810,21 @@ export const InApp = (props) => {
765
810
 
766
811
  // Check account-level device support
767
812
  const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
768
- const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
813
+ const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL || get(editContent, 'IOS.deviceType') === IOS;
769
814
 
770
815
  // Check if devices have content (for HTML Editor)
771
- const hasAndroidContent = htmlContentAndroid && htmlContentAndroid?.trim() !== '';
772
- const hasIosContent = htmlContentIos && htmlContentIos?.trim() !== '';
816
+ // If Android has content but iOS doesn't, and both devices are supported, check if content should be synced
817
+ // This handles the case where "keepContentSame" checkbox was used during creation
818
+ let hasAndroidContent = htmlContentAndroid && htmlContentAndroid?.trim() !== '';
819
+ let hasIosContent = htmlContentIos && htmlContentIos?.trim() !== '';
820
+
821
+ // FIX ISSUE #2: If Android has content but iOS doesn't, and both are supported,
822
+ // check if we should copy Android content to iOS (for "same content" scenario)
823
+ if (hasAndroidContent && !hasIosContent && androidSupported && iosSupported && isHTMLTemplate) {
824
+ // In create mode, if Android has content and iOS doesn't, use Android content for iOS
825
+ // This handles the "keepContentSame" checkbox scenario
826
+ hasIosContent = true; // Mark as having content so iOS is included in payload
827
+ }
773
828
 
774
829
  // For HTML Editor, check if device has content and is supported
775
830
  // For legacy editor, use isDisableDone check
@@ -802,10 +857,30 @@ export const InApp = (props) => {
802
857
  } : {};
803
858
 
804
859
  // Use HTML content if HTMLEditor is enabled and it's an HTML template, otherwise use regular message
805
- const iosMessage = isHTMLTemplate && htmlContentIos
860
+ // FIX ISSUE #2: If iOS content is empty but Android has content (same content scenario), use Android content
861
+ let iosMessage = isHTMLTemplate && htmlContentIos
806
862
  ? htmlContentIos
807
863
  : templateMessageIos;
808
864
 
865
+ // FIX: If both devices have the same content (keepContentSame scenario), ensure iOS uses Android content
866
+ // This handles the case where keepContentSame is enabled and both should have the same content
867
+ const bothHaveSameContent = isHTMLTemplate && androidMessage && iosMessage
868
+ && androidMessage.trim() === iosMessage.trim()
869
+ && androidMessage.trim() !== '';
870
+
871
+ // If iOS message is empty but Android has content and both devices are supported, use Android content
872
+ // OR if both have the same content (keepContentSame), ensure they're both set correctly
873
+ if (isHTMLTemplate && androidSupported && iosSupported) {
874
+ if ((!iosMessage || iosMessage.trim() === '') && androidMessage && androidMessage.trim() !== '') {
875
+ iosMessage = androidMessage;
876
+ hasIosContent = true; // Mark as having content
877
+ } else if (bothHaveSameContent) {
878
+ // Ensure both are using the same content (in case of any discrepancies)
879
+ iosMessage = androidMessage;
880
+ hasIosContent = true;
881
+ }
882
+ }
883
+
809
884
  // Determine type and style for iOS
810
885
  const iosType = isHTMLTemplate ? INAPP_MEDIA_TYPES.HTML : templateMediaType;
811
886
  const iosExpandableStyle = isHTMLTemplate ? BIG_HTML : BIG_TEXT;
@@ -909,11 +984,39 @@ export const InApp = (props) => {
909
984
  );
910
985
  };
911
986
 
987
+ // Handle validation errors from HTMLEditor
988
+ const handleHtmlValidationErrors = useCallback((validationErrors) => {
989
+ // Update errorMessage state with validation errors from HTMLEditor
990
+ setErrorMessage((prev) => {
991
+ // Merge validation errors, preserving any existing errors from other sources
992
+ const newErrorState = {
993
+ STANDARD_ERROR_MSG: {
994
+ ANDROID: validationErrors.STANDARD_ERROR_MSG?.ANDROID || prev.STANDARD_ERROR_MSG?.ANDROID || [],
995
+ IOS: validationErrors.STANDARD_ERROR_MSG?.IOS || prev.STANDARD_ERROR_MSG?.IOS || [],
996
+ GENERIC: validationErrors.STANDARD_ERROR_MSG?.GENERIC || prev.STANDARD_ERROR_MSG?.GENERIC || [],
997
+ },
998
+ LIQUID_ERROR_MSG: {
999
+ ANDROID: validationErrors.LIQUID_ERROR_MSG?.ANDROID || prev.LIQUID_ERROR_MSG?.ANDROID || [],
1000
+ IOS: validationErrors.LIQUID_ERROR_MSG?.IOS || prev.LIQUID_ERROR_MSG?.IOS || [],
1001
+ GENERIC: validationErrors.LIQUID_ERROR_MSG?.GENERIC || prev.LIQUID_ERROR_MSG?.GENERIC || [],
1002
+ },
1003
+ };
1004
+ return newErrorState;
1005
+ });
1006
+ }, []);
1007
+
912
1008
  // Handle HTML content changes from HTMLEditor
913
1009
  const handleHtmlContentChange = useCallback((deviceContent, changedDevice) => {
914
1010
  // The onChange callback from useInAppContent passes the full deviceContent object
915
- // But we only want to update the state for the device that actually changed
916
- // Use the second parameter (changedDevice) if provided, otherwise update both
1011
+ // When keepContentSame is enabled, both android and ios will have the same content
1012
+ // We need to detect this and update both states accordingly
1013
+
1014
+ // FIX: Check if both devices have the same content (indicates keepContentSame is enabled)
1015
+ // When keepContentSame is enabled, useInAppContent passes both android and ios with the same value
1016
+ const hasBothDevices = deviceContent?.android !== undefined && deviceContent?.ios !== undefined;
1017
+ const androidContent = typeof deviceContent?.android === 'string' ? deviceContent.android : '';
1018
+ const iosContent = typeof deviceContent?.ios === 'string' ? deviceContent.ios : '';
1019
+ const isSyncMode = hasBothDevices && androidContent === iosContent;
917
1020
 
918
1021
  // Clear validation errors when content changes (similar to Bee Editor)
919
1022
  // This ensures Done button re-enables after user fixes errors
@@ -932,8 +1035,18 @@ export const InApp = (props) => {
932
1035
  }));
933
1036
  }
934
1037
 
935
- if (changedDevice) {
936
- // Only update the device that actually changed
1038
+ // FIX: If sync mode (keepContentSame is enabled), update both devices
1039
+ if (isSyncMode) {
1040
+ // Update both Android and iOS with the same content
1041
+ // Use the Android content for both to ensure they're exactly the same
1042
+ const syncContent = androidContent || '';
1043
+ setHtmlContentAndroid(syncContent);
1044
+ setHtmlContentIos(syncContent);
1045
+ // Also update refs to ensure latest values are available immediately
1046
+ htmlContentAndroidRef.current = syncContent;
1047
+ htmlContentIosRef.current = syncContent;
1048
+ } else if (changedDevice) {
1049
+ // Only update the device that actually changed (when sync is disabled)
937
1050
  if (changedDevice.toUpperCase() === ANDROID && deviceContent?.android !== undefined) {
938
1051
  setHtmlContentAndroid(deviceContent.android || '');
939
1052
  } else if (changedDevice.toUpperCase() === IOS_CAPITAL && deviceContent?.ios !== undefined) {
@@ -955,16 +1068,35 @@ export const InApp = (props) => {
955
1068
  });
956
1069
  }
957
1070
  }
958
- }, [ANDROID, IOS, setErrorMessage]);
1071
+ }, [ANDROID, IOS_CAPITAL, setErrorMessage]);
959
1072
 
960
1073
  // Handle HTML save from HTMLEditor
961
1074
  const handleHtmlSave = useCallback((deviceContent) => {
962
- // Update state for both devices if present in the callback
963
- if (deviceContent?.android !== undefined) {
964
- setHtmlContentAndroid(deviceContent.android || '');
965
- }
966
- if (deviceContent?.ios !== undefined) {
967
- setHtmlContentIos(deviceContent.ios || '');
1075
+ // FIX: Check if both devices have the same content (indicates keepContentSame is enabled)
1076
+ // When keepContentSame is enabled, useInAppContent passes both android and ios with the same value
1077
+ const hasBothDevices = deviceContent?.android !== undefined && deviceContent?.ios !== undefined;
1078
+ const androidContent = typeof deviceContent?.android === 'string' ? deviceContent.android : '';
1079
+ const iosContent = typeof deviceContent?.ios === 'string' ? deviceContent.ios : '';
1080
+ const isSyncMode = hasBothDevices && androidContent === iosContent;
1081
+
1082
+ // FIX: If sync mode (keepContentSame is enabled), update both devices
1083
+ if (isSyncMode) {
1084
+ // Update both Android and iOS with the same content
1085
+ // Use the Android content for both to ensure they're exactly the same
1086
+ const syncContent = androidContent || '';
1087
+ setHtmlContentAndroid(syncContent);
1088
+ setHtmlContentIos(syncContent);
1089
+ // Also update refs to ensure latest values are available immediately
1090
+ htmlContentAndroidRef.current = syncContent;
1091
+ htmlContentIosRef.current = syncContent;
1092
+ } else {
1093
+ // Update state for both devices if present in the callback
1094
+ if (deviceContent?.android !== undefined) {
1095
+ setHtmlContentAndroid(deviceContent.android || '');
1096
+ }
1097
+ if (deviceContent?.ios !== undefined) {
1098
+ setHtmlContentIos(deviceContent.ios || '');
1099
+ }
968
1100
  }
969
1101
  // Auto-save can trigger this, but we don't want to submit automatically
970
1102
  // The user will click the Create/Update button to submit
@@ -1211,34 +1343,57 @@ export const InApp = (props) => {
1211
1343
  />
1212
1344
  </>
1213
1345
  )}
1214
- {shouldUseHTMLEditor ? (
1215
- <HTMLEditor
1216
- key={`inapp-html-editor-v${htmlEditorContentVersion}`}
1217
- variant={HTML_EDITOR_VARIANTS.INAPP}
1218
- layoutType={templateLayoutType}
1219
- initialContent={{
1220
- android: htmlContentAndroidRef.current || htmlContentAndroid || templateMessageAndroid || '',
1221
- ios: htmlContentIosRef.current || htmlContentIos || templateMessageIos || '',
1222
- }}
1223
- onContentChange={handleHtmlContentChange}
1224
- onSave={handleHtmlSave}
1225
- tags={tags}
1226
- injectedTags={injectedTags}
1227
- location={location}
1228
- selectedOfferDetails={selectedOfferDetails}
1229
- onTagSelect={() => {
1230
- // Tag insertion is handled by HTMLEditor's CodeEditorPane at cursor position
1231
- // Content updates will be propagated via onContentChange callback
1232
- }}
1233
- // Don't pass globalActions to prevent duplicate API calls
1234
- // HTMLEditor will use onContextChange instead
1235
- onContextChange={handleOnTagsContextChange}
1236
- // Pass validation errors to HTMLEditor for display
1237
- errors={errorMessage}
1238
- data-test="inapp-html-editor"
1239
- style={{ width: '138%' }}
1240
- />
1241
- ) : (
1346
+ {shouldUseHTMLEditor ? (() => {
1347
+ // FIX: When loading edit content, check if Android and iOS have the same content
1348
+ // If they do, ensure both are initialized with the same content
1349
+ const androidContentValue = htmlContentAndroidRef.current || htmlContentAndroid || templateMessageAndroid || '';
1350
+ const iosContentValue = htmlContentIosRef.current || htmlContentIos || templateMessageIos || '';
1351
+
1352
+ // Check if both devices have the same content (indicates "keepContentSame" was used)
1353
+ // Also check if iOS is empty but Android has content (same content scenario)
1354
+ const hasSameContent = androidContentValue && iosContentValue && androidContentValue.trim() === iosContentValue.trim();
1355
+ const iosEmptyButAndroidHasContent = (!iosContentValue || iosContentValue.trim() === '') && androidContentValue && androidContentValue.trim() !== '';
1356
+
1357
+ // If content is the same OR iOS is empty but Android has content, ensure both devices get the same content
1358
+ // This ensures the "keepContentSame" checkbox state is correctly reflected
1359
+ const initialContentForEditor = (hasSameContent || iosEmptyButAndroidHasContent)
1360
+ ? {
1361
+ android: androidContentValue,
1362
+ ios: androidContentValue, // Use Android content for both
1363
+ }
1364
+ : {
1365
+ android: androidContentValue,
1366
+ ios: iosContentValue,
1367
+ };
1368
+
1369
+ return (
1370
+ <HTMLEditor
1371
+ key={`inapp-html-editor-v${htmlEditorContentVersion}`}
1372
+ variant={HTML_EDITOR_VARIANTS.INAPP}
1373
+ layoutType={templateLayoutType}
1374
+ initialContent={initialContentForEditor}
1375
+ onContentChange={handleHtmlContentChange}
1376
+ onSave={handleHtmlSave}
1377
+ tags={tags}
1378
+ injectedTags={injectedTags}
1379
+ location={location}
1380
+ selectedOfferDetails={selectedOfferDetails}
1381
+ onTagSelect={() => {
1382
+ // Tag insertion is handled by HTMLEditor's CodeEditorPane at cursor position
1383
+ // Content updates will be propagated via onContentChange callback
1384
+ }}
1385
+ // Don't pass globalActions to prevent duplicate API calls
1386
+ // HTMLEditor will use onContextChange instead
1387
+ onContextChange={handleOnTagsContextChange}
1388
+ // Pass validation errors to HTMLEditor for display
1389
+ errors={errorMessage}
1390
+ // Pass callback to receive validation errors from HTMLEditor
1391
+ onValidationChange={handleHtmlValidationErrors}
1392
+ data-test="inapp-html-editor"
1393
+ style={{ width: '138%' }}
1394
+ />
1395
+ );
1396
+ })() : (
1242
1397
  <InappAdvanced
1243
1398
  getFormData={getFormData}
1244
1399
  setIsLoadingContent={setIsLoadingContent}
@@ -143,6 +143,17 @@ export const InappAdvanced = (props) => {
143
143
  } = isFullMode ? editData?.templateDetails || {} : templateData || {};
144
144
  editContent = get(versions, `base.content`, {});
145
145
 
146
+ // LIBRARY MODE FIX: Backend stores bodyType as 'POPUP' but UI expects 'MODAL'
147
+ // Convert back to MODAL for display (same as HTML Editor)
148
+ if (!isFullMode && editContent) {
149
+ if (editContent.ANDROID?.bodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
150
+ editContent.ANDROID.bodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
151
+ }
152
+ if (editContent.IOS?.bodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
153
+ editContent.IOS.bodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
154
+ }
155
+ }
156
+
146
157
  if (editContent && !isEmpty(editContent)) {
147
158
  setEditFlow(true);
148
159
  if (setTemplateName && name) {
@@ -171,7 +182,12 @@ export const InappAdvanced = (props) => {
171
182
  });
172
183
  }
173
184
  // Get layout type from Android or iOS, whichever is available
174
- const layoutType = editContent?.ANDROID?.bodyType || editContent?.IOS?.bodyType;
185
+ // FIX: Convert POPUP to MODAL for UI consistency (backend stores POPUP, UI uses MODAL)
186
+ let layoutType = editContent?.ANDROID?.bodyType || editContent?.IOS?.bodyType;
187
+ // Map POPUP to MODAL so it's treated the same as HTML Editor templates
188
+ if (layoutType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
189
+ layoutType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
190
+ }
175
191
  if (layoutType) {
176
192
  setTemplateLayoutType(layoutType);
177
193
  }
@@ -682,21 +698,80 @@ export const InappAdvanced = (props) => {
682
698
  // Check if BEE editor has content for a device
683
699
  const hasBeeContent = (device) => {
684
700
  if (device === ANDROID) {
685
- // Check state values first (most up-to-date), then fall back to refs
686
- const stateHtmlValue = androidBeeHtml?.value || (typeof androidBeeHtml === 'string' ? androidBeeHtml : '');
687
- const htmlContent = stateHtmlValue || latestAndroidHtmlRef.current || '';
701
+ // Check JSON content first - this is updated immediately when content is added
688
702
  const jsonContent = androidBeeJson && androidBeeJson !== '{}';
689
- // Ensure htmlContent is a string before calling trim
690
- const htmlContentStr = typeof htmlContent === 'string' ? htmlContent : '';
691
- return htmlContentStr && htmlContentStr.trim() !== '' && jsonContent;
703
+
704
+ // If JSON is empty, there's no content
705
+ if (!jsonContent) {
706
+ return false;
707
+ }
708
+
709
+ // Check if JSON has actual content (not just empty structure)
710
+ // Parse JSON to check if it has meaningful content
711
+ try {
712
+ const parsedJson = typeof androidBeeJson === 'string' ? JSON.parse(androidBeeJson) : androidBeeJson;
713
+ // Check if JSON has rows with content (Bee editor structure)
714
+ const hasRows = parsedJson?.page?.rows && Array.isArray(parsedJson.page.rows) && parsedJson.page.rows.length > 0;
715
+ if (!hasRows) {
716
+ return false;
717
+ }
718
+
719
+ // Check if rows have actual modules/content
720
+ const hasContent = parsedJson.page.rows.some((row) => {
721
+ const columns = row?.columns || [];
722
+ return columns.some((col) => {
723
+ const modules = col?.modules || [];
724
+ return modules.length > 0;
725
+ });
726
+ });
727
+
728
+ // If JSON has content, return true (HTML might not be ready yet, but that's okay)
729
+ // The HTML will be generated when saving, but we should enable the button as soon as JSON has content
730
+ return hasContent;
731
+ } catch (e) {
732
+ // If JSON parsing fails, fall back to checking HTML
733
+ const stateHtmlValue = androidBeeHtml?.value || (typeof androidBeeHtml === 'string' ? androidBeeHtml : '');
734
+ const htmlContent = stateHtmlValue || latestAndroidHtmlRef.current || '';
735
+ const htmlContentStr = typeof htmlContent === 'string' ? htmlContent : '';
736
+ return htmlContentStr && htmlContentStr.trim() !== '';
737
+ }
692
738
  }
693
- // Check state values first (most up-to-date), then fall back to refs
694
- const stateHtmlValue = iosBeeHtml?.value || (typeof iosBeeHtml === 'string' ? iosBeeHtml : '');
695
- const htmlContent = stateHtmlValue || latestIosHtmlRef.current || '';
739
+
740
+ // iOS device - same logic as Android
696
741
  const jsonContent = iosBeeJson && iosBeeJson !== '{}';
697
- // Ensure htmlContent is a string before calling trim
698
- const htmlContentStr = typeof htmlContent === 'string' ? htmlContent : '';
699
- return htmlContentStr && htmlContentStr.trim() !== '' && jsonContent;
742
+
743
+ // If JSON is empty, there's no content
744
+ if (!jsonContent) {
745
+ return false;
746
+ }
747
+
748
+ // Check if JSON has actual content (not just empty structure)
749
+ try {
750
+ const parsedJson = typeof iosBeeJson === 'string' ? JSON.parse(iosBeeJson) : iosBeeJson;
751
+ // Check if JSON has rows with content (Bee editor structure)
752
+ const hasRows = parsedJson?.page?.rows && Array.isArray(parsedJson.page.rows) && parsedJson.page.rows.length > 0;
753
+ if (!hasRows) {
754
+ return false;
755
+ }
756
+
757
+ // Check if rows have actual modules/content
758
+ const hasContent = parsedJson.page.rows.some((row) => {
759
+ const columns = row?.columns || [];
760
+ return columns.some((col) => {
761
+ const modules = col?.modules || [];
762
+ return modules.length > 0;
763
+ });
764
+ });
765
+
766
+ // If JSON has content, return true (HTML might not be ready yet, but that's okay)
767
+ return hasContent;
768
+ } catch (e) {
769
+ // If JSON parsing fails, fall back to checking HTML
770
+ const stateHtmlValue = iosBeeHtml?.value || (typeof iosBeeHtml === 'string' ? iosBeeHtml : '');
771
+ const htmlContent = stateHtmlValue || latestIosHtmlRef.current || '';
772
+ const htmlContentStr = typeof htmlContent === 'string' ? htmlContent : '';
773
+ return htmlContentStr && htmlContentStr.trim() !== '';
774
+ }
700
775
  };
701
776
 
702
777
  // Check if Done button should be disabled
@@ -709,11 +784,9 @@ export const InappAdvanced = (props) => {
709
784
  // Get account-level device support
710
785
  const androidSupported = get(accountData, 'selectedWeChatAccount.configs.android') === DEVICE_SUPPORTED || get(editContent, 'ANDROID.deviceType') === ANDROID;
711
786
  const iosSupported = get(accountData, 'selectedWeChatAccount.configs.ios') === DEVICE_SUPPORTED || get(editContent, 'IOS.deviceType') === IOS_CAPITAL;
712
-
713
787
  // Check if devices have content
714
788
  const hasAndroidContent = hasBeeContent(ANDROID);
715
789
  const hasIosContent = hasBeeContent(IOS);
716
-
717
790
  // If no tags are available (e.g., in tests), allow submission even without content
718
791
  // This is to support test scenarios where content validation might not be fully set up
719
792
  const hasTags = tags && tags.length > 0;
@@ -1169,7 +1169,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1169
1169
  return templates.filter((template) => {
1170
1170
  const androidBodyType = get(template, 'versions.base.content.ANDROID.bodyType');
1171
1171
  const iosBodyType = get(template, 'versions.base.content.IOS.bodyType');
1172
- const inappBodyType = androidBodyType || iosBodyType;
1172
+ let inappBodyType = androidBodyType || iosBodyType;
1173
+ // FIX: Map POPUP to MODAL for filter consistency (backend stores POPUP, UI uses MODAL)
1174
+ if (inappBodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
1175
+ inappBodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
1176
+ }
1173
1177
  return inappBodyType === selectedInAppLayout;
1174
1178
  });
1175
1179
  }
@@ -1275,7 +1279,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1275
1279
  const cardDataList = filteredTemplates && filteredTemplates.length ? map(filteredTemplates, (template) => {
1276
1280
  const androidBodyType = get(template, 'versions.base.content.ANDROID.bodyType');
1277
1281
  const iosBodyType = get(template, 'versions.base.content.IOS.bodyType');
1278
- const inappBodyType = androidBodyType || iosBodyType;
1282
+ let inappBodyType = androidBodyType || iosBodyType;
1283
+ // FIX: Map POPUP to MODAL for display consistency (backend stores POPUP, UI shows MODAL/Popup)
1284
+ // This ensures Bee Editor templates show the same as HTML Editor templates
1285
+ if (inappBodyType === INAPP_MESSAGE_LAYOUT_TYPES.POPUP) {
1286
+ inappBodyType = INAPP_MESSAGE_LAYOUT_TYPES.MODAL;
1287
+ }
1279
1288
  const isZaloPreviewLoading = previewTemplateId === template?._id;
1280
1289
  const templateData = {
1281
1290
  key: `${currentChannel}-card-${template?.name}`,