@capillarytech/creatives-library 8.0.87-alpha.21 → 8.0.87-alpha.23

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 (59) hide show
  1. package/containers/Templates/constants.js +6 -0
  2. package/containers/Templates/index.js +44 -24
  3. package/package.json +1 -1
  4. package/services/api.js +22 -12
  5. package/services/tests/api.test.js +5 -1
  6. package/utils/commonUtils.js +64 -10
  7. package/utils/tests/commonUtil.test.js +108 -3
  8. package/utils/tests/transformerUtils.test.js +2127 -0
  9. package/utils/transformerUtils.js +42 -96
  10. package/v2Components/CapImageUpload/index.js +13 -10
  11. package/v2Components/CapVideoUpload/index.js +12 -9
  12. package/v2Components/CapWhatsappCTA/messages.js +4 -0
  13. package/v2Components/CapWhatsappCarouselButton/constant.js +56 -0
  14. package/v2Components/CapWhatsappCarouselButton/index.js +446 -0
  15. package/v2Components/CapWhatsappCarouselButton/index.scss +39 -0
  16. package/v2Components/CapWhatsappCarouselButton/tests/index.test.js +237 -0
  17. package/v2Components/FormBuilder/constants.js +8 -0
  18. package/v2Components/FormBuilder/index.js +2 -2
  19. package/v2Components/TemplatePreview/_templatePreview.scss +20 -0
  20. package/v2Components/TemplatePreview/assets/images/empty_image_preview.svg +4 -0
  21. package/v2Components/TemplatePreview/assets/images/empty_video_preview.svg +4 -0
  22. package/v2Components/TemplatePreview/index.js +160 -105
  23. package/v2Components/TemplatePreview/tests/__snapshots__/index.test.js.snap +6 -6
  24. package/v2Containers/Cap/tests/saga.test.js +90 -1
  25. package/v2Containers/CreativesContainer/SlideBoxContent.js +0 -6
  26. package/v2Containers/CreativesContainer/constants.js +8 -1
  27. package/v2Containers/CreativesContainer/index.js +102 -9
  28. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +3 -0
  29. package/v2Containers/Email/index.js +0 -1
  30. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +192 -0
  31. package/v2Containers/EmailWrapper/constants.js +11 -1
  32. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +343 -0
  33. package/v2Containers/EmailWrapper/index.js +116 -300
  34. package/v2Containers/EmailWrapper/mockdata/mockdata.js +119 -0
  35. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +214 -0
  36. package/v2Containers/EmailWrapper/tests/index.test.js +101 -0
  37. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +601 -0
  38. package/v2Containers/MobilePush/Edit/index.js +0 -1
  39. package/v2Containers/MobilepushWrapper/index.js +1 -2
  40. package/v2Containers/Sms/Create/index.js +0 -1
  41. package/v2Containers/SmsWrapper/index.js +0 -2
  42. package/v2Containers/Templates/_templates.scss +47 -0
  43. package/v2Containers/Templates/index.js +55 -5
  44. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +177 -156
  45. package/v2Containers/TemplatesV2/index.js +2 -2
  46. package/v2Containers/Whatsapp/constants.js +87 -1
  47. package/v2Containers/Whatsapp/index.js +715 -190
  48. package/v2Containers/Whatsapp/index.scss +52 -1
  49. package/v2Containers/Whatsapp/messages.js +38 -2
  50. package/v2Containers/Whatsapp/styles.scss +5 -0
  51. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +27722 -90751
  52. package/v2Containers/Whatsapp/tests/__snapshots__/utils.test.js.snap +6 -0
  53. package/v2Containers/Whatsapp/tests/mockData.js +3 -7
  54. package/v2Containers/Whatsapp/tests/utils.test.js +178 -1
  55. package/v2Containers/Whatsapp/utils.js +52 -0
  56. package/v2Containers/Zalo/index.js +47 -15
  57. package/v2Containers/Zalo/index.scss +8 -0
  58. package/v2Containers/Zalo/messages.js +4 -0
  59. package/v2Containers/mockdata.js +2 -0
@@ -44,7 +44,7 @@ import {
44
44
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
45
45
  import { CREATIVE } from '../Facebook/constants';
46
46
  import { LOYALTY } from '../App/constants';
47
- import { WHATSAPP_STATUSES, WHATSAPP_MEDIA_TYPES } from '../Whatsapp/constants';
47
+ import { WHATSAPP_STATUSES, WHATSAPP_MEDIA_TYPES, PHONE_NUMBER, URL } from '../Whatsapp/constants';
48
48
 
49
49
  import { updateImagesInHtml } from '../../utils/cdnTransformation';
50
50
  import { IOS } from '../InApp/constants';
@@ -58,6 +58,7 @@ import {
58
58
  CAP_SPACE_32, CAP_SPACE_56, CAP_SPACE_64,
59
59
  } from '@capillarytech/cap-ui-library/styled/variables';
60
60
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
61
+ import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
61
62
 
62
63
  import {
63
64
  transformChannelPayload,
@@ -234,6 +235,39 @@ export class Creatives extends React.Component {
234
235
  this.setState({ isGetFormData: false });
235
236
  };
236
237
 
238
+ mapCarouselDataToCreatives = (cards) => {
239
+ return cards.map((card) => {
240
+ const { cardVarMapped, bodyTemplate, media, buttons, mediaType } = card || {};
241
+ const buttonData = buttons?.map((button) => {
242
+ const { type, text, phoneNumber, url, dynamicUrlPayload } = button || {};
243
+ const buttonObj = { type, text };
244
+ if (type === PHONE_NUMBER) {
245
+ buttonObj.phone_number = phoneNumber;
246
+ }
247
+ if (type === URL) {
248
+ buttonObj.url = url;
249
+ if (dynamicUrlPayload) {
250
+ buttonObj.urlType = DYNAMIC_URL;
251
+ }
252
+ }
253
+ return buttonObj;
254
+ });
255
+ return {
256
+ bodyText: bodyTemplate,
257
+ varMap: cardVarMapped,
258
+ ...(mediaType === IMAGE && {
259
+ imageUrl: media?.url,
260
+ }),
261
+ ...(mediaType === VIDEO && {
262
+ videoUrl: media?.url,
263
+ videoPreviewImg: media?.previewUrl,
264
+ }),
265
+ buttons: buttonData,
266
+ mediaType: mediaType?.toLowerCase(),
267
+ };
268
+ });
269
+ }
270
+
237
271
  getTemplateData = (templateData) => { //from consumers to creatives
238
272
  const { isFullMode, messageDetails = {}, smsRegister } = this.props;
239
273
  const { additionalProperties = {} } = messageDetails;
@@ -476,6 +510,7 @@ export class Creatives extends React.Component {
476
510
  mediaType = 'TEXT',
477
511
  whatsappMedia = {},
478
512
  isPreviewUrl = false,
513
+ cards = [],
479
514
  } = {},
480
515
  } = templateData;
481
516
  const mediaParams = {};
@@ -498,7 +533,7 @@ export class Creatives extends React.Component {
498
533
  default:
499
534
  break;
500
535
  }
501
- const modifiedButtons = cloneDeep(buttons).map((btn) => {
536
+ const modifiedButtons = cloneDeep(buttons)?.map((btn) => {
502
537
  if (btn.type === 'PHONE_NUMBER') {
503
538
  btn.phone_number = btn.phoneNumber;
504
539
  delete btn.phoneNumber;
@@ -542,6 +577,10 @@ export class Creatives extends React.Component {
542
577
  },
543
578
  ],
544
579
  isPreviewUrl,
580
+ carouselData: this.mapCarouselDataToCreatives(cards),
581
+ ...(cards?.length && {
582
+ mediaType: WHATSAPP_MEDIA_TYPES.CAROUSEL,
583
+ }),
545
584
  },
546
585
  },
547
586
  },
@@ -588,6 +627,47 @@ export class Creatives extends React.Component {
588
627
  return templateData || null;
589
628
  }
590
629
 
630
+ getCarouselMappedData = (carouselData = []) => {
631
+ return carouselData.map((carousel) => {
632
+ const { bodyText, imageUrl, videoUrl, videoPreviewImg, buttons, mediaType, cardVarMapped, bodyTemplate } = carousel || {};
633
+ const buttonData = buttons.map((button, index) => {
634
+ const { type, text, phone_number, urlType, url } = button || {};
635
+ const buttonObj = {
636
+ type,
637
+ text,
638
+ index,
639
+ };
640
+ if (type === PHONE_NUMBER) {
641
+ buttonObj.phoneNumber = phone_number;
642
+ }
643
+ if (type === URL) {
644
+ buttonObj.url = url;
645
+ if (urlType === DYNAMIC_URL) {
646
+ const dynamicUrlPayload = url?.match(/{{(.*?)}}/g);
647
+ buttonObj.dynamicUrlPayload = dynamicUrlPayload?.length === 1 ? dynamicUrlPayload[0] : '';
648
+ }
649
+ }
650
+ return buttonObj;
651
+ });
652
+ return {
653
+ body: bodyText,
654
+ cardVarMapped,
655
+ bodyTemplate,
656
+ media: {
657
+ ...(mediaType?.toLowerCase() === IMAGE.toLowerCase() && {
658
+ url: imageUrl,
659
+ }),
660
+ ...(mediaType?.toLowerCase() === VIDEO.toLowerCase() && {
661
+ url: videoUrl,
662
+ previewUrl: videoPreviewImg,
663
+ }),
664
+ },
665
+ buttons: buttonData,
666
+ mediaType: mediaType?.toUpperCase(),
667
+ };
668
+ });
669
+ };
670
+
591
671
  getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
592
672
  let templateData = { channel };
593
673
  switch (channel) {
@@ -666,7 +746,10 @@ export class Creatives extends React.Component {
666
746
  forEach(androidContent.custom, (customKeyValue) => {
667
747
  custom[customKeyValue.key] = customKeyValue.value;
668
748
  });
669
- androidContent.custom = custom;
749
+ //skipping it for MPUSH as custom key is already in the reqd format for hydra sdk
750
+ if (channel !== constants.MOBILE_PUSH) {
751
+ androidContent.custom = custom;
752
+ }
670
753
  templateData.androidContent = androidContent;
671
754
  templateData.androidContent.type = get(channelTemplate, 'definition.mode', '').toUpperCase();
672
755
  templateData.androidContent.deviceType = 'ANDROID';
@@ -682,7 +765,10 @@ export class Creatives extends React.Component {
682
765
  forEach(iosContent.custom, (customKeyValue) => {
683
766
  custom[customKeyValue.key] = customKeyValue.value;
684
767
  });
685
- iosContent.custom = custom;
768
+ //skipping it for MPUSH as custom key is already in the reqd format for hydra sdk
769
+ if (channel !== constants.MOBILE_PUSH) {
770
+ iosContent.custom = custom;
771
+ }
686
772
  templateData.iosContent = iosContent;
687
773
  templateData.iosContent.type = get(channelTemplate, 'definition.mode').toUpperCase();
688
774
  templateData.iosContent.deviceType = IOS.toUpperCase();
@@ -760,6 +846,7 @@ export class Creatives extends React.Component {
760
846
  headerTemplate = '',
761
847
  } = {},
762
848
  isPreviewUrl = false,
849
+ carouselData = [],
763
850
  } = cloneDeep(versions.base.content.whatsapp);
764
851
 
765
852
  const modifiedButtons = cloneDeep(buttons).map((btn) => {
@@ -826,11 +913,17 @@ export class Creatives extends React.Component {
826
913
  varMapped,
827
914
  category,
828
915
  language: languages[0].language,
829
- buttonType,
830
- buttons: modifiedButtons,
916
+ ...(mediaType !== WHATSAPP_MEDIA_TYPES.CAROUSEL && {
917
+ buttonType,
918
+ buttons: modifiedButtons,
919
+ whatsappMedia,
920
+ }),
831
921
  mediaType,
832
- whatsappMedia,
833
922
  isPreviewUrl,
923
+ ...(mediaType === WHATSAPP_MEDIA_TYPES.CAROUSEL && {
924
+ cards: this.getCarouselMappedData(carouselData),
925
+ mediaType: carouselData[0]?.mediaType?.toUpperCase(),
926
+ }),
834
927
  },
835
928
  };
836
929
  }
@@ -1031,8 +1124,8 @@ export class Creatives extends React.Component {
1031
1124
 
1032
1125
  processCentralCommsMetaId = (channel, creativesData) => {
1033
1126
  // Create the payload for the centralcommnsmetaId API call
1034
- const { isLoyaltyModule, loyaltyMetaData } = this.props;
1035
- const { actionName, setMetaData = () => {} } = this.props.loyaltyMetaData || {};
1127
+ const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1128
+ const { actionName, setMetaData = () => {} } = loyaltyMetaData;
1036
1129
  const { formatMessage } = this.props.intl;
1037
1130
 
1038
1131
  // const isTemplateModified = getTemplateDiffState(
@@ -651,6 +651,7 @@ exports[`Test SlideBoxContent container Should render correct component for rcs
651
651
  "search": "",
652
652
  }
653
653
  }
654
+ loyaltyMetaData={Object {}}
654
655
  messageDetails={Object {}}
655
656
  onCreateComplete={[MockFunction]}
656
657
  slidBoxContent="templates"
@@ -935,6 +936,7 @@ exports[`Test SlideBoxContent container Should render correct component for what
935
936
  charCounterEnabled={false}
936
937
  content={
937
938
  Object {
939
+ "carouselData": Array [],
938
940
  "charCount": 151,
939
941
  "ctaData": Array [
940
942
  Object {
@@ -1041,6 +1043,7 @@ exports[`Test SlideBoxContent container Should render correct component for what
1041
1043
  "search": "",
1042
1044
  }
1043
1045
  }
1046
+ loyaltyMetaData={Object {}}
1044
1047
  messageDetails={Object {}}
1045
1048
  onCreateComplete={[MockFunction]}
1046
1049
  slidBoxContent="templates"
@@ -2747,7 +2747,6 @@ export class Email extends React.Component { // eslint-disable-line react/prefer
2747
2747
  eventContextTags={this.props?.eventContextTags}
2748
2748
  forwardedTags={this.props?.forwardedTags}
2749
2749
  isLoyaltyModule={this.props?.isLoyaltyModule}
2750
- messageDetails={this.props?.messageDetails}
2751
2750
  /> : ''}
2752
2751
  </Col>
2753
2752
  </Row>
@@ -0,0 +1,192 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import { FormattedMessage } from 'react-intl';
4
+ import styled from 'styled-components';
5
+ import isEmpty from 'lodash/isEmpty';
6
+ import get from 'lodash/get';
7
+ import CapRadioCard from '@capillarytech/cap-ui-library/CapRadioCard';
8
+ import CapInput from '@capillarytech/cap-ui-library/CapInput';
9
+ import CapUploader from '@capillarytech/cap-ui-library/CapUploader';
10
+ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
11
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
12
+ import CapError from '@capillarytech/cap-ui-library/CapError';
13
+ import ComponentWithLabelHOC from '@capillarytech/cap-ui-library/assets/HOCs/ComponentWithLabelHOC';
14
+ import Email from '../../Email';
15
+ import CmsTemplatesComponent from '../../../v2Components/CmsTemplatesComponent';
16
+ import messages from '../messages';
17
+ import { EMAIL_CREATE_MODES, STEPS } from '../constants';
18
+
19
+ const CapRadioCardWithLabel = ComponentWithLabelHOC(CapRadioCard);
20
+
21
+ const CardContainer = styled.div`
22
+ margin-top: 16px;
23
+ .ant-radio-group{
24
+ .ant-radio-button-wrapper{
25
+ &:first-child{
26
+ margin-left: unset;
27
+ }
28
+ }
29
+ }
30
+ `;
31
+
32
+ // Mode selection component that handles the creation mode selection UI
33
+ const ModeSelectionUI = ({
34
+ isFullMode,
35
+ templateName,
36
+ onTemplateNameChange,
37
+ isTemplateNameEmpty,
38
+ modes,
39
+ emailCreateMode,
40
+ onChange,
41
+ EmailLayout,
42
+ modeContent,
43
+ useFileUpload,
44
+ uploadButtonLabel,
45
+ }) => (
46
+ <>
47
+ {isFullMode && (
48
+ <CapInput
49
+ label={<FormattedMessage {...messages.creativeName} />}
50
+ onChange={onTemplateNameChange}
51
+ value={templateName}
52
+ labelPosition="top"
53
+ size="default"
54
+ style={{ width: '68%'}}
55
+ />
56
+ )}
57
+ <CardContainer>
58
+ <CapRadioCardWithLabel
59
+ panes={modes}
60
+ onChange={onChange}
61
+ selected={emailCreateMode}
62
+ label={<FormattedMessage {...messages.createMode} />}
63
+ />
64
+ </CardContainer>
65
+ <>
66
+ {emailCreateMode === EMAIL_CREATE_MODES.UPLOAD && (
67
+ <div style={{ marginLeft: '8px' }}>
68
+ <CapUploader onChange={useFileUpload} accept=".zip, .html, .htm" showUploadList={false}>
69
+ {(isFullMode && isTemplateNameEmpty) && (
70
+ <CapError type="error">
71
+ <FormattedMessage {...messages.emptyTemplateName} />
72
+ </CapError>
73
+ )}
74
+ <CapButton disabled={isFullMode && isTemplateNameEmpty}>
75
+ {uploadButtonLabel}
76
+ </CapButton>
77
+ </CapUploader>
78
+ {!isEmpty(EmailLayout) && (
79
+ <>{get(modeContent, "file.name")}</>
80
+ )}
81
+ </div>
82
+ )}
83
+ </>
84
+ </>
85
+ );
86
+
87
+ ModeSelectionUI.propTypes = {
88
+ isFullMode: PropTypes.bool,
89
+ templateName: PropTypes.string,
90
+ onTemplateNameChange: PropTypes.func.isRequired,
91
+ isTemplateNameEmpty: PropTypes.bool,
92
+ modes: PropTypes.array.isRequired,
93
+ emailCreateMode: PropTypes.string,
94
+ onChange: PropTypes.func.isRequired,
95
+ EmailLayout: PropTypes.object,
96
+ modeContent: PropTypes.object,
97
+ useFileUpload: PropTypes.func.isRequired,
98
+ uploadButtonLabel: PropTypes.node.isRequired,
99
+ };
100
+
101
+ // Content creation component that handles the email or template selection UI
102
+ const ContentCreationUI = ({
103
+ isShowEmailCreate,
104
+ emailProps,
105
+ cmsTemplatesProps,
106
+ }) => (
107
+ <>
108
+ {isShowEmailCreate ? (
109
+ <Email {...emailProps} />
110
+ ) : (
111
+ <CmsTemplatesComponent {...cmsTemplatesProps} />
112
+ )}
113
+ </>
114
+ );
115
+
116
+ ContentCreationUI.propTypes = {
117
+ isShowEmailCreate: PropTypes.bool.isRequired,
118
+ emailProps: PropTypes.object.isRequired,
119
+ cmsTemplatesProps: PropTypes.object.isRequired,
120
+ };
121
+
122
+ // Main EmailWrapper presentational component
123
+ const EmailWrapperView = ({
124
+ isUploading,
125
+ emailCreateMode,
126
+ step,
127
+ isFullMode,
128
+ templateName,
129
+ onTemplateNameChange,
130
+ isTemplateNameEmpty,
131
+ modes,
132
+ onChange,
133
+ EmailLayout,
134
+ modeContent,
135
+ useFileUpload,
136
+ uploadButtonLabel,
137
+ isShowEmailCreate,
138
+ emailProps,
139
+ cmsTemplatesProps,
140
+ }) => {
141
+ console.log("EmailLayout", step, emailCreateMode);
142
+ const isShowTemplateSelection = step === STEPS.MODE_SELECTION || (step === STEPS.TEMPLATE_SELECTION && emailCreateMode === EMAIL_CREATE_MODES.UPLOAD);
143
+
144
+ return (
145
+ <>
146
+ <CapSpin spinning={emailCreateMode === EMAIL_CREATE_MODES.UPLOAD ? isUploading : false}>
147
+ {isShowTemplateSelection ? (
148
+ <ModeSelectionUI
149
+ isFullMode={isFullMode}
150
+ templateName={templateName}
151
+ onTemplateNameChange={onTemplateNameChange}
152
+ isTemplateNameEmpty={isTemplateNameEmpty}
153
+ modes={modes}
154
+ emailCreateMode={emailCreateMode}
155
+ onChange={onChange}
156
+ EmailLayout={EmailLayout}
157
+ modeContent={modeContent}
158
+ useFileUpload={useFileUpload}
159
+ uploadButtonLabel={uploadButtonLabel}
160
+ />
161
+ ) : (
162
+ <ContentCreationUI
163
+ isShowEmailCreate={isShowEmailCreate}
164
+ emailProps={emailProps}
165
+ cmsTemplatesProps={cmsTemplatesProps}
166
+ />
167
+ )}
168
+ </CapSpin>
169
+ </>
170
+ );
171
+ };
172
+
173
+ EmailWrapperView.propTypes = {
174
+ isUploading: PropTypes.bool,
175
+ emailCreateMode: PropTypes.string,
176
+ step: PropTypes.string,
177
+ isFullMode: PropTypes.bool,
178
+ templateName: PropTypes.string,
179
+ onTemplateNameChange: PropTypes.func.isRequired,
180
+ isTemplateNameEmpty: PropTypes.bool,
181
+ modes: PropTypes.array.isRequired,
182
+ onChange: PropTypes.func.isRequired,
183
+ EmailLayout: PropTypes.object,
184
+ modeContent: PropTypes.object,
185
+ useFileUpload: PropTypes.func.isRequired,
186
+ uploadButtonLabel: PropTypes.node.isRequired,
187
+ isShowEmailCreate: PropTypes.bool.isRequired,
188
+ emailProps: PropTypes.object.isRequired,
189
+ cmsTemplatesProps: PropTypes.object.isRequired,
190
+ };
191
+
192
+ export default EmailWrapperView;
@@ -4,4 +4,14 @@
4
4
  *
5
5
  */
6
6
 
7
- export const DEFAULT_ACTION = 'app/EmailWrapper/DEFAULT_ACTION';
7
+ export const DEFAULT_ACTION = "app/EmailWrapper/DEFAULT_ACTION";
8
+ export const EMAIL_CREATE_MODES = {
9
+ UPLOAD: "upload",
10
+ EDITOR: "editor",
11
+ };
12
+
13
+ export const STEPS = {
14
+ MODE_SELECTION: "modeSelection",
15
+ TEMPLATE_SELECTION: "templateSelection",
16
+ CREATE_TEMPLATE_CONTENT: "createTemplateContent",
17
+ };