@capillarytech/creatives-library 8.0.139 → 8.0.140

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 (44) hide show
  1. package/containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
  2. package/initialReducer.js +2 -0
  3. package/package.json +1 -1
  4. package/services/api.js +5 -0
  5. package/translations/en.json +1 -0
  6. package/utils/content.js +15 -0
  7. package/utils/tests/tagValidations.test.js +112 -0
  8. package/v2Components/FormBuilder/index.js +13 -1
  9. package/v2Components/FormBuilder/messages.js +4 -0
  10. package/v2Components/MobilePushPreviewV2/index.js +8 -0
  11. package/v2Components/TemplatePreview/assets/images/empty_android.svg +8 -0
  12. package/v2Components/TemplatePreview/assets/images/empty_ios.svg +5 -0
  13. package/v2Components/TemplatePreview/index.js +28 -2
  14. package/v2Containers/BeeEditor/index.js +1 -0
  15. package/v2Containers/BeePopupEditor/constants.js +10 -0
  16. package/v2Containers/BeePopupEditor/index.js +169 -0
  17. package/v2Containers/Cap/constants.js +7 -0
  18. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
  19. package/v2Containers/CreativesContainer/SlideBoxContent.js +26 -8
  20. package/v2Containers/CreativesContainer/index.js +12 -2
  21. package/v2Containers/Email/index.js +16 -0
  22. package/v2Containers/Email/messages.js +4 -0
  23. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +5 -0
  24. package/v2Containers/InApp/actions.js +7 -0
  25. package/v2Containers/InApp/constants.js +5 -1
  26. package/v2Containers/InApp/index.js +76 -53
  27. package/v2Containers/InApp/reducer.js +17 -0
  28. package/v2Containers/InApp/sagas.js +27 -0
  29. package/v2Containers/InApp/selectors.js +23 -1
  30. package/v2Containers/InappAdvanced/index.js +459 -0
  31. package/v2Containers/InappAdvanced/index.scss +11 -0
  32. package/v2Containers/InappWrapper/_inappWrapper.scss +19 -0
  33. package/v2Containers/InappWrapper/index.js +192 -0
  34. package/v2Containers/InappWrapper/messages.js +38 -0
  35. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  36. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  37. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
  38. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
  39. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +111 -0
  40. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
  41. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
  42. package/v2Containers/Templates/index.js +5 -0
  43. package/v2Containers/Templates/sagas.js +4 -1
  44. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +132 -0
@@ -281,6 +281,7 @@ exports[`<Cap /> should render its children 1`] = `
281
281
  "creatives.componentsV2.FTP.message": "Message",
282
282
  "creatives.componentsV2.FTP.search": "Search",
283
283
  "creatives.componentsV2.Footer.header": "This is the Footer component !",
284
+ "creatives.componentsV2.FormBuilder.base64ImageError": "Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.",
284
285
  "creatives.componentsV2.FormBuilder.cancel": "Cancel",
285
286
  "creatives.componentsV2.FormBuilder.contentNotValidLanguage": "Content is not valid for language:",
286
287
  "creatives.componentsV2.FormBuilder.dragAndDrop": "Drag and drop image here",
package/initialReducer.js CHANGED
@@ -15,6 +15,7 @@ import galleryReducer from './v2Containers/Assets/Gallery/reducer';
15
15
  import CapCollapsibleLeftNavigationReducer from '@capillarytech/cap-ui-library/CapCollapsibleLeftNavigation/reducer';
16
16
  import { AIRA_REDUCER_DOMAIN, askAiraReducer } from '@capillarytech/cap-ui-library/CapAskAira';
17
17
  import previewAndTestReducer from './v2Components/TestAndPreviewSlidebox/reducer';
18
+ import inAppReducer from './v2Containers/InApp/reducer';
18
19
 
19
20
  export const initialReducer = {
20
21
  language: languageProviderReducer,
@@ -33,4 +34,5 @@ export const initialReducer = {
33
34
  gallery: galleryReducer,
34
35
  navigationConfig: CapCollapsibleLeftNavigationReducer,
35
36
  previewAndTest: previewAndTestReducer,
37
+ inapp: inAppReducer,
36
38
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.139",
4
+ "version": "8.0.140",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -684,4 +684,9 @@ export const updateTestMessageMeta = (payload) => {
684
684
  return request(url, getAPICallObject('POST', payload?.data, false, true));
685
685
  };
686
686
 
687
+ export const getBeePopupBuilderToken = () => {
688
+ const url = `${API_ENDPOINT}/common/getInappTokenData`;
689
+ return request(url, getAPICallObject('GET'));
690
+ };
691
+
687
692
  export {request, getAPICallObject};
@@ -256,6 +256,7 @@
256
256
  "creatives.componentsV2.FormBuilder.uploadComputer": "Your computer",
257
257
  "creatives.componentsV2.FormBuilder.uploadGallery": "Gallery",
258
258
  "creatives.componentsV2.FormBuilder.yes": "Yes",
259
+ "creatives.componentsV2.FormBuilder.base64ImageError": "Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.",
259
260
  "creatives.componentsV2.Header.cancel": "Cancel",
260
261
  "creatives.componentsV2.Header.header": "This is the Header component !",
261
262
  "creatives.componentsV2.Header.preview": "Preview",
@@ -0,0 +1,15 @@
1
+ import { BASE64_IMAGE_REGEX, BASE64_IMAGE_ERROR_MESSAGE } from '../v2Containers/Cap/constants';
2
+
3
+ export const containsBase64Images = ({content, CapNotification, callback}) => {
4
+ if (!content || typeof content !== 'string') {
5
+ return false;
6
+ }
7
+ // Create a new regex instance to avoid global flag state issues
8
+ const regex = new RegExp(BASE64_IMAGE_REGEX.source, BASE64_IMAGE_REGEX.flags);
9
+ if (regex.test(content)) {
10
+ CapNotification && CapNotification.error(BASE64_IMAGE_ERROR_MESSAGE);
11
+ callback && callback();
12
+ return true;
13
+ }
14
+ return false;
15
+ };
@@ -10,6 +10,7 @@ import {
10
10
  skipTags,
11
11
  isInsideLiquidBlock,
12
12
  } from '../tagValidations';
13
+ import { containsBase64Images } from '../content';
13
14
  import { eventContextTags } from '../../v2Containers/TagList/tests/mockdata';
14
15
 
15
16
  describe("check if curly brackets are balanced", () => {
@@ -933,3 +934,114 @@ describe('isInsideLiquidBlock', () => {
933
934
  expect(isInsideLiquidBlock(content, blockEnd - 1)).toBe(true);
934
935
  });
935
936
  });
937
+
938
+ describe('containsBase64Images', () => {
939
+ let mockCapNotification;
940
+ let mockCallback;
941
+
942
+ beforeEach(() => {
943
+ mockCapNotification = {
944
+ error: jest.fn()
945
+ };
946
+ mockCallback = jest.fn();
947
+ });
948
+
949
+ it('should return false for empty or null content', () => {
950
+ expect(containsBase64Images({content: null})).toBe(false);
951
+ expect(containsBase64Images({content: undefined})).toBe(false);
952
+ expect(containsBase64Images({content: ''})).toBe(false);
953
+ expect(containsBase64Images({content: ' '})).toBe(false);
954
+ });
955
+
956
+ it('should return false for content without images', () => {
957
+ const content = '<div><p>Hello world</p></div>';
958
+ expect(containsBase64Images({content})).toBe(false);
959
+ expect(mockCapNotification.error).not.toHaveBeenCalled();
960
+ });
961
+
962
+ it('should return false for content with regular image URLs', () => {
963
+ const content = '<img src="https://example.com/image.jpg" alt="test" />';
964
+ expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(false);
965
+ expect(mockCapNotification.error).not.toHaveBeenCalled();
966
+ });
967
+
968
+ it('should return true for content with base64 images and trigger notification', () => {
969
+ const content = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==" alt="test" />';
970
+ expect(containsBase64Images({content, CapNotification: mockCapNotification, callback: mockCallback})).toBe(true);
971
+
972
+ expect(mockCapNotification.error).toHaveBeenCalledWith({
973
+ key: 'base64Error',
974
+ message: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
975
+ description: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
976
+ });
977
+ expect(mockCallback).toHaveBeenCalled();
978
+ });
979
+
980
+ it('should return true for content with multiple base64 images', () => {
981
+ const content = `
982
+ <div>
983
+ <img src="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD..." alt="image1" />
984
+ <p>Some text</p>
985
+ <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP//..." alt="image2" />
986
+ </div>
987
+ `;
988
+ expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
989
+ expect(mockCapNotification.error).toHaveBeenCalled();
990
+ });
991
+
992
+ it('should return false for content with data URLs that are not images', () => {
993
+ const content = '<a href="data:text/plain;base64,SGVsbG8gV29ybGQ=">Download</a>';
994
+ expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(false);
995
+ expect(mockCapNotification.error).not.toHaveBeenCalled();
996
+ });
997
+
998
+ it('should handle various image formats', () => {
999
+ const pngContent = '<img src="data:image/png;base64,iVBORw0..." />';
1000
+ const jpegContent = '<img src="data:image/jpeg;base64,/9j/4AAQ..." />';
1001
+ const gifContent = '<img src="data:image/gif;base64,R0lGODlh..." />';
1002
+ const webpContent = '<img src="data:image/webp;base64,UklGRi..." />';
1003
+
1004
+ expect(containsBase64Images({content: pngContent, CapNotification: mockCapNotification})).toBe(true);
1005
+ expect(containsBase64Images({content: jpegContent, CapNotification: mockCapNotification})).toBe(true);
1006
+ expect(containsBase64Images({content: gifContent, CapNotification: mockCapNotification})).toBe(true);
1007
+ expect(containsBase64Images({content: webpContent, CapNotification: mockCapNotification})).toBe(true);
1008
+
1009
+ expect(mockCapNotification.error).toHaveBeenCalledTimes(4);
1010
+ });
1011
+
1012
+ it('should handle mixed content', () => {
1013
+ const content = `
1014
+ <div>
1015
+ <img src="https://example.com/regular-image.jpg" alt="regular" />
1016
+ <img src="data:image/png;base64,iVBORw0..." alt="base64" />
1017
+ <img src="/local/path/image.gif" alt="local" />
1018
+ </div>
1019
+ `;
1020
+ expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
1021
+ expect(mockCapNotification.error).toHaveBeenCalled();
1022
+ });
1023
+
1024
+ it('should work without CapNotification parameter', () => {
1025
+ const content = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
1026
+ expect(containsBase64Images({content})).toBe(true);
1027
+ // Should not throw error when CapNotification is undefined
1028
+ });
1029
+
1030
+ it('should work without callback parameter', () => {
1031
+ const content = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
1032
+ expect(containsBase64Images({content, CapNotification: mockCapNotification})).toBe(true);
1033
+ expect(mockCapNotification.error).toHaveBeenCalled();
1034
+ // Should not throw error when callback is undefined
1035
+ });
1036
+
1037
+ it('should execute callback only when base64 images are found', () => {
1038
+ const contentWithBase64 = '<img src="data:image/png;base64,iVBORw0..." alt="test" />';
1039
+ const contentWithoutBase64 = '<img src="https://example.com/image.jpg" alt="test" />';
1040
+
1041
+ containsBase64Images({content: contentWithBase64, callback: mockCallback});
1042
+ expect(mockCallback).toHaveBeenCalledTimes(1);
1043
+
1044
+ containsBase64Images({content: contentWithoutBase64, callback: mockCallback});
1045
+ expect(mockCallback).toHaveBeenCalledTimes(1); // Should still be 1, not called again
1046
+ });
1047
+ });
@@ -48,7 +48,8 @@ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetai
48
48
  import * as actions from "../../v2Containers/Cap/actions";
49
49
  import './_formBuilder.scss';
50
50
  import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
51
- import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed } from '../../utils/tagValidations';
51
+ import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed} from '../../utils/tagValidations';
52
+ import { containsBase64Images } from '../../utils/content';
52
53
  import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
53
54
  import globalMessages from '../../v2Containers/Cap/messages';
54
55
  import { convert } from 'html-to-text';
@@ -982,6 +983,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
982
983
  return false;
983
984
  }
984
985
  const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail);
986
+
987
+ // Check for base64 images in email content
988
+ isEmail && containsBase64Images({content, callback:()=>{
989
+ tagValidationResponse.valid = false;
990
+ tagValidationResponse.hasBase64Images = true;
991
+ }})
992
+
985
993
  if (errorData[index][currentLang] === undefined) {
986
994
  errorData[index][currentLang] = {};
987
995
  }
@@ -1012,6 +1020,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1012
1020
  isLiquidValid = true;
1013
1021
  }
1014
1022
  }
1023
+ if (tagValidationResponse?.hasBase64Images) {
1024
+ errorString += this.props.intl.formatMessage(messages.base64ImageError);
1025
+ }
1015
1026
  }
1016
1027
  }
1017
1028
  }
@@ -3564,6 +3575,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3564
3575
  }
3565
3576
  }
3566
3577
  const { currentTab, formData } = this.state;
3578
+ console.log('3363 currentTab', currentTab, formData);
3567
3579
  const supportedLanguages = (formData[currentTab - 1] || {}).selectedLanguages || {};
3568
3580
  const currentLang = supportedLanguages[langTab - 1];
3569
3581
 
@@ -98,4 +98,8 @@ export default defineMessages({
98
98
  id: 'creatives.componentsV2.FormBuilder.liquidSpinText',
99
99
  defaultMessage: 'Validating the template, it might take a few seconds',
100
100
  },
101
+ base64ImageError: {
102
+ id: 'creatives.componentsV2.FormBuilder.base64ImageError',
103
+ defaultMessage: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
104
+ },
101
105
  });
@@ -45,6 +45,14 @@ class MobilePushPreviewV2 extends React.Component { // eslint-disable-line react
45
45
  if (channel === INAPP.toUpperCase()) {
46
46
  const androidContent = get(templateData, 'versions.base.content.ANDROID') || get(templateData, 'androidContent');
47
47
  const iosContent = get(templateData, 'versions.base.content.IOS') || get(templateData, 'iosContent');
48
+ const isBeeFreeTemplate = get(androidContent, 'isBEEeditor') || get(iosContent, 'isBEEeditor');
49
+ if (isBeeFreeTemplate) {
50
+ content = {
51
+ inAppPreviewContent: device === ANDROID.toLowerCase() ? androidContent?.beeHtml : iosContent?.beeHtml,
52
+ isBeeFreeTemplate: true,
53
+ };
54
+ return content;
55
+ }
48
56
  const androidPreviewContent = {
49
57
  templateTitle: androidContent?.title,
50
58
  templateMsg: androidContent?.message,
@@ -0,0 +1,8 @@
1
+ <svg width="220" height="472" viewBox="0 0 220 472" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="220" height="472" rx="26" fill="#101211"/>
3
+ <rect x="9" y="36" width="202" height="404" rx="9" fill="#343434"/>
4
+ <rect x="84" y="14" width="52" height="5" rx="2.5" fill="#2E2E2E"/>
5
+ <rect x="84" y="455" width="52" height="5" rx="2.5" fill="#2E2E2E"/>
6
+ <circle cx="45.5" cy="19.5" r="5.5" fill="#2E2E2E"/>
7
+ <circle cx="65.5" cy="19.5" r="3.5" fill="#2E2E2E"/>
8
+ </svg>
@@ -0,0 +1,5 @@
1
+ <svg width="228" height="472" viewBox="0 0 228 472" fill="none" xmlns="http://www.w3.org/2000/svg">
2
+ <rect width="228" height="472" rx="32" fill="#101211"/>
3
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M58 11H33C20.2975 11 10 21.2975 10 34V439C10 451.703 20.2975 462 33 462H195C207.703 462 218 451.703 218 439V34C218 21.2975 207.703 11 195 11H170V14C170 21.1797 164.18 27 157 27H71C63.8203 27 58 21.1797 58 14V11Z" fill="#343434"/>
4
+ <path fill-rule="evenodd" clip-rule="evenodd" d="M58 11H33C20.2975 11 10 21.2975 10 34V439C10 451.703 20.2975 462 33 462H195C207.703 462 218 451.703 218 439V34C218 21.2975 207.703 11 195 11H170V14C170 21.1797 164.18 27 157 27H71C63.8203 27 58 21.1797 58 14V11Z" fill="#343434"/>
5
+ </svg>
@@ -236,7 +236,7 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
236
236
  } = this.props;
237
237
  let content = channel && channel.toLowerCase() === 'sms' ? [this.props.content] : this.props.content;
238
238
  const { formatMessage } = intl;
239
- const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent } = content || {};
239
+ const { rcsPreviewContent, inAppPreviewContent, viberPreviewContent, isBeeFreeTemplate } = content || {};
240
240
  const { rcsImageSrc, rcsTitle, rcsDesc, buttonText: rcsButtonText } = rcsPreviewContent || {};
241
241
  const {
242
242
  videoParams,
@@ -1326,6 +1326,31 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1326
1326
  </div>
1327
1327
  )}
1328
1328
  {channel?.toUpperCase() === INAPP && (
1329
+ isBeeFreeTemplate ? (
1330
+ <div className="shell-v2 align-center">
1331
+ <div style={{ position: 'relative', display: 'inline-block', width: '100%', height: '100%' }}>
1332
+ <CapImage
1333
+ className="preview-image"
1334
+ src={this.props.device === ANDROID ? inAppMobileAndroidFull : inAppMobileIOSFull}
1335
+ alt={formatMessage(messages.previewGenerated)}
1336
+ />
1337
+ <iframe
1338
+ srcDoc={inAppPreviewContent?.value}
1339
+ title="Inapp Preview"
1340
+ style={{
1341
+ position: 'absolute',
1342
+ top: '3rem',
1343
+ left: '5rem',
1344
+ width: '60%',
1345
+ height: '100%',
1346
+ zIndex: 1,
1347
+ pointerEvents: 'none'
1348
+ }}
1349
+ frameBorder="0"
1350
+ />
1351
+ </div>
1352
+ </div>
1353
+ ) : (
1329
1354
  <div className="shell-v2 align-center">
1330
1355
  <CapImage
1331
1356
  className="preview-image"
@@ -1402,8 +1427,9 @@ export class TemplatePreview extends React.Component { // eslint-disable-line re
1402
1427
  )}
1403
1428
  </div>
1404
1429
  </div>
1430
+ </div>
1405
1431
  </div>
1406
- </div>
1432
+ )
1407
1433
  )}
1408
1434
  </CapRow>
1409
1435
  </CapColumn>
@@ -56,6 +56,7 @@ function BeeEditor(props) {
56
56
  currentOrgDetails,
57
57
  eventContextTags,
58
58
  } = props;
59
+ console.log('BEE editor props', props);
59
60
  const { saveRowRequest } = BEESelect;
60
61
  const {formatMessage} = intl;
61
62
  const UNSUBSCRIBE = 'unsubscribe';
@@ -0,0 +1,10 @@
1
+ export const BEE_LAYOUT_OPTIONS = {
2
+ POPUP: "classic-center",
3
+ HEADER: "bar-top",
4
+ FOOTER: "bar-bottom",
5
+ FULLSCREEN: "classic-center",
6
+ };
7
+ export const MOBILE = "mobile";
8
+ export const UNSUBSCRIBE = 'unsubscribe';
9
+ export const ANDROID = 'ANDROID';
10
+ export const IOS = 'IOS';
@@ -0,0 +1,169 @@
1
+ import React, { useEffect, useRef, useState } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { injectIntl } from 'react-intl';
4
+ import { connect } from 'react-redux';
5
+ // import { bindActionCreators } from 'redux';
6
+ import { createStructuredSelector } from 'reselect';
7
+ import { UserIsAuthenticated } from '../../utils/authWrapper';
8
+ import TagList from '../TagList';
9
+ import { ANDROID, BEE_LAYOUT_OPTIONS, MOBILE, UNSUBSCRIBE } from './constants';
10
+ import emptyAndroidSvg from '../../v2Components/TemplatePreview/assets/images/empty_android.svg';
11
+ import emptyIosSvg from '../../v2Components/TemplatePreview/assets/images/empty_ios.svg';
12
+ function BeePopupEditor(props) {
13
+ const {
14
+ uid,
15
+ id,
16
+ tokenData,
17
+ saveBeeInstance,
18
+ saveBeeData,
19
+ beeJson,
20
+ templateLayoutType,
21
+ moduleFilterEnabled,
22
+ label,
23
+ location,
24
+ injectedTags,
25
+ className,
26
+ userLocale,
27
+ selectedOfferDetails,
28
+ tags,
29
+ onContextChange,
30
+ device,
31
+ } = props;
32
+
33
+ let beePluginInstance = null;
34
+ let intervalTimer;
35
+ const savedCallback = useRef();
36
+
37
+ const [visibleTaglist, setVisibleTaglist] = useState(false);
38
+ const [selectedTag, setSelectedTag] = useState({});
39
+ const filteredTags = (tags || []).filter((obj) => obj.definition.value !== UNSUBSCRIBE);
40
+
41
+ useEffect(() => {
42
+ const beeConfig = {
43
+ uid,
44
+ trackChanges: true,
45
+ container: 'bee-plugin-container',
46
+ workspace: {
47
+ popup: {
48
+ backgroundImageMobile: device === ANDROID ? emptyAndroidSvg : emptyIosSvg,
49
+ layout: BEE_LAYOUT_OPTIONS[templateLayoutType],
50
+ customStyles: {
51
+ container: {
52
+ width: "90%",
53
+ margin: "1.5rem",
54
+ },
55
+ },
56
+ },
57
+ stage: MOBILE,
58
+ hideStageToggle: true,
59
+ },
60
+ contentDialog: {
61
+ mergeTags: {
62
+ label: 'Add Label',
63
+ handler: async (resolve, reject) => {
64
+ // this will open tag modal
65
+ await setVisibleTaglist(true);
66
+ // until tag modal will not open promise will not execute
67
+ // once tag modal is opened it will start 2 sec interval to wait use has selected any tag or cancel the tag selection
68
+ const promise = new Promise((resolveP) => {
69
+ intervalTimer = setInterval(() => {
70
+ // this will execute, if user cancel the tag selection
71
+ if ((savedCallback.current || {}).close === true) {
72
+ reject();
73
+ clearInterval(intervalTimer);
74
+ return;
75
+ }
76
+ // this block is checking use has selected any tag or not
77
+ if (Object.keys(savedCallback.current || {}).length > 0) {
78
+ resolveP(savedCallback.current);
79
+ setSelectedTag({});
80
+ clearInterval(intervalTimer);
81
+ }
82
+ }, 2000);
83
+ });
84
+ // once prmise will resolve , pass the resolve data to handler to show tags in bee edior
85
+ const result = await promise;
86
+ resolve(result);
87
+ },
88
+ },
89
+ },
90
+ onChange: (jsonFile, htmlFile) => {
91
+ saveBeeData(jsonFile, htmlFile, device);
92
+ },
93
+ };
94
+ window.BeePlugin.create(tokenData, beeConfig, (instance) => {
95
+ beePluginInstance = instance;
96
+ const parseJson = JSON.parse(beeJson);
97
+ beePluginInstance.start(parseJson);
98
+ saveBeeInstance(beePluginInstance);
99
+ });
100
+ return () => clearInterval(intervalTimer);
101
+ }, [templateLayoutType]);
102
+
103
+ useEffect(() => {
104
+ savedCallback.current = Object.keys(selectedTag).length > 0 && selectedTag;
105
+ }, [selectedTag]);
106
+
107
+ const onTagSelect = (result) => {
108
+ const msg = {
109
+ name: result,
110
+ value: `{{${result}}}`,
111
+ };
112
+ setSelectedTag(msg);
113
+ setVisibleTaglist(false);
114
+ };
115
+
116
+ const onCancelTagList = () => {
117
+ setVisibleTaglist(false);
118
+ setSelectedTag({close: true});
119
+ };
120
+
121
+ return (
122
+ <>
123
+ <div id="bee-plugin-container" style={{ height: "650px" }}></div>
124
+ <TagList
125
+ moduleFilterEnabled={moduleFilterEnabled}
126
+ label={label}
127
+ onTagSelect={onTagSelect}
128
+ location={location}
129
+ tags={filteredTags}
130
+ injectedTags={injectedTags}
131
+ className={className}
132
+ id={id}
133
+ userLocale={userLocale}
134
+ selectedOfferDetails={selectedOfferDetails}
135
+ visibleTaglist={visibleTaglist}
136
+ hidePopover
137
+ modalProps={{
138
+ onCancel: onCancelTagList,
139
+ style: { left: 135, top: 250 },
140
+ className: "bee-editor-tag-list",
141
+ }}
142
+ onContextChange={onContextChange}
143
+ />
144
+ </>
145
+ );
146
+ }
147
+ BeePopupEditor.propTypes = {
148
+ saveBeeData: PropTypes.func.isRequired,
149
+ saveBeeInstance: PropTypes.func.isRequired,
150
+ beeJson: PropTypes.object.isRequired,
151
+ tokenData: PropTypes.object.isRequired,
152
+ uid: PropTypes.string.isRequired,
153
+ templateLayoutType: PropTypes.string.isRequired,
154
+ id: PropTypes.string.isRequired,
155
+ moduleFilterEnabled: PropTypes.bool.isRequired,
156
+ label: PropTypes.string.isRequired,
157
+ location: PropTypes.object.isRequired,
158
+ injectedTags: PropTypes.array.isRequired,
159
+ className: PropTypes.string.isRequired,
160
+ userLocale: PropTypes.string.isRequired,
161
+ selectedOfferDetails: PropTypes.object.isRequired,
162
+ tags: PropTypes.array.isRequired,
163
+ onContextChange: PropTypes.func.isRequired,
164
+ device: PropTypes.string.isRequired,
165
+ };
166
+ const mapStateToProps = () => createStructuredSelector({});
167
+
168
+ function mapDispatchToProps() {}
169
+ export default UserIsAuthenticated(connect(mapStateToProps, mapDispatchToProps)(injectIntl(BeePopupEditor)));
@@ -67,3 +67,10 @@ export const GET_LIQUID_TAGS_SUCCESS = 'cap/GET_LIQUID_TAGS_SUCCESS_V2';
67
67
  export const CREATE_CENTRAL_COMMS_META_ID_FAILURE = 'cap/CREATE_CENTRAL_COMMS_META_ID_FAILURE_V2';
68
68
  export const CREATE_CENTRAL_COMMS_META_ID_REQUEST = 'cap/CREATE_CENTRAL_COMMS_META_ID_REQUEST_V2';
69
69
  export const CREATE_CENTRAL_COMMS_META_ID_SUCCESS = 'cap/CREATE_CENTRAL_COMMS_META_ID_SUCCESS_V2';
70
+
71
+ export const BASE64_IMAGE_REGEX = /<img[^>]*\bsrc\s*=\s*["']data:image\/[^"']*["'][^>]*>/gi;
72
+ export const BASE64_IMAGE_ERROR_MESSAGE = {
73
+ key: 'base64Error',
74
+ message: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
75
+ description: 'Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.',
76
+ };
@@ -317,6 +317,7 @@ exports[`<Cap /> should render correct component 1`] = `
317
317
  "creatives.componentsV2.FTP.message": "Message",
318
318
  "creatives.componentsV2.FTP.search": "Search",
319
319
  "creatives.componentsV2.Footer.header": "This is the Footer component !",
320
+ "creatives.componentsV2.FormBuilder.base64ImageError": "Base64 images are not allowed. Please upload your image to a gallery and use it, or add the image URL instead.",
320
321
  "creatives.componentsV2.FormBuilder.cancel": "Cancel",
321
322
  "creatives.componentsV2.FormBuilder.contentNotValidLanguage": "Content is not valid for language:",
322
323
  "creatives.componentsV2.FormBuilder.dragAndDrop": "Drag and drop image here",
@@ -27,6 +27,7 @@ import Rcs from '../Rcs';
27
27
  import { getWhatsappContent } from '../Whatsapp/utils';
28
28
  import * as commonUtil from '../../utils/common';
29
29
  import Zalo from '../Zalo';
30
+ import InappWrapper from '../InappWrapper';
30
31
  import MobilePushNew from '../MobilePushNew';
31
32
  const CreativesWrapper = styled.div`
32
33
  .ant-popover,
@@ -115,6 +116,8 @@ export function SlideBoxContent(props) {
115
116
  emailCreateMode,
116
117
  onMobilepushModeChange,
117
118
  mobilePushCreateMode,
119
+ inAppCreateMode,
120
+ onInAppModeChange,
118
121
  templateStep,
119
122
  onEnterTemplateName,
120
123
  onRemoveTemplateName,
@@ -935,25 +938,32 @@ export function SlideBoxContent(props) {
935
938
  />
936
939
  )}
937
940
 
938
- {isCreateInApp && (<InApp
941
+ {isCreateInApp && (<InappWrapper
942
+ key="creatives-email-wrapper"
943
+ date={new Date().getMilliseconds()}
939
944
  isFullMode={isFullMode}
940
945
  onCreateComplete={onCreateComplete}
941
946
  handleClose={handleClose}
942
- location={{
943
- pathname: `/inapp/create`,
944
- query,
945
- search: '',
946
- }}
947
947
  getFormData={getFormData}
948
948
  isGetFormData={isGetFormData}
949
949
  templateData={templateData}
950
950
  getDefaultTags={type}
951
- eventContextTags={eventContextTags}
952
- showLiquidErrorInFooter={showLiquidErrorInFooter}
951
+ step={templateStep}
952
+ onResetStep={onResetStep}
953
+ onInAppModeChange={onInAppModeChange}
954
+ onEnterTemplateName={onEnterTemplateName}
955
+ onRemoveTemplateName={onRemoveTemplateName}
956
+ inAppCreateMode={inAppCreateMode}
957
+ forwardedTags={forwardedTags}
958
+ type={type}
959
+ query={query}
960
+ selectedOfferDetails={selectedOfferDetails}
961
+ onValidationFail={onValidationFail}
953
962
  />
954
963
  )}
955
964
 
956
965
  {isEditInApp && (<InApp
966
+ isEditInApp={true}
957
967
  isFullMode={isFullMode}
958
968
  templateData={templateData}
959
969
  getFormData={getFormData}
@@ -971,6 +981,14 @@ export function SlideBoxContent(props) {
971
981
  search: '',
972
982
  }}
973
983
  showLiquidErrorInFooter={showLiquidErrorInFooter}
984
+ setIsLoadingContent={setIsLoadingContent}
985
+ onInAppModeChange={onInAppModeChange}
986
+ inAppCreateMode={inAppCreateMode}
987
+ isGetFormData={isGetFormData}
988
+ showTemplateName={showTemplateName}
989
+ onValidationFail={onValidationFail}
990
+ type={type}
991
+ query={query}
974
992
  />
975
993
  )}
976
994
 
@@ -1267,7 +1267,7 @@ export class Creatives extends React.Component {
1267
1267
  shouldShowFooter = () => {
1268
1268
  const { isFullMode } = this.props;
1269
1269
  const {
1270
- slidBoxContent, currentChannel, emailCreateMode, templateNameExists, templateStep, mobilePushCreateMode, weChatTemplateType, templateData,
1270
+ slidBoxContent, currentChannel, emailCreateMode, templateNameExists, templateStep, mobilePushCreateMode, weChatTemplateType, templateData, inAppCreateMode,
1271
1271
  } = this.state;
1272
1272
  const channel = currentChannel.toUpperCase();
1273
1273
  const currentStep = this.creativesTemplateSteps[templateStep];
@@ -1310,6 +1310,13 @@ export class Creatives extends React.Component {
1310
1310
  showFooter = true;
1311
1311
  }
1312
1312
 
1313
+ if (channel === constants.INAPP &&
1314
+ ((slidBoxContent === 'createTemplate' && !isEmpty(inAppCreateMode))
1315
+ || (slidBoxContent === 'editTemplate' && currentStep === 'modeSelection')) &&
1316
+ templateNameExists) {
1317
+ showFooter = true;
1318
+ }
1319
+
1313
1320
  if (showFooter) {
1314
1321
  if (slidBoxContent === "createTemplate" && ((channel === constants.EMAIL && currentStep === 'createTemplateContent') ||
1315
1322
  ([constants.SMS, constants.WECHAT].includes(channel) && currentStep === 'modeSelection'))) {
@@ -1430,7 +1437,7 @@ export class Creatives extends React.Component {
1430
1437
  } else if (currentChannel.toUpperCase() === constants.WECHAT) {
1431
1438
  isShowContinueFooter = !isEmpty(weChatTemplateType) && currentStep === "modeSelection";
1432
1439
  } else if (currentChannel.toUpperCase() === constants.INAPP) {
1433
- isShowContinueFooter = !isEmpty(inAppCreateMode) && currentChannel === "modeSelection";
1440
+ isShowContinueFooter = !isEmpty(inAppCreateMode) && currentStep === "modeSelection";
1434
1441
  }
1435
1442
 
1436
1443
  return isShowContinueFooter;
@@ -1467,6 +1474,7 @@ export class Creatives extends React.Component {
1467
1474
  templateStep,
1468
1475
  isLoadingContent,
1469
1476
  mobilePushCreateMode,
1477
+ inAppCreateMode,
1470
1478
  isDiscardMessage,
1471
1479
  weChatTemplateType,
1472
1480
  weChatMaptemplateStep,
@@ -1569,6 +1577,8 @@ export class Creatives extends React.Component {
1569
1577
  setIsLoadingContent={this.setIsLoadingContent}
1570
1578
  onMobilepushModeChange={this.onMobilepushModeChange}
1571
1579
  mobilePushCreateMode={mobilePushCreateMode}
1580
+ inAppCreateMode={inAppCreateMode}
1581
+ onInAppModeChange={this.onInAppModeChange}
1572
1582
  showTemplateName={this.showTemplateName}
1573
1583
  onValidationFail={this.onValidationFail}
1574
1584
  channelsToHide={channelsToHide}