@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.
- package/containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +5 -0
- package/translations/en.json +1 -0
- package/utils/content.js +15 -0
- package/utils/tests/tagValidations.test.js +112 -0
- package/v2Components/FormBuilder/index.js +13 -1
- package/v2Components/FormBuilder/messages.js +4 -0
- package/v2Components/MobilePushPreviewV2/index.js +8 -0
- package/v2Components/TemplatePreview/assets/images/empty_android.svg +8 -0
- package/v2Components/TemplatePreview/assets/images/empty_ios.svg +5 -0
- package/v2Components/TemplatePreview/index.js +28 -2
- package/v2Containers/BeeEditor/index.js +1 -0
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +169 -0
- package/v2Containers/Cap/constants.js +7 -0
- package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +1 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +26 -8
- package/v2Containers/CreativesContainer/index.js +12 -2
- package/v2Containers/Email/index.js +16 -0
- package/v2Containers/Email/messages.js +4 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +5 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +5 -1
- package/v2Containers/InApp/index.js +76 -53
- package/v2Containers/InApp/reducer.js +17 -0
- package/v2Containers/InApp/sagas.js +27 -0
- package/v2Containers/InApp/selectors.js +23 -1
- package/v2Containers/InappAdvanced/index.js +459 -0
- package/v2Containers/InappAdvanced/index.scss +11 -0
- package/v2Containers/InappWrapper/_inappWrapper.scss +19 -0
- package/v2Containers/InappWrapper/index.js +192 -0
- package/v2Containers/InappWrapper/messages.js +38 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +25 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +18 -0
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +111 -0
- package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +8 -0
- package/v2Containers/Templates/index.js +5 -0
- package/v2Containers/Templates/sagas.js +4 -1
- 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
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};
|
package/translations/en.json
CHANGED
|
@@ -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",
|
package/utils/content.js
ADDED
|
@@ -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
|
|
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
|
-
|
|
1432
|
+
)
|
|
1407
1433
|
)}
|
|
1408
1434
|
</CapRow>
|
|
1409
1435
|
</CapColumn>
|
|
@@ -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 && (<
|
|
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
|
-
|
|
952
|
-
|
|
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) &&
|
|
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}
|