@capillarytech/creatives-library 8.0.271 → 8.0.272

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 (149) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +34 -0
  8. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
  9. package/tests/integration/TemplateCreation/api-response.js +31 -1
  10. package/tests/integration/TemplateCreation/msw-handler.js +2 -0
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +28 -5
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/v2Components/CapDeviceContent/index.js +61 -56
  16. package/v2Components/CapTagList/index.js +6 -1
  17. package/v2Components/CapTagListWithInput/index.js +5 -1
  18. package/v2Components/CapTagListWithInput/messages.js +1 -1
  19. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  20. package/v2Components/ErrorInfoNote/constants.js +1 -0
  21. package/v2Components/ErrorInfoNote/index.js +402 -72
  22. package/v2Components/ErrorInfoNote/messages.js +32 -6
  23. package/v2Components/ErrorInfoNote/style.scss +278 -6
  24. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  25. package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
  26. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
  27. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
  28. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  29. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  30. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  32. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  33. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  34. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  35. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
  36. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
  37. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  43. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
  44. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  45. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  46. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
  47. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
  48. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
  49. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  50. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
  51. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
  53. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  54. package/v2Components/HtmlEditor/constants.js +45 -20
  55. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  56. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
  57. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  58. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  59. package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
  60. package/v2Components/HtmlEditor/index.js +1 -1
  61. package/v2Components/HtmlEditor/messages.js +102 -94
  62. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
  63. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  64. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  65. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  66. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
  67. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  68. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  69. package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
  70. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  71. package/v2Components/MobilePushPreviewV2/index.js +33 -7
  72. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  73. package/v2Components/TemplatePreview/index.js +47 -32
  74. package/v2Components/TemplatePreview/messages.js +4 -0
  75. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  76. package/v2Containers/BeeEditor/index.js +172 -90
  77. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  78. package/v2Containers/BeePopupEditor/constants.js +10 -0
  79. package/v2Containers/BeePopupEditor/index.js +194 -0
  80. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  84. package/v2Containers/CreativesContainer/constants.js +1 -0
  85. package/v2Containers/CreativesContainer/index.js +251 -47
  86. package/v2Containers/CreativesContainer/messages.js +8 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  88. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
  90. package/v2Containers/Email/actions.js +7 -0
  91. package/v2Containers/Email/constants.js +5 -1
  92. package/v2Containers/Email/index.js +234 -29
  93. package/v2Containers/Email/messages.js +32 -0
  94. package/v2Containers/Email/reducer.js +12 -1
  95. package/v2Containers/Email/sagas.js +61 -7
  96. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  97. package/v2Containers/Email/tests/reducer.test.js +46 -0
  98. package/v2Containers/Email/tests/sagas.test.js +320 -29
  99. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
  100. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
  101. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  102. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
  103. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  104. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  105. package/v2Containers/EmailWrapper/constants.js +2 -0
  106. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
  107. package/v2Containers/EmailWrapper/index.js +103 -23
  108. package/v2Containers/EmailWrapper/messages.js +65 -1
  109. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
  110. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
  111. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  112. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  113. package/v2Containers/InApp/actions.js +7 -0
  114. package/v2Containers/InApp/constants.js +20 -4
  115. package/v2Containers/InApp/index.js +802 -360
  116. package/v2Containers/InApp/index.scss +4 -3
  117. package/v2Containers/InApp/messages.js +7 -3
  118. package/v2Containers/InApp/reducer.js +21 -3
  119. package/v2Containers/InApp/sagas.js +29 -9
  120. package/v2Containers/InApp/selectors.js +25 -5
  121. package/v2Containers/InApp/tests/index.test.js +154 -50
  122. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  123. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  124. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  125. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  126. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  127. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  128. package/v2Containers/InAppWrapper/constants.js +16 -0
  129. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  130. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  131. package/v2Containers/InAppWrapper/index.js +148 -0
  132. package/v2Containers/InAppWrapper/messages.js +49 -0
  133. package/v2Containers/InappAdvance/index.js +1099 -0
  134. package/v2Containers/InappAdvance/index.scss +10 -0
  135. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  136. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  137. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  138. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  139. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  140. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  141. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  142. package/v2Containers/TagList/index.js +62 -19
  143. package/v2Containers/Templates/_templates.scss +60 -1
  144. package/v2Containers/Templates/index.js +89 -4
  145. package/v2Containers/Templates/messages.js +4 -0
  146. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  148. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  149. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -2281,6 +2281,7 @@ new message content.",
2281
2281
  "email": [Function],
2282
2282
  "facebookPreview": [Function],
2283
2283
  "gallery": [Function],
2284
+ "inApp": [Function],
2284
2285
  "language": [Function],
2285
2286
  "navigationConfig": [Function],
2286
2287
  "previewAndTest": [Function],
@@ -13480,6 +13481,7 @@ new message content.",
13480
13481
  "email": [Function],
13481
13482
  "facebookPreview": [Function],
13482
13483
  "gallery": [Function],
13484
+ "inApp": [Function],
13483
13485
  "language": [Function],
13484
13486
  "navigationConfig": [Function],
13485
13487
  "previewAndTest": [Function],
@@ -24425,6 +24427,7 @@ new message content.",
24425
24427
  "email": [Function],
24426
24428
  "facebookPreview": [Function],
24427
24429
  "gallery": [Function],
24430
+ "inApp": [Function],
24428
24431
  "language": [Function],
24429
24432
  "navigationConfig": [Function],
24430
24433
  "previewAndTest": [Function],
@@ -35370,6 +35373,7 @@ new message content.",
35370
35373
  "email": [Function],
35371
35374
  "facebookPreview": [Function],
35372
35375
  "gallery": [Function],
35376
+ "inApp": [Function],
35373
35377
  "language": [Function],
35374
35378
  "navigationConfig": [Function],
35375
35379
  "previewAndTest": [Function],
@@ -40,6 +40,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
40
40
  tags: [],
41
41
  tagsError: false,
42
42
  currentContext: null, // Track current context to detect changes
43
+ hasTriggeredInitialApiCall: false, // Track if we've triggered API call when popover opens
43
44
  };
44
45
  this.renderTags = this.renderTags.bind(this);
45
46
  this.populateTags = this.populateTags.bind(this);
@@ -51,6 +52,14 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
51
52
 
52
53
  componentDidMount() {
53
54
  this.generateTags(this.props);
55
+ // Trigger initial API call if tags are empty (similar to Email/SMS behavior)
56
+ const { tags, injectedTags, onContextChange } = this.props;
57
+ const hasNoTags = (!tags || tags.length === 0) && _.isEmpty(injectedTags);
58
+ if (hasNoTags && onContextChange) {
59
+ // Trigger API call with default 'Outbound' context to match CapTagList default
60
+ // This ensures tags are loaded when component mounts
61
+ this.getTagsforContext('Outbound');
62
+ }
54
63
  }
55
64
 
56
65
  componentWillReceiveProps(nextProps) {
@@ -59,23 +68,27 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
59
68
  // 2. Context change is happening (detected by different tag arrays)
60
69
  const { injectedTags: currentInjectedTags, tags: currentTags } = this.props;
61
70
  const { injectedTags: nextInjectedTags, tags: nextTags, fetchingSchemaError } = nextProps;
62
-
71
+
63
72
  const isInitialLoad = _.isEmpty(currentInjectedTags) && _.isEmpty(currentTags);
64
73
  const isContextChange = !_.isEqual(nextTags, currentTags) && !_.isEmpty(currentTags);
65
-
74
+
66
75
  if (isInitialLoad || isContextChange) {
67
76
  this.setState({loading: true});
68
77
  }
69
-
78
+
70
79
  // Only reset loading for injectedTags changes if we're not currently loading due to context change
71
80
  if (!_.isEqual(nextInjectedTags, currentInjectedTags) && !this.state.loading) {
72
81
  this.setState({loading: false});
73
82
  this.clearLoadingTimeout();
74
83
  }
75
-
84
+
76
85
  if (!_.isEqual(nextTags, currentTags)) {
77
86
  this.setState({loading: false});
78
87
  this.clearLoadingTimeout();
88
+ // Reset the flag when tags are received, so we can trigger API call again if needed
89
+ if (nextTags && nextTags.length > 0) {
90
+ this.setState({ hasTriggeredInitialApiCall: false });
91
+ }
79
92
  }
80
93
  if (fetchingSchemaError) {
81
94
  this.setState({tagsError: fetchingSchemaError, loading: false});
@@ -86,7 +99,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
86
99
  componentDidUpdate(prevProps) {
87
100
  const { tags, injectedTags, selectedOfferDetails } = this.props;
88
101
  const { tags: prevTags, injectedTags: prevInjectedTags, selectedOfferDetails: prevSelectedOfferDetails } = prevProps;
89
-
102
+
90
103
  if (tags !== prevTags || injectedTags !== prevInjectedTags || selectedOfferDetails !== prevSelectedOfferDetails) {
91
104
  this.generateTags(this.props);
92
105
  }
@@ -111,17 +124,44 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
111
124
  getTagsforContext = (data) => {
112
125
  // Set loading state when context change is requested
113
126
  this.setState({loading: true, currentContext: data});
114
-
127
+
115
128
  // Set a timeout to prevent infinite loading (fallback safety)
116
129
  this.clearLoadingTimeout();
117
130
  this.loadingTimeout = setTimeout(() => {
118
131
  this.setState({loading: false});
119
132
  }, 5000); // Reduced timeout to 5 seconds for better UX
120
-
133
+
121
134
  const { onContextChange } = this.props;
122
- onContextChange(data);
135
+ // Always call onContextChange if available - this triggers the API call
136
+ // The API call will fetch tags from /meta/TAG endpoint
137
+ if (onContextChange) {
138
+ onContextChange(data);
139
+ } else {
140
+ console.warn('TagList: onContextChange is not available. API call will not be triggered.');
141
+ }
123
142
  }
124
143
 
144
+ handlePopoverVisibilityChange = (visible) => {
145
+ // When popover opens, trigger API call if tags are empty or if we haven't triggered it yet
146
+ // This ensures API call happens when user clicks "Add Label" button
147
+ if (visible && this.props.onContextChange) {
148
+ const { tags, injectedTags } = this.props;
149
+ // Check if tags array is empty or if state tags are empty
150
+ const hasNoTags = (!tags || tags.length === 0) && _.isEmpty(injectedTags);
151
+ const hasNoStateTags = _.isEmpty(this.state.tags);
152
+ const hasNotTriggeredApiCall = !this.state.hasTriggeredInitialApiCall;
153
+
154
+ // Trigger API call if tags are not loaded yet OR if we haven't triggered it yet
155
+ if ((hasNoTags || hasNoStateTags || hasNotTriggeredApiCall)) {
156
+ // Mark that we've triggered the API call
157
+ this.setState({ hasTriggeredInitialApiCall: true });
158
+ // Trigger API call with default 'Outbound' context to match CapTagList default
159
+ // This will call onContextChange which triggers handleOnTagsContextChange in InApp
160
+ this.getTagsforContext('Outbound');
161
+ }
162
+ }
163
+ };
164
+
125
165
  generateTags = (props) => {
126
166
  let tags = {};
127
167
  let injectedTags = {};
@@ -149,11 +189,11 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
149
189
  if (eventContextTags?.length) {
150
190
  const TAG_HEADER_MSG_LABEL = this.props.intl.formatMessage(messages.entryEvent);
151
191
  eventContextTagsObj.eventContextTags = {
152
- name: TAG_HEADER_MSG_LABEL,
153
- desc: TAG_HEADER_MSG_LABEL,
154
- resolved: true,
192
+ "name": TAG_HEADER_MSG_LABEL,
193
+ "desc": TAG_HEADER_MSG_LABEL,
194
+ "resolved": true,
155
195
  'tag-header': true,
156
- subtags: {},
196
+ "subtags": {},
157
197
  };
158
198
 
159
199
  eventContextTags.forEach((tag) => {
@@ -164,11 +204,11 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
164
204
  // Initializing the tags profile if it doesn't exist
165
205
  if (!eventContextTagsObj?.eventContextTags?.subtags?.[profileId]) {
166
206
  eventContextTagsObj.eventContextTags.subtags[profileId] = {
167
- name: profileName,
168
- desc: profileName,
169
- resolved: true,
207
+ "name": profileName,
208
+ "desc": profileName,
209
+ "resolved": true,
170
210
  'tag-header': true,
171
- subtags: {},
211
+ "subtags": {},
172
212
  };
173
213
  }
174
214
  // Adding the current tag to the profile group
@@ -201,7 +241,7 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
201
241
  _.forEach(tagsList, (temp) => {
202
242
  const tag = temp.definition;
203
243
  const { intl } = this.props;
204
- const { locale: userLocale } = intl || {};
244
+ const { locale: userLocale } = intl || {};
205
245
 
206
246
  // Check if the tag.value should be skipped based on feature control
207
247
  if (_.includes(excludedTags, tag.value)) {
@@ -209,8 +249,8 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
209
249
  }
210
250
  if (!tag['tag-header']) {
211
251
  mainTags[tag.value] = {
212
- "name": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
213
- "desc": tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
252
+ name: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
253
+ desc: tag?.label[userLocale] ? tag?.label[userLocale] : tag?.label?.en,
214
254
  };
215
255
  } else if (tag['tag-header'] && mainTags[tag.value]) {
216
256
  mainTags[tag.value].subtags = _.concat(mainTags[tag.value].subtags, tag.subtags);
@@ -366,12 +406,14 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
366
406
  visibleTaglist={this.props.visibleTaglist}
367
407
  hidePopover={this.props.hidePopover}
368
408
  onContextChange={this.getTagsforContext}
409
+ onVisibleChange={this.handlePopoverVisibilityChange}
369
410
  moduleFilterEnabled={this.props.moduleFilterEnabled}
370
411
  modalProps={this.props.modalProps}
371
412
  currentOrgDetails={this.props.currentOrgDetails}
372
413
  channel={this.props.channel}
373
414
  disabled={this.props.disabled}
374
415
  fetchingSchemaError={this?.state?.tagsError}
416
+ popoverPlacement={this.props.popoverPlacement}
375
417
  />
376
418
  </div>
377
419
  );
@@ -402,6 +444,7 @@ TagList.propTypes = {
402
444
  disabled: PropTypes.bool,
403
445
  fetchingSchemaError: PropTypes.bool,
404
446
  eventContextTags: PropTypes.array,
447
+ popoverPlacement: PropTypes.string,
405
448
  intl: PropTypes.shape({
406
449
  formatMessage: PropTypes.func.isRequired,
407
450
  locale: PropTypes.string,
@@ -316,6 +316,64 @@
316
316
  }
317
317
  }
318
318
  }
319
+ .INAPP {
320
+ margin-bottom: $CAP_SPACE_12;
321
+ .inapp-container {
322
+ margin-top: $CAP_SPACE_24;
323
+ }
324
+ // Modal Layout - centered
325
+ .inapp-modal-layout {
326
+ position: absolute;
327
+ display: flex;
328
+ width: 12rem;
329
+ top: 10%;
330
+ left: 10%;
331
+ bottom: 5%;
332
+ overflow: hidden;
333
+ background-color: $CAP_WHITE;
334
+ border-radius: $CAP_SPACE_08;
335
+ }
336
+
337
+ // Full Screen Layout
338
+ .inapp-fullscreen-layout {
339
+ position: absolute;
340
+ display: flex;
341
+ width: 12rem;
342
+ left: 10%;
343
+ top: 1%;
344
+ bottom: 1%;
345
+ overflow: hidden;
346
+ background-color: $CAP_WHITE;
347
+ border-radius: $CAP_SPACE_08;
348
+ }
349
+
350
+ // Top Banner Layout
351
+ .inapp-top-banner-layout {
352
+ position: absolute;
353
+ display: flex;
354
+ width: 12rem;
355
+ left: 10%;
356
+ top: 1%;
357
+ bottom: 20%;
358
+ overflow: hidden;
359
+ background-color: $CAP_WHITE;
360
+ border-radius: $CAP_SPACE_08;
361
+ }
362
+
363
+ // Bottom Banner Layout
364
+ .inapp-bottom-banner-layout {
365
+ position: absolute;
366
+ display: flex;
367
+ justify-content: center;
368
+ width: 12rem;
369
+ left: 10%;
370
+ top: 50%;
371
+ bottom: 2%;
372
+ overflow: hidden;
373
+ background-color: $CAP_WHITE;
374
+ border-radius: $CAP_SPACE_08;
375
+ }
376
+ }
319
377
  }
320
378
 
321
379
  .WEBPUSH {
@@ -897,7 +955,8 @@
897
955
  }
898
956
 
899
957
  .whatsapp-filters,
900
- .zalo-filters {
958
+ .zalo-filters,
959
+ .inapp-filters {
901
960
  display: flex;
902
961
  width: 100%;
903
962
  padding-left: 8px;
@@ -128,13 +128,14 @@ import {
128
128
  VIDEO,
129
129
  GIF,
130
130
  } from '../Whatsapp/constants';
131
- import { INAPP_LAYOUT_DETAILS } from '../InApp/constants';
131
+ import { INAPP_LAYOUT_DETAILS, INAPP_MESSAGE_LAYOUT_TYPES, INAPP_MEDIA_TYPES, BIG_HTML, ANDROID, IOS } from '../InApp/constants';
132
132
  import { ZALO_STATUS_OPTIONS, ZALO_STATUSES } from '../Zalo/constants';
133
133
  import { getWhatsappContent, getWhatsappStatus, getWhatsappCategory, getWhatsappCta, getWhatsappQuickReply, getWhatsappAutoFill, getWhatsappCarouselButtonView } from '../Whatsapp/utils';
134
134
  import { getRCSContent } from '../Rcs/utils';
135
135
  import {RCS_STATUSES} from '../Rcs/constants';
136
136
  import zaloMessages from '../Zalo/messages';
137
137
  import rcsMessages from '../Rcs/messages';
138
+ import inAppMessages from '../InApp/messages';
138
139
  import globalMessages from '../../v2Containers/Cap/messages';
139
140
  import { handlePreviewInNewTab } from '../../utils/common';
140
141
 
@@ -204,6 +205,29 @@ const SMS_FILTERS = {
204
205
  SERVICE_IMPLICIT: 'implicit',
205
206
  };
206
207
 
208
+ const INAPP_LAYOUT_OPTIONS = [
209
+ {
210
+ key: 'popup',
211
+ value: INAPP_MESSAGE_LAYOUT_TYPES.MODAL,
212
+ label: <FormattedMessage {...inAppMessages.layoutModal} />,
213
+ },
214
+ {
215
+ key: 'topBanner',
216
+ value: INAPP_MESSAGE_LAYOUT_TYPES.TOPBANNER,
217
+ label: <FormattedMessage {...inAppMessages.layoutTopBanner} />,
218
+ },
219
+ {
220
+ key: 'bottomBanner',
221
+ value: INAPP_MESSAGE_LAYOUT_TYPES.BOTTOMBANNER,
222
+ label: <FormattedMessage {...inAppMessages.layoutBottomBanner} />,
223
+ },
224
+ {
225
+ key: 'fullScreen',
226
+ value: INAPP_MESSAGE_LAYOUT_TYPES.FULLSCREEN,
227
+ label: <FormattedMessage {...inAppMessages.layoutFullScreen} />,
228
+ },
229
+ ];
230
+
207
231
  const WHATSAPP_LOWERCASE = WHATSAPP.toLowerCase();
208
232
  const RCS_LOWERCASE = RCS.toLowerCase();
209
233
  const SMS_LOWERCASE = SMS.toLowerCase();
@@ -255,6 +279,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
255
279
  selectedWhatsappStatus: '',
256
280
  selectedWhatsappCategory: '',
257
281
  selectedZaloStatus: '',
282
+ selectedInAppLayout: '',
258
283
  hostName: '',
259
284
  searchedZaloTemplates: [],
260
285
  searchingZaloTemplate: false,
@@ -1759,6 +1784,19 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1759
1784
  return templates;
1760
1785
  }
1761
1786
 
1787
+ filterInAppTemplates = (templates) => {
1788
+ const { selectedInAppLayout } = this.state;
1789
+ if (!selectedInAppLayout) {
1790
+ return templates;
1791
+ }
1792
+ return templates.filter((template) => {
1793
+ const androidBodyType = get(template, 'versions.base.content.ANDROID.bodyType');
1794
+ const iosBodyType = get(template, 'versions.base.content.IOS.bodyType');
1795
+ const inappBodyType = androidBodyType || iosBodyType;
1796
+ return inappBodyType === selectedInAppLayout;
1797
+ });
1798
+ }
1799
+
1762
1800
  filterSMSTemplates = (templates) => {
1763
1801
  const { smsFilter } = this.state;
1764
1802
  if (SMS_FILTERS.ALL === smsFilter) {
@@ -1845,6 +1883,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1845
1883
  case ZALO:
1846
1884
  filteredTemplates = this.filterZaloTemplates(templates);
1847
1885
  break;
1886
+ case INAPP:
1887
+ filteredTemplates = this.filterInAppTemplates(templates);
1888
+ break;
1848
1889
  default:
1849
1890
  break;
1850
1891
  }
@@ -2134,9 +2175,12 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2134
2175
  templateData.isNewMobilePush = commonUtil.hasNewMobilePushFeatureEnabled();
2135
2176
  }
2136
2177
  break;
2137
- case INAPP:
2178
+ case INAPP: {
2179
+ // Pass the full template object to CapCustomCard so getInAppContent can extract the data
2180
+ // Similar to how MOBILE_PUSH passes the full template when new feature is enabled
2138
2181
  templateData.content = template;
2139
2182
  break;
2183
+ }
2140
2184
  case WECHAT:
2141
2185
  templateData.content = this.prepareWeChatPreviewData(template);
2142
2186
  break;
@@ -2514,7 +2558,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
2514
2558
  return (<div>
2515
2559
  {[WECHAT, MOBILE_PUSH, WEBPUSH, INAPP, WHATSAPP, ZALO, RCS].includes(currentChannel) && this.showAccountName()}
2516
2560
  {filterContent}
2517
- {[WHATSAPP, ZALO,RCS].includes(currentChannel) && this.selectedFilters()}
2561
+ {[WHATSAPP, ZALO, INAPP,RCS].includes(currentChannel) && this.selectedFilters()}
2518
2562
  {<div>
2519
2563
  {!isEmpty(filteredTemplates) || !isEmpty(this.state.searchText) || !isEmpty(this.props.Templates.templateError) ? (
2520
2564
  <div className={!isEmpty(this.state.searchText) && isEmpty(cardDataList) ? '' : this.isFullMode() ? "v2-pagination-container" : "v2-pagination-container-half"}>
@@ -2729,6 +2773,9 @@ return (<div>
2729
2773
 
2730
2774
  prepareWeChatMappedPreviewData(content, templateTags, tagData) {
2731
2775
  let formattedContent = cloneDeep(content);
2776
+ if (!formattedContent || typeof formattedContent !== 'string') {
2777
+ return formattedContent || '';
2778
+ }
2732
2779
  forEach(templateTags, (tag) => {
2733
2780
  if (tagData[tag].value !== undefined) {
2734
2781
  formattedContent = formattedContent.replace(`{{${tag}.DATA}}`, tagData[tag].value);
@@ -3981,14 +4028,19 @@ return (<div>
3981
4028
  }
3982
4029
 
3983
4030
  removeZaloFilter = () => this.setState({ selectedZaloStatus: null });
4031
+ removeInAppLayoutFilter = () => this.setState({ selectedInAppLayout: '' });
3984
4032
 
3985
4033
  selectedFilters = () => {
3986
- const { selectedWhatsappStatus, selectedWhatsappCategory, selectedZaloStatus } = this.state;
4034
+ const { selectedWhatsappStatus, selectedWhatsappCategory, selectedZaloStatus, selectedInAppLayout } = this.state;
3987
4035
  const {
3988
4036
  intl: {
3989
4037
  formatMessage,
3990
4038
  },
3991
4039
  } = this.props;
4040
+ const getInAppLayoutLabel = (layoutValue) => {
4041
+ const layoutOption = INAPP_LAYOUT_OPTIONS.find(opt => opt.value === layoutValue);
4042
+ return layoutOption ? layoutOption.label : layoutValue;
4043
+ };
3992
4044
  return (
3993
4045
  <CapRow type="flex" align="middle" className="selected-whatsapp-filters">
3994
4046
  {
@@ -4023,6 +4075,23 @@ return (<div>
4023
4075
  </CapTag>
4024
4076
  ) : null
4025
4077
  }
4078
+ {
4079
+ selectedInAppLayout ? (
4080
+ <CapTag closable onClose={this.removeInAppLayoutFilter}>
4081
+ {formatMessage(messages.layout)}: {getInAppLayoutLabel(selectedInAppLayout)}
4082
+ </CapTag>
4083
+ ) : null
4084
+ }
4085
+ {
4086
+ selectedInAppLayout ? (
4087
+ <CapLink
4088
+ onClick={() => {
4089
+ this.removeInAppLayoutFilter();
4090
+ }}
4091
+ title={this.props.intl.formatMessage(messages.clearAll)}
4092
+ />
4093
+ ) : null
4094
+ }
4026
4095
  </CapRow>
4027
4096
  );
4028
4097
  }
@@ -4033,6 +4102,7 @@ return (<div>
4033
4102
  setLineFilter = (e) => this.setState({lineFilter: e.target.value});
4034
4103
  setSMSFilter = (e) => this.setState({smsFilter: e.target.value});
4035
4104
  setZaloStatus = (value) => this.setState({selectedZaloStatus: value?.item?.props?.value});
4105
+ setInAppLayout = (value) => this.setState({selectedInAppLayout: value?.item?.props?.value});
4036
4106
 
4037
4107
  openCreativesFullMode = () => {
4038
4108
  const channelLowerCase = this.state.channel.toLowerCase();
@@ -4285,6 +4355,21 @@ return (<div>
4285
4355
  )
4286
4356
  : () => null
4287
4357
  }
4358
+ {
4359
+ channel.toUpperCase() === INAPP && (
4360
+ <div className="inapp-filters">
4361
+ <CapSelectFilter
4362
+ placement="bottomLeft"
4363
+ data={INAPP_LAYOUT_OPTIONS}
4364
+ onSelect={this.setInAppLayout}
4365
+ selectedValue={this.state.selectedInAppLayout}
4366
+ placeholder="Layout"
4367
+ showBadge
4368
+ width="120px"
4369
+ />
4370
+ </div>
4371
+ )
4372
+ }
4288
4373
  <div style={{display: "flex", justifyContent: "space-between", alignItems: 'center'}}>
4289
4374
  {
4290
4375
  this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE && (isWhatsappCountExeeded)? (
@@ -558,6 +558,10 @@ export default defineMessages({
558
558
  id: `${scope}.rcsOnlyApprovedTemplates`,
559
559
  defaultMessage: 'Only "Approved" templates are available here, as you can use those templates to create a message.',
560
560
  },
561
+ "layout": {
562
+ id: `${scope}.layout`,
563
+ defaultMessage: `Layout`,
564
+ },
561
565
  "status": {
562
566
  id: `${scope}.status`,
563
567
  defaultMessage: 'Status',
@@ -14,8 +14,10 @@ export default css`
14
14
  margin: 0 auto;
15
15
  width: 100%;
16
16
  padding: ${CAP_SPACE_24} 0;
17
- .ant-tabs-content{
18
- margin-top: ${CAP_SPACE_16}
17
+ /* Only main channel tabs content, not HTML Editor validation panel tabs */
18
+ > .cap-tab-v2 > .ant-tabs-content-holder > .ant-tabs-content,
19
+ > .cap-tab-v2 > .ant-tabs-content {
20
+ margin-top: ${CAP_SPACE_16};
19
21
  }
20
22
  ` : `.cap-tab-v2 {
21
23
  height: calc(100vh - 11.25rem);