@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
@@ -43,3 +43,9 @@ export const RESET_TEMPLATE = "app/containers/Templates/RESET_TEMPLATE";
43
43
  export const RESET_TEMPLATE_DATA = "app/containers/Templates/RESET_TEMPLATE_DATA";
44
44
  export const RESET_UPLOAD_DATA = "app/containers/Templates/RESET_UPLOAD_DATA";
45
45
  export const CLEAR_TEMPLATE_STORE_DATA = "app/containers/Templates/CLEAR_TEMPLATE_STORE_DATA";
46
+
47
+
48
+ export const TRUE = 'true';
49
+ export const FALSE = 'false';
50
+ export const EMAIL = 'email';
51
+ export const TYPE_EMBEDDED = 'embedded';
@@ -57,6 +57,8 @@ import lineCreateReducer from '../Line/Create/reducer';
57
57
  import { ebillSaga } from '../Ebill/sagas';
58
58
  import { lineCreateSaga } from '../Line/Create/sagas';
59
59
  import { duplicateMPushSaga } from '../MobilePush/Create/sagas';
60
+ import { FALSE, TRUE, TYPE_EMBEDDED } from './constants';
61
+ import { DEFAULT } from '../../v2Containers/Cap/constants';
60
62
 
61
63
  const MenuItem = Menu.Item;
62
64
  const Option = Select.Option;
@@ -914,31 +916,49 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
914
916
 
915
917
  handleEdmDefaultTemplateSelection = (isSelected, id) => {
916
918
  if (isSelected) {
917
- const data = _.find(this.props.Templates.cmsTemplates, {_id: id});
918
- const type = this.props.location.query.type
919
- const module = this.props.location.query.module ? this.props.location.query.module : 'default';
920
- const isLanguageSupport = this.props.location.query.isLanguageSupport ? this.props.location.query.isLanguageSupport : true;
921
- const isEdmSupport = (this.props.location.query.isEdmSupport !== "false") || false;
919
+ const template = _.find(this.props?.Templates?.cmsTemplates, (template = []) => {
920
+ return template?.versions && template?.versions?.base && _.get(template, 'versions.base.drag_drop_id') === id;
921
+ });
922
+ const {type,module = DEFAULT} = this.props?.location?.query;
923
+ const isLanguageSupport = this.props?.location?.query?.isLanguageSupport === TRUE;
924
+ const isEdmSupport = this.props?.location?.query?.isEdmSupport !== FALSE;
922
925
  let getQuery = '';
923
-
924
- this.props.actions.setEdmTemplate(data);
925
- if (this.state.channel && this.state.channel.toLowerCase() === 'email') {
926
- getQuery = (type === 'embedded') ? {type: 'embedded', module, isLanguageSupport, isEdmSupport} : {module, isLanguageSupport, isEdmSupport};
927
- } else {
928
- getQuery = (type === 'embedded') ? {type: 'embedded', module} : {module};
929
- }
930
- if (this.isEnabledInLibraryModule("callCreateFromProps")) {
931
- this.props.createNew();
932
- return;
933
- }
926
+
927
+ if (template) {
928
+ this.props.actions.setEdmTemplate({
929
+ ...template,
930
+ isEdmSupport,
931
+ isLanguageSupport,
932
+ projectId: template?.versions?.base?.drag_drop_id,
933
+ });
934
+
935
+ if (this.state.channel && this.state.channel.toLowerCase() === EMAIL) {
936
+ getQuery = (type === TYPE_EMBEDDED) ? {
937
+ type: TYPE_EMBEDDED,
938
+ module,
939
+ isLanguageSupport: isLanguageSupport ? 1 : 0,
940
+ isEdmSupport: isEdmSupport ? 1 : 0,
941
+ projectId: template?.versions?.base?.drag_drop_id,
942
+ } : {
943
+ module,
944
+ isLanguageSupport: isLanguageSupport ? 1 : 0,
945
+ isEdmSupport: isEdmSupport ? 1 : 0,
946
+ projectId: template?.versions?.base?.drag_drop_id,
947
+ };
948
+ } else {
949
+ getQuery = (type === TYPE_EMBEDDED) ? {type: TYPE_EMBEDDED, module} : {module};
950
+ }
934
951
 
935
- const queryParams = commonUtil.createQueryString(getQuery);
936
- // this.props.router.push(`${pathName}${queryParams}`);
952
+ if (this.isEnabledInLibraryModule("callCreateFromProps")) {
953
+ this.props.createNew();
954
+ return;
955
+ }
937
956
 
938
- this.props.router.push({
939
- pathname: `/${this.state.channel.toLowerCase()}/create`,
940
- search: getQuery,
941
- });
957
+ this.props.router.push({
958
+ pathname: `/${this.state.channel.toLowerCase()}/create`,
959
+ search: commonUtil.createQueryString(getQuery),
960
+ });
961
+ }
942
962
  }
943
963
  };
944
964
 
@@ -1128,7 +1148,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1128
1148
  const imgSrc = template && template.versions && template.versions.base ? template.versions.base.preview_http_url : '';
1129
1149
  const ifPreviewGenerated = template && template.versions && template.versions.base ? template.versions.base.isPreviewGenerated : 0;
1130
1150
  content = <img src={imgSrc || ''} alt={this.props.intl.formatMessage(messages.previewGenerateText)} width="100%" height="100%"/>;
1131
-
1151
+
1132
1152
  temp.content = (
1133
1153
  <div
1134
1154
  className="sms-template-content">
@@ -1136,7 +1156,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1136
1156
  {content}
1137
1157
  </div>
1138
1158
  {!layoutSelection && template._id === this.state.hoveredItem ? <CapButton onClick={(e) => this.handleEditClick(e, template._id)} className="edit-button" type="secondary">{type === 'embedded' ? this.props.intl.formatMessage(messages.selectButton) : this.props.intl.formatMessage(messages.editButton)}</CapButton> : ''}
1139
- {layoutSelection && template._id === this.state.hoveredItem ? <CapButton onClick={(event) =>{this.handleEdmDefaultTemplateSelection(true, template._id); event.stopPropagation();}} className="select-button" type="secondary">{type === 'embedded' ? this.props.intl.formatMessage(messages.selectDefaultButton) : this.props.intl.formatMessage(messages.selectDefaultButton)}</CapButton> : ''}
1159
+ {layoutSelection && template._id === this.state.hoveredItem ? <CapButton onClick={(event) =>{this.handleEdmDefaultTemplateSelection(true, template?.versions?.base?.drag_drop_id); event.stopPropagation();}} className="select-button" type="secondary">{type === 'embedded' ? this.props.intl.formatMessage(messages.selectDefaultButton) : this.props.intl.formatMessage(messages.selectDefaultButton)}</CapButton> : ''}
1140
1160
  {!layoutSelection && template._id === this.state.hoveredItem ? <CapButton onClick={() => this.handlePreviewClick(template)} className="preview-button" type="cancel">{this.props.intl.formatMessage(messages.previewButton)}</CapButton> : ''}
1141
1161
  </div>);
1142
1162
  } else if (this.state.channel.toLowerCase() === 'mobilepush') {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.87-alpha.21",
4
+ "version": "8.0.87-alpha.23",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
package/services/api.js CHANGED
@@ -9,7 +9,7 @@ import config from '../config/app';
9
9
  import pathConfig from '../config/path';
10
10
  import getSchema from './getSchema';
11
11
  import * as API from '../utils/ApiCaller';
12
- import { addBaseToTemplate } from '../utils/commonUtils';
12
+ import { addBaseToTemplate, transformCustomFieldsData } from '../utils/commonUtils';
13
13
  import { EMAIL, SMS } from '../v2Containers/CreativesContainer/constants';
14
14
  let API_ENDPOINT = config.development.api_endpoint;
15
15
  let EXPORT_API_ENDPOINT = config.development.exports_api_endpoint;
@@ -359,7 +359,13 @@ export const fetchSchemaForEntity = async ({queryParams}) => {
359
359
  });
360
360
  }
361
361
  const url = `${API_ENDPOINT}/meta/${queryParams.type}?query=${JSON.stringify(queryParams)}`;
362
- return request(url, getAPICallObject('GET'));// request(url, getAPICallObject('GET'));
362
+ const result = await request(url, getAPICallObject('GET'));// request(url, getAPICallObject('GET'));
363
+ const customFields = get(result, 'response.metaEntities.custom', {});
364
+ if (customFields && Object.keys(customFields)?.length > 0) {
365
+ const transformedData = transformCustomFieldsData(customFields);
366
+ result.response.metaEntities.custom = transformedData;
367
+ }
368
+ return result;
363
369
  };
364
370
 
365
371
  export const fetchWeCrmAccounts = (sourceName) => {
@@ -471,14 +477,12 @@ export const getUnsubscribeUrl = () => {
471
477
  return request(url, getAPICallObject('POST', body));
472
478
  };
473
479
 
474
- export const getMarketingObjectives = (channel) =>
475
- request(
476
- `${CAMPAIGNS_API_ENDPOINT}/socialObjectives?channel=${channel}`,
477
- getAPICallObject('GET', null, false, true),
478
- );
480
+ export const getMarketingObjectives = (channel) => request(
481
+ `${CAMPAIGNS_API_ENDPOINT}/socialObjectives?channel=${channel}`,
482
+ getAPICallObject('GET', null, false, true),
483
+ );
479
484
 
480
- export const getAllLineStickers = () =>
481
- request(`${API_ENDPOINT}/meta/sticker`, getAPICallObject('GET'));
485
+ export const getAllLineStickers = () => request(`${API_ENDPOINT}/meta/sticker`, getAPICallObject('GET'));
482
486
 
483
487
 
484
488
  export const createCustomRow = (data) => {
@@ -520,7 +524,13 @@ export const checkBulkDuplicates = (data) => {
520
524
  export const createWhatsappTemplate = ({payload, gupshupMediaFile }) => {
521
525
  const data = new FormData();
522
526
  data.append('payload', JSON.stringify(payload));
523
- data.append('gupshupMediaFile', gupshupMediaFile);
527
+ if (Array.isArray(gupshupMediaFile) && gupshupMediaFile?.length) {
528
+ gupshupMediaFile.forEach((file, index) => {
529
+ data.append(`gupshupMediaFile_${index}`, file);
530
+ });
531
+ } else {
532
+ data.append('gupshupMediaFile', gupshupMediaFile);
533
+ }
524
534
  const url = `${API_ENDPOINT}/templates/WHATSAPP`;
525
535
  return request(url, getAPICallObject('POST', data, true));
526
536
  };
@@ -569,8 +579,8 @@ export const getLiquidTags = (content) => {
569
579
  return request(url, getAPICallObject("POST", { content }, false, true));
570
580
  };
571
581
 
572
- export const createCentralCommsMetaId = (payload, metaId = 'TRANSACTION') => {
573
- const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaId}`;
582
+ export const createCentralCommsMetaId = (payload, metaType = 'TRANSACTION') => {
583
+ const url = `${API_ENDPOINT}/common/central-comms/meta-id/${metaType}`;
574
584
  return request(url, getAPICallObject('POST', payload, false, false, false, true));
575
585
  };
576
586
 
@@ -86,7 +86,11 @@ describe('uploadFile -- whatsapp image upload', () => {
86
86
  describe('createWhatsappTemplate -- Test with valid responses', () => {
87
87
  it('Should return correct response', () =>
88
88
  expect(
89
- createWhatsappTemplate(sampleFile, mockData.createWhatsappPayload),
89
+ createWhatsappTemplate({gupshupMediaFile: [sampleFile], payload: mockData.createWhatsappPayload}),
90
+ ).toEqual(Promise.resolve()));
91
+ it('Should return correct response when single file is present', () =>
92
+ expect(
93
+ createWhatsappTemplate({gupshupMediaFile: sampleFile, payload: mockData.createWhatsappPayload}),
90
94
  ).toEqual(Promise.resolve()));
91
95
  });
92
96
 
@@ -9,23 +9,77 @@ export const apiMessageFormatHandler = (id, fallback) => (
9
9
  );
10
10
 
11
11
  export const addBaseToTemplate = (template) => {
12
- const { versions: {
13
- history = [],
14
- } = {}} = template || {};
12
+ const {
13
+ versions: {
14
+ history = [],
15
+ } = {},
16
+ } = template || {};
15
17
  if (history?.length) {
16
18
  return ({
17
19
  ...template,
18
20
  versions: {
19
21
  ...template.versions,
20
- base:{
22
+ base: {
21
23
  ...history[0],
22
- ...( !history?.[0]?.subject && { subject: get(template, 'versions.base.subject') })
23
- }
24
- }
24
+ ...( !history?.[0]?.subject && { subject: get(template, 'versions.base.subject') }),
25
+ },
26
+ },
25
27
  });
26
28
  }
27
- return template;
29
+ return template;
30
+ };
31
+
32
+ export const isEmbeddedEditOrPreview = (queryType, creativesMode) => queryType === EMBEDDED && [EDIT, PREVIEW].includes(creativesMode);
33
+
34
+ export const transformCustomFieldsData = (customFields) => {
35
+ // Define mapping of scopes to their display names
36
+ const scopeMapping = {
37
+ loyalty_registration: 'Registration custom fields',
38
+ loyalty_transaction: 'Registration custom fields',
39
+ store_custom_fields: 'Organization custom fields',
40
+ org_custom_field: 'Organization custom fields',
41
+ };
42
+
43
+ // Initialize result structure with all possible sections
44
+ const result = Object.values(scopeMapping).reduce((acc, sectionName) => {
45
+ acc[sectionName] = {
46
+ name: sectionName,
47
+ subtags: {},
48
+ };
49
+ return acc;
50
+ }, {});
51
+
52
+ // Process each custom field
53
+ Object.values(customFields).forEach((field) => {
54
+ const {scope = ''} = field;
55
+ const sectionName = scopeMapping[scope];
56
+
57
+ if (sectionName) {
58
+ // Determine the prefix based on scope
59
+ let prefix = 'custom_field';
60
+ if (scope === 'org_custom_field') {
61
+ prefix = 'org_custom_field';
62
+ } else if (scope === 'store_custom_fields') {
63
+ prefix = 'store_custom_field';
64
+ }
65
+
66
+ const fieldKey = `${prefix}.${field?.name}`;
67
+ // Add field to appropriate section
68
+ result[sectionName].subtags[fieldKey] = {
69
+ name: field?.name,
70
+ desc: field?.name || field?.label,
71
+ };
72
+ }
73
+ });
74
+
75
+ // Remove empty sections (those with no subtags)
76
+ Object.keys(result).forEach((sectionName) => {
77
+ if (Object.keys(result[sectionName]?.subtags)?.length === 0) {
78
+ delete result[sectionName];
79
+ }
80
+ });
81
+
82
+ return result;
28
83
  };
29
84
 
30
- export const isEmbeddedEditOrPreview = (queryType, creativesMode) => queryType === EMBEDDED &&
31
- [EDIT, PREVIEW].includes(creativesMode);
85
+ export const isTagIncluded = (value) => value && value.includes('{{') && value.includes('}}');
@@ -2,10 +2,9 @@ import "@testing-library/jest-dom";
2
2
  import get from 'lodash/get';
3
3
  import { getTreeStructuredTags } from "../common";
4
4
  import * as mockdata from "./common.mockdata";
5
- import { addBaseToTemplate, isEmbeddedEditOrPreview } from "../commonUtils";
5
+ import { addBaseToTemplate, isEmbeddedEditOrPreview, transformCustomFieldsData } from "../commonUtils";
6
6
  import { EMBEDDED, FULL } from "../../v2Containers/Whatsapp/constants";
7
7
  import { CREATE, EDIT, PREVIEW } from "../../v2Containers/App/constants";
8
- import { query } from "express";
9
8
 
10
9
  jest.mock('@capillarytech/cap-ui-utils', () => ({
11
10
  Auth: {
@@ -38,7 +37,7 @@ describe('addBaseToTemplate', () => {
38
37
  versions: {
39
38
  history: ['v1', 'v2', 'v3'],
40
39
  base: {
41
- "0":"v","1":"1","subject":undefined,
40
+ 0: "v", 1: "1", subject: undefined,
42
41
  },
43
42
  },
44
43
  };
@@ -115,3 +114,109 @@ describe('isEmbeddedEditOrPreview', () => {
115
114
  expect(isEmbeddedEditOrPreview(queryType, creativesMode)).toBe(false);
116
115
  });
117
116
  });
117
+
118
+ describe('transformCustomFieldsData', () => {
119
+ it('should transform registration custom fields correctly', () => {
120
+ const input = {
121
+ 1: {
122
+ name: 'age_group',
123
+ label: 'Age Group',
124
+ scope: 'loyalty_registration',
125
+ },
126
+ 2: {
127
+ name: 'gender',
128
+ label: 'Gender',
129
+ scope: 'loyalty_registration',
130
+ },
131
+ };
132
+
133
+ const expected = {
134
+ 'Registration custom fields': {
135
+ name: 'Registration custom fields',
136
+ subtags: {
137
+ 'custom_field.age_group': {
138
+ name: 'age_group',
139
+ desc: 'age_group',
140
+ },
141
+ 'custom_field.gender': {
142
+ name: 'gender',
143
+ desc: 'gender',
144
+ },
145
+ },
146
+ },
147
+ };
148
+
149
+ expect(transformCustomFieldsData(input)).toEqual(expected);
150
+ });
151
+
152
+ it('should transform organization custom fields correctly', () => {
153
+ const input = {
154
+ 1: {
155
+ name: 'org_phone',
156
+ label: 'Phone',
157
+ scope: 'org_custom_field',
158
+ },
159
+ 2: {
160
+ name: 'store_location',
161
+ label: 'Location',
162
+ scope: 'store_custom_fields',
163
+ },
164
+ };
165
+
166
+ const expected = {
167
+ 'Organization custom fields': {
168
+ name: 'Organization custom fields',
169
+ subtags: {
170
+ 'org_custom_field.org_phone': {
171
+ name: 'org_phone',
172
+ desc: 'org_phone',
173
+ },
174
+ 'store_custom_field.store_location': {
175
+ name: 'store_location',
176
+ desc: 'store_location',
177
+ },
178
+ },
179
+ },
180
+ };
181
+
182
+ expect(transformCustomFieldsData(input)).toEqual(expected);
183
+ });
184
+
185
+ it('should handle empty input correctly', () => {
186
+ expect(transformCustomFieldsData({})).toEqual({});
187
+ });
188
+
189
+ it('should handle fields with missing properties', () => {
190
+ const input = {
191
+ 1: {
192
+ scope: 'loyalty_registration',
193
+ },
194
+ };
195
+
196
+ const expected = {
197
+ 'Registration custom fields': {
198
+ name: 'Registration custom fields',
199
+ subtags: {
200
+ 'custom_field.undefined': {
201
+ name: undefined,
202
+ desc: undefined,
203
+ },
204
+ },
205
+ },
206
+ };
207
+
208
+ expect(transformCustomFieldsData(input)).toEqual(expected);
209
+ });
210
+
211
+ it('should ignore fields with invalid scope', () => {
212
+ const input = {
213
+ 1: {
214
+ name: 'test',
215
+ label: 'Test',
216
+ scope: 'invalid_scope',
217
+ },
218
+ };
219
+
220
+ expect(transformCustomFieldsData(input)).toEqual({});
221
+ });
222
+ });