@capillarytech/creatives-library 8.0.236-beta.0 → 8.0.237

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 (54) hide show
  1. package/config/app.js +1 -1
  2. package/constants/unified.js +0 -1
  3. package/package.json +1 -1
  4. package/services/api.js +0 -5
  5. package/utils/common.js +1 -6
  6. package/v2Components/CapTagList/index.js +1 -2
  7. package/v2Components/CapTagListWithInput/index.js +1 -5
  8. package/v2Components/CapTagListWithInput/messages.js +1 -1
  9. package/v2Components/ErrorInfoNote/style.scss +1 -1
  10. package/v2Components/HtmlEditor/HTMLEditor.js +14 -86
  11. package/v2Components/HtmlEditor/_htmlEditor.scss +4 -0
  12. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  13. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +98 -11
  14. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +115 -174
  15. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  16. package/v2Components/HtmlEditor/hooks/useEditorContent.js +2 -5
  17. package/v2Components/TestAndPreviewSlidebox/index.js +25 -31
  18. package/v2Containers/CreativesContainer/SlideBoxContent.js +35 -83
  19. package/v2Containers/CreativesContainer/SlideBoxFooter.js +3 -9
  20. package/v2Containers/CreativesContainer/index.js +11 -83
  21. package/v2Containers/CreativesContainer/messages.js +0 -4
  22. package/v2Containers/Email/actions.js +0 -7
  23. package/v2Containers/Email/constants.js +1 -5
  24. package/v2Containers/Email/index.js +0 -13
  25. package/v2Containers/Email/messages.js +0 -32
  26. package/v2Containers/Email/reducer.js +1 -12
  27. package/v2Containers/Email/sagas.js +0 -17
  28. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +7 -193
  29. package/v2Containers/EmailWrapper/constants.js +0 -2
  30. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +67 -436
  31. package/v2Containers/EmailWrapper/index.js +23 -99
  32. package/v2Containers/EmailWrapper/messages.js +1 -61
  33. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +49 -49
  34. package/v2Containers/Rcs/index.js +5 -3
  35. package/v2Containers/Rcs/sagas.js +4 -4
  36. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +0 -16
  37. package/v2Containers/Rcs/tests/index.test.js +139 -2
  38. package/v2Containers/Rcs/tests/mockData.js +6 -3
  39. package/v2Containers/Rcs/tests/saga.test.js +88 -0
  40. package/v2Containers/TagList/index.js +0 -2
  41. package/v2Containers/Templates/actions.js +2 -6
  42. package/v2Containers/Templates/constants.js +1 -0
  43. package/v2Containers/Templates/index.js +66 -39
  44. package/v2Containers/Templates/messages.js +8 -0
  45. package/v2Containers/Templates/reducer.js +3 -3
  46. package/v2Containers/Templates/sagas.js +1 -1
  47. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +8 -8
  48. package/v2Containers/Templates/tests/actions.test.js +36 -1
  49. package/v2Containers/Templates/tests/reducer.test.js +9 -2
  50. package/v2Containers/Templates/tests/sagas.test.js +262 -9
  51. package/HOW_BEE_EDITOR_WORKS.md +0 -375
  52. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +0 -1034
  53. package/v2Containers/EmailWrapper/tests/EmailHTMLEditor.test.js +0 -177
  54. package/v2Containers/EmailWrapper/tests/EmailHTMLEditorValidation.test.js +0 -90
@@ -578,7 +578,12 @@ describe('RCS createPayload', () => {
578
578
  expect(doneBtn.exists()).toBe(true);
579
579
  await act(async () => {
580
580
  const onClick = doneBtn.prop('onClick');
581
- if (typeof onClick === 'function') onClick();
581
+ if (typeof onClick === 'function') {
582
+ const result = onClick();
583
+ if (typeof result === 'function') {
584
+ await result();
585
+ }
586
+ }
582
587
  await Promise.resolve();
583
588
  });
584
589
 
@@ -594,7 +599,10 @@ describe('RCS createPayload', () => {
594
599
  expect(payloadArg.name).toBe('PromoCard');
595
600
  expect(payloadArg.type).toBe('RCS');
596
601
  expect(payloadArg.versions.base.content.RCS.rcsContent.contentType).toBe('rich_card');
597
- expect(payloadArg.versions.base.content.RCS.rcsContent.isHostIcs).toBe(true);
602
+ expect(payloadArg.versions.base.content.RCS.rcsContent.accountId).toBe('rcs-account-123');
603
+ expect(payloadArg.versions.base.content.RCS.rcsContent.accessToken).toBe('secret-token');
604
+ expect(payloadArg.versions.base.content.RCS.rcsContent.hostName).toBe('rcs.host.example.com');
605
+ expect(payloadArg.versions.base.content.RCS.rcsContent.accountName).toBe('Brand RCS Account');
598
606
  expect(payloadArg.versions.base.content.RCS.rcsContent.cardSettings).toEqual(
599
607
  expect.objectContaining({ cardOrientation: 'HORIZONTAL', mediaAlignment: 'LEFT', cardWidth: 'SMALL' }),
600
608
  );
@@ -605,6 +613,135 @@ describe('RCS createPayload', () => {
605
613
  );
606
614
  });
607
615
 
616
+ it('should include empty account metadata when no account is selected', async () => {
617
+ const createRcsTemplate = jest.fn();
618
+ const getTemplateDetails = jest.fn();
619
+ const getFormData = jest.fn();
620
+ const { createPayloadFullMode } = mockData;
621
+
622
+ const wrapper = mountWithIntl(
623
+ <Provider store={store}>
624
+ <Rcs
625
+ actions={{
626
+ createRcsTemplate,
627
+ clearCreateResponse: jest.fn(),
628
+ getTemplateDetails,
629
+ uploadRcsAsset: jest.fn(),
630
+ clearRcsMediaAsset: jest.fn(),
631
+ editTemplate: jest.fn(),
632
+ clearEditResponse: jest.fn(),
633
+ }}
634
+ globalActions={{ fetchSchemaForEntity }}
635
+ onCreateComplete={onCreateComplete}
636
+ handleClose={handleClose}
637
+ intl={{ formatMessage }}
638
+ location={{ pathname: '/rcs/create', query: { type: false, module: 'default' }, search: '' }}
639
+ params={params}
640
+ rcsData={createPayloadFullMode}
641
+ accountData={{}}
642
+ isFullMode
643
+ isEditFlow={false}
644
+ loadingTags={false}
645
+ metaEntities={[]}
646
+ isDltEnabled={false}
647
+ smsRegister={'DLT'}
648
+ getFormData={getFormData}
649
+ />
650
+ </Provider>,
651
+ );
652
+
653
+ await act(async () => {
654
+ await Promise.resolve();
655
+ });
656
+ wrapper.update();
657
+
658
+ const doneBtn = wrapper.find('CapButton.rcs-done-btn').at(0);
659
+ expect(doneBtn.exists()).toBe(true);
660
+ await act(async () => {
661
+ const onClick = doneBtn.prop('onClick');
662
+ if (typeof onClick === 'function') {
663
+ onClick();
664
+ }
665
+ await Promise.resolve();
666
+ });
667
+ wrapper.update();
668
+
669
+ const payloadArg =
670
+ createRcsTemplate.mock.calls[0]?.[0] ||
671
+ getFormData.mock.calls[0]?.[0]?.value;
672
+ expect(payloadArg).toBeDefined();
673
+ const rcsContent = payloadArg.versions.base.content.RCS.rcsContent;
674
+ expect(rcsContent.accountId).toBe('');
675
+ expect(rcsContent.accessToken).toBe('');
676
+ expect(rcsContent.hostName).toBe('');
677
+ expect(rcsContent.accountName).toBe('');
678
+ });
679
+
680
+ it('should attach DLT template configs when enabled', async () => {
681
+ const createRcsTemplate = jest.fn();
682
+ const getFormData = jest.fn();
683
+ const { createPayloadFullMode } = mockData;
684
+
685
+ const wrapper = mountWithIntl(
686
+ <Provider store={store}>
687
+ <Rcs
688
+ actions={{
689
+ createRcsTemplate,
690
+ clearCreateResponse: jest.fn(),
691
+ getTemplateDetails: jest.fn(),
692
+ uploadRcsAsset: jest.fn(),
693
+ clearRcsMediaAsset: jest.fn(),
694
+ editTemplate: jest.fn(),
695
+ clearEditResponse: jest.fn(),
696
+ }}
697
+ globalActions={{ fetchSchemaForEntity }}
698
+ onCreateComplete={onCreateComplete}
699
+ handleClose={handleClose}
700
+ intl={{ formatMessage }}
701
+ location={{ pathname: '/rcs/create', query: { type: false, module: 'default' }, search: '' }}
702
+ params={params}
703
+ rcsData={createPayloadFullMode}
704
+ accountData={createPayloadFullMode.accountData}
705
+ isFullMode
706
+ isEditFlow={false}
707
+ loadingTags={false}
708
+ metaEntities={[]}
709
+ isDltEnabled
710
+ smsRegister={'DLT'}
711
+ getFormData={getFormData}
712
+ />
713
+ </Provider>,
714
+ );
715
+
716
+ await act(async () => Promise.resolve());
717
+ wrapper.update();
718
+
719
+ const doneBtn = wrapper.find('CapButton.rcs-done-btn').at(0);
720
+ await act(async () => {
721
+ const onClick = doneBtn.prop('onClick');
722
+ if (typeof onClick === 'function') {
723
+ onClick();
724
+ }
725
+ await Promise.resolve();
726
+ });
727
+ wrapper.update();
728
+
729
+ const payloadArg =
730
+ createRcsTemplate.mock.calls[0]?.[0] ||
731
+ getFormData.mock.calls[0]?.[0]?.value;
732
+ expect(payloadArg).toBeDefined();
733
+ expect(payloadArg.versions.base.content.RCS.smsFallBackContent).toEqual(
734
+ expect.objectContaining({
735
+ templateConfigs: expect.objectContaining({
736
+ templateId: '',
737
+ templateName: '',
738
+ template: '',
739
+ registeredSenderIds: [],
740
+ }),
741
+ }),
742
+ );
743
+ });
744
+
608
745
  it('should build expected payload in non-full mode (VIDEO, vertical medium)', () => {
609
746
  const getFormData = jest.fn();
610
747
  const { createPayloadNonFullMode } = mockData;
@@ -24,7 +24,6 @@ export const mockData = {
24
24
  },
25
25
  cardType: "STANDALONE",
26
26
  contentType: "text_message",
27
- isHostIcs: true,
28
27
  smsFallBackContent: {
29
28
  message: "Fallback SMS for text",
30
29
  },
@@ -71,7 +70,6 @@ export const mockData = {
71
70
  },
72
71
  cardType: "STANDALONE",
73
72
  contentType: "rich_card",
74
- isHostIcs: true,
75
73
  smsFallBackContent: {
76
74
  message: "Fallback SMS for image",
77
75
  },
@@ -106,7 +104,6 @@ export const mockData = {
106
104
  },
107
105
  cardType: "STANDALONE",
108
106
  contentType: "rich_card",
109
- isHostIcs: true,
110
107
  smsFallBackContent: {
111
108
  message: "Fallback SMS for video",
112
109
  },
@@ -122,6 +119,10 @@ export const mockData = {
122
119
  name: 'PromoCard',
123
120
  versions: {
124
121
  base: {
122
+ template_id: 'TPL-001',
123
+ template_name: 'PromoTPL',
124
+ 'sms-editor': '<p>SMS body</p>',
125
+ header: ['SENDER1'],
125
126
  content: {
126
127
  RCS: {
127
128
  rcsContent: {
@@ -149,6 +150,8 @@ export const mockData = {
149
150
  selectedRcsAccount: {
150
151
  sourceAccountIdentifier: 'rcs-account-123',
151
152
  configs: { accessToken: 'secret-token' },
153
+ hostName: 'rcs.host.example.com',
154
+ name: 'Brand RCS Account',
152
155
  },
153
156
  },
154
157
  },
@@ -167,6 +167,50 @@ describe("rcs Sagas", () => {
167
167
  .run();
168
168
  });
169
169
 
170
+ it("handles failure picking status.message when message is absent", () => {
171
+ const template = {};
172
+ const apiResult = { status: { code: 500, message: "Nested Msg" } };
173
+ return expectSaga(createRCSTemplate, { template })
174
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
175
+ .put({ type: CREATE_RCS_TEMPLATE_FAILURE, error: "Nested Msg", errorMsg: "Nested Msg" })
176
+ .run();
177
+ });
178
+
179
+ it("handles failure picking result.status.code when others are absent", () => {
180
+ const template = {};
181
+ const apiResult = { status: { code: 500 } };
182
+ return expectSaga(createRCSTemplate, { template })
183
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
184
+ .put({ type: CREATE_RCS_TEMPLATE_FAILURE, error: "Error: 500", errorMsg: "Error: 500" })
185
+ .run();
186
+ });
187
+
188
+ it("handles callback with errorMsg", () => {
189
+ const template = {};
190
+ const callback = jest.fn();
191
+ const apiResult = { status: { code: 500, message: "Nested Msg" } };
192
+ return expectSaga(createRCSTemplate, { template, callback })
193
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
194
+ .put({ type: CREATE_RCS_TEMPLATE_FAILURE, error: "Nested Msg", errorMsg: "Nested Msg" })
195
+ .run()
196
+ .then(() => {
197
+ expect(callback).toHaveBeenCalledWith(null, "Nested Msg");
198
+ });
199
+ });
200
+
201
+ it("handles callback with error object when errorMsg is undefined", () => {
202
+ const template = {};
203
+ const callback = jest.fn();
204
+ const apiError = new Error("Network Error");
205
+ return expectSaga(createRCSTemplate, { template, callback })
206
+ .provide([[matchers.call.fn(Api.createRcsTemplate), Promise.reject(apiError)]])
207
+ .put({ type: CREATE_RCS_TEMPLATE_FAILURE, error: apiError, errorMsg: undefined })
208
+ .run()
209
+ .then(() => {
210
+ expect(callback).toHaveBeenCalledWith(null, apiError);
211
+ });
212
+ });
213
+
170
214
  it("handles status code exactly 400 (boundary condition)", () => {
171
215
  const template = {};
172
216
  const callback = jest.fn();
@@ -226,6 +270,50 @@ describe("rcs Sagas", () => {
226
270
  .run();
227
271
  });
228
272
 
273
+ it("handles failure picking status.message when message is absent", () => {
274
+ const template = {};
275
+ const apiResult = { status: { code: 500, message: "Nested Msg" } };
276
+ return expectSaga(editTemplate, { template })
277
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
278
+ .put({ type: EDIT_RCS_TEMPLATE_FAILURE, error: "Nested Msg", errorMsg: "Nested Msg" })
279
+ .run();
280
+ });
281
+
282
+ it("handles failure picking result.status.code when others are absent", () => {
283
+ const template = {};
284
+ const apiResult = { status: { code: 500 } };
285
+ return expectSaga(editTemplate, { template })
286
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
287
+ .put({ type: EDIT_RCS_TEMPLATE_FAILURE, error: "Error: 500", errorMsg: "Error: 500" })
288
+ .run();
289
+ });
290
+
291
+ it("handles callback with errorMsg", () => {
292
+ const template = {};
293
+ const callback = jest.fn();
294
+ const apiResult = { status: { code: 500, message: "Nested Msg" } };
295
+ return expectSaga(editTemplate, { template, callback })
296
+ .provide([[matchers.call.fn(Api.createRcsTemplate), apiResult]])
297
+ .put({ type: EDIT_RCS_TEMPLATE_FAILURE, error: "Nested Msg", errorMsg: "Nested Msg" })
298
+ .run()
299
+ .then(() => {
300
+ expect(callback).toHaveBeenCalledWith(null, "Nested Msg");
301
+ });
302
+ });
303
+
304
+ it("handles callback with error object when errorMsg is undefined", () => {
305
+ const template = {};
306
+ const callback = jest.fn();
307
+ const apiError = new Error("Network Error");
308
+ return expectSaga(editTemplate, { template, callback })
309
+ .provide([[matchers.call.fn(Api.createRcsTemplate), Promise.reject(apiError)]])
310
+ .put({ type: EDIT_RCS_TEMPLATE_FAILURE, error: apiError, errorMsg: undefined })
311
+ .run()
312
+ .then(() => {
313
+ expect(callback).toHaveBeenCalledWith(null, apiError);
314
+ });
315
+ });
316
+
229
317
  it("handles status code exactly 400 (boundary condition)", () => {
230
318
  const template = {};
231
319
  const callback = jest.fn();
@@ -372,7 +372,6 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
372
372
  channel={this.props.channel}
373
373
  disabled={this.props.disabled}
374
374
  fetchingSchemaError={this?.state?.tagsError}
375
- popoverPlacement={this.props.popoverPlacement}
376
375
  />
377
376
  </div>
378
377
  );
@@ -403,7 +402,6 @@ TagList.propTypes = {
403
402
  disabled: PropTypes.bool,
404
403
  fetchingSchemaError: PropTypes.bool,
405
404
  eventContextTags: PropTypes.array,
406
- popoverPlacement: PropTypes.string,
407
405
  intl: PropTypes.shape({
408
406
  formatMessage: PropTypes.func.isRequired,
409
407
  locale: PropTypes.string,
@@ -68,7 +68,9 @@ export function setChannelAccount(channel, account) {
68
68
  [LINE]: types.SET_LINE_ACCOUNT,
69
69
  [WHATSAPP]: types.SET_WHATSAPP_ACCOUNT,
70
70
  [ZALO]: types.SET_ZALO_ACCOUNT,
71
+ [RCS]: types.SET_RCS_ACCOUNT,
71
72
  };
73
+
72
74
  if (channelTypeMapping[channel]) {
73
75
  return {
74
76
  type: channelTypeMapping[channel],
@@ -84,12 +86,6 @@ export function setViberAccount(viberAccount) {
84
86
  viberAccount,
85
87
  };
86
88
  }
87
- export function setRcsAccount(rcsAccount) {
88
- return {
89
- type: types.SET_RCS_ACCOUNT,
90
- rcsAccount,
91
- };
92
- }
93
89
 
94
90
  export function setFacebookAccount(faceBookAccount) {
95
91
  return {
@@ -80,6 +80,7 @@ export const GET_CDN_TRANSFORMATION_CONFIG_FAILURE = 'app/v2Containers/Templates
80
80
  export const ACCOUNT_MAPPING_ON_CHANNEL = {
81
81
  whatsapp: 'selectedWhatsappAccount',
82
82
  zalo: 'selectedZaloAccount',
83
+ rcs: 'selectedRcsAccount',
83
84
  };
84
85
 
85
86
  export const noFilteredWhatsappZaloTemplatesTitle= 'noFilteredWhatsappZaloTemplatesTitle';
@@ -292,7 +292,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
292
292
  return '';
293
293
  }
294
294
 
295
- const domainHostName = domainPropertiesData?.find(({ domainProperties }) => {
295
+ let domainHostName = domainPropertiesData?.find(({ domainProperties }) => {
296
296
  const {
297
297
  connectionProperties: { account_sid, wabaId, userid, sourceAccountIdentifier, oa_id } = {},
298
298
  } = domainProperties || {};
@@ -301,6 +301,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
301
301
  return [account_sid, wabaId, userid, oa_id, sourceAccountIdentifier].includes(selectedSourceAccountIdentifier);
302
302
  })?.domainProperties?.hostName || '';
303
303
 
304
+ // Fallback for RCS: If no match found (empty connectionProperties) and there's exactly one domain property, use that hostName
305
+ if (!domainHostName && domainPropertiesData.length === 1) {
306
+ domainHostName = domainPropertiesData[0]?.domainProperties?.hostName || '';
307
+ }
308
+
304
309
  this.setState({ hostName: domainHostName });
305
310
  return domainHostName;
306
311
  }
@@ -410,7 +415,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
410
415
  if (this.props.location.query.type === 'embedded') {
411
416
  this.props.actions.resetAccount();
412
417
  }
413
- if (['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill', RCS_LOWERCASE].includes((this.state.channel || '').toLowerCase())) {
418
+ if (['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill'].includes((this.state.channel || '').toLowerCase())) {
414
419
  const queryParams = {
415
420
  // name: this.state.searchText,
416
421
  // sortBy: this.state.sortBy,
@@ -483,7 +488,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
483
488
  }
484
489
 
485
490
  this.setState({ channel }, () => {
486
- if (['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill', RCS_LOWERCASE].includes(this.state.channel.toLowerCase()) || (this.state.channel.toLowerCase() === 'wechat' && !isEmpty(nextProps.Templates.selectedWeChatAccount))) {
491
+ if (['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill'].includes(this.state.channel.toLowerCase()) || (this.state.channel.toLowerCase() === 'wechat' && !isEmpty(nextProps.Templates.selectedWeChatAccount))) {
487
492
  if (this.state.channel.toLowerCase() === 'wechat' && !isEmpty(nextProps.Templates.selectedWeChatAccount)) {
488
493
  params.wecrmId = (nextProps.Templates.selectedWeChatAccount.configs || {}).wecrm_app_id;
489
494
  params.wecrmToken = (nextProps.Templates.selectedWeChatAccount.configs || {}).wecrm_token;
@@ -521,22 +526,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
521
526
  this.getAllTemplates({params});
522
527
  });
523
528
  }
524
- if (selectedChannel === RCS_LOWERCASE && isEmpty(nextProps.Templates.selectedRcsAccount)) {
525
- let selectedAccount = '';
526
- if (isEmpty(nextProps.Templates.weCrmAccounts)) {
527
- return;
528
- }
529
- selectedAccount = nextProps.Templates.weCrmAccounts[0];
530
- const { configs = {}, name = "" } = selectedAccount;
531
- const { wecrm_app_id = "", wecrm_token = "", sourceAccountIdentifier = "" } = configs;
532
- this.setState({selectedAccount: name}, () => {
533
- params.wecrmId = wecrm_app_id;
534
- params.wecrmToken = wecrm_token;
535
- params.originalId = sourceAccountIdentifier;
536
- this.props.actions.setRcsAccount(nextProps.Templates.weCrmAccounts[0]);
537
- this.getAllTemplates({params});
538
- });
539
- }
540
529
  if (selectedChannel === FACEBOOK_CHANNEL && isEmpty(nextProps.Templates.selectedFacebookAccount)) {
541
530
  if (isEmpty(nextProps.Templates.campaignSettings)) {
542
531
  return;
@@ -751,7 +740,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
751
740
  this.setState({ previewTemplate: nextProps.Templates.templateDetails });
752
741
  }
753
742
  const { weCrmAccounts: weCrmAccountsList = [], senderDetails = {} } = get(nextProps, 'Templates', {});
754
- const weCrmChannels = [WHATSAPP_LOWERCASE, ZALO_LOWERCASE];
743
+ const weCrmChannels = [WHATSAPP_LOWERCASE, ZALO_LOWERCASE, RCS_LOWERCASE];
755
744
 
756
745
  // Keeping the wechat flow separate as it has different logic for setting the account. Currently we don't support wechat but still keeping the flow.
757
746
  if (weCrmAccountsList?.length === 1 && this.state?.defaultAccount && selectedChannel === WECHAT_LOWERCASE && !isEmpty(senderDetails?.hostName)) {
@@ -773,6 +762,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
773
762
  this.getAllTemplates({params: paramsDefault}, true);
774
763
  }
775
764
 
765
+ // Auto-select single account for multi-account channels (WhatsApp, Zalo, RCS)
776
766
  if (weCrmAccountsList?.length && this.state?.defaultAccount && (weCrmChannels.includes(selectedChannel))) {
777
767
  const isSingleAccount = weCrmAccountsList?.length === 1;
778
768
  const selectedAccount = this.props.Templates[ACCOUNT_MAPPING_ON_CHANNEL[selectedChannel]] || {};
@@ -799,6 +789,11 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
799
789
  paramsDefault.name = this.state.searchText;
800
790
  paramsDefault.sortBy = this.state.sortBy;
801
791
  }
792
+ if (selectedChannel === RCS_LOWERCASE) {
793
+ paramsDefault.accountId = sourceAccountIdentifier;
794
+ paramsDefault.accessToken = configs?.accessToken;
795
+ paramsDefault.host = hostName || this.props.Templates?.selectedRcsAccount?.hostName;
796
+ }
802
797
  this.setState({ defaultAccount: false });
803
798
  /**
804
799
  * Incase of multiple accounts, getAllTemplates is called on selecting the account. It's handled in onAccountSelect function.
@@ -834,7 +829,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
834
829
  params.sortBy = this.state.sortBy;
835
830
  }
836
831
  const selectedChannel = this.state.channel.toLowerCase();
837
- const isZaloOrWhatsapp = [ZALO_LOWERCASE, WHATSAPP_LOWERCASE].includes(selectedChannel);
832
+ const isZaloOrWhatsappOrRcs = [ZALO_LOWERCASE, WHATSAPP_LOWERCASE, RCS_LOWERCASE].includes(selectedChannel);
838
833
  if (selectedChannel === 'wechat') {
839
834
  params.wecrmId = this.props.Templates.weCrmAccounts[findIndex(this.props.Templates.weCrmAccounts, { name: value})].configs.wecrm_app_id;
840
835
  params.wecrmToken = this.props.Templates.weCrmAccounts[findIndex(this.props.Templates.weCrmAccounts, { name: value})].configs.wecrm_token;
@@ -858,7 +853,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
858
853
  const setAcc = this.props?.Templates?.weCrmAccounts?.find((item) => item?.name === this.state.selectedAccount);
859
854
  const { domainProperties = [] } = this.props?.Templates?.senderDetails || {};
860
855
  let hostName = '';
861
- if (isZaloOrWhatsapp) {
856
+ if (isZaloOrWhatsappOrRcs) {
862
857
  hostName = this.getHostName(setAcc?.sourceAccountIdentifier, domainProperties);
863
858
  setAcc.hostName = hostName;
864
859
  }
@@ -881,11 +876,19 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
881
876
  params.accountId = sourceAccountIdentifier;
882
877
  params.host = hostName;
883
878
  }
879
+ if (selectedChannel === RCS_LOWERCASE && hostName) {
880
+ const { configs: { accessToken = "" } = {} } = setAcc || {};
881
+ params.accountId = sourceAccountIdentifier;
882
+ params.host = hostName;
883
+ if (accessToken) {
884
+ params.accessToken = accessToken;
885
+ }
886
+ }
884
887
  }
885
888
 
886
889
  this.setState({ activeMode: TEMPLATES_MODE }, () => {
887
- // Restrict getAllTemplates call only if selectedChannel is ZALO or WHATSAPP and hostName is empty
888
- if (!(isZaloOrWhatsapp && !params?.host)) {
890
+ // Restrict getAllTemplates call only if selectedChannel is ZALO, WHATSAPP or RCS and hostName is empty
891
+ if (!(isZaloOrWhatsappOrRcs && !params?.host)) {
889
892
  this.getAllTemplates({ params, resetPage: true });
890
893
  }
891
894
  });
@@ -937,11 +940,6 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
937
940
  const zaloSelectedTemplateData = this.selectTemplate(parseInt(creativesParams._id, 10)) || {};
938
941
  const { name = '' } = zaloSelectedTemplateData;
939
942
  creativesParams.name = name
940
- } else if (this.state.channel?.toLowerCase() === EMAIL_LOWERCASE) {
941
- const emailSelectedTemplateData = this.selectTemplate(creativesParams._id) || {};
942
- const activeTab = get(emailSelectedTemplateData, 'versions.base.activeTab', 'en');
943
- const isDragDrop = get(emailSelectedTemplateData, `versions.base.${activeTab}.is_drag_drop`, false);
944
- creativesParams.is_drag_drop = isDragDrop;
945
943
  }
946
944
  }
947
945
  creativesParams.type = this.state.channel.toUpperCase();
@@ -1009,6 +1007,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1009
1007
  if (this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE) {
1010
1008
  this.setWhatsappQueryParams(queryParams, params);
1011
1009
  }
1010
+ if (this.state?.channel?.toLowerCase() === RCS_LOWERCASE && !isEmpty(this.props.Templates?.selectedRcsAccount)) {
1011
+ const { sourceAccountIdentifier = '', configs: { accessToken = '' } = {}, hostName = '' } = this.props.Templates.selectedRcsAccount;
1012
+ if (sourceAccountIdentifier) {
1013
+ queryParams.accountId = sourceAccountIdentifier;
1014
+ }
1015
+ if (accessToken) {
1016
+ queryParams.accessToken = accessToken;
1017
+ }
1018
+ if (hostName) {
1019
+ queryParams.host = hostName;
1020
+ }
1021
+ }
1012
1022
  this.props.actions.getAllTemplates(channel, queryParams,`${copyOf}`);
1013
1023
  } else if ((resetPage || (page === 1 && this.state.templatesCount === 0) || page <= (this.state.templatesCount / this.state.perPageLimit))) {
1014
1024
  if (getNextPage) {
@@ -1061,6 +1071,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1061
1071
  if (this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE) {
1062
1072
  this.setWhatsappQueryParams(queryParams, params);
1063
1073
  }
1074
+ if (this.state?.channel?.toLowerCase() === RCS_LOWERCASE && !isEmpty(this.props.Templates?.selectedRcsAccount)) {
1075
+ const { sourceAccountIdentifier = '', configs: { accessToken = '' } = {}, hostName = '' } = this.props.Templates.selectedRcsAccount;
1076
+ if (sourceAccountIdentifier) {
1077
+ queryParams.accountId = sourceAccountIdentifier;
1078
+ }
1079
+ if (accessToken) {
1080
+ queryParams.accessToken = accessToken;
1081
+ }
1082
+ if (hostName) {
1083
+ queryParams.host = hostName;
1084
+ }
1085
+ }
1064
1086
  this.setState({ page, templatesCount }, () => {
1065
1087
  queryParams.page = page;
1066
1088
  queryParams.perPage = this.state.perPageLimit;
@@ -1760,9 +1782,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1760
1782
  const noLoaderAndSearchText = isEmpty(this.state.searchText) && !isLoading;
1761
1783
 
1762
1784
  return (<div>
1763
- {[WECHAT, MOBILE_PUSH, INAPP, WHATSAPP, ZALO].includes(currentChannel) && this.showAccountName()}
1785
+ {[WECHAT, MOBILE_PUSH, INAPP, WHATSAPP, ZALO,RCS].includes(currentChannel) && this.showAccountName()}
1764
1786
  {filterContent}
1765
- {[WHATSAPP, ZALO].includes(currentChannel) && this.selectedFilters()}
1787
+ {[WHATSAPP, ZALO,RCS].includes(currentChannel) && this.selectedFilters()}
1766
1788
  {<div>
1767
1789
  {!isEmpty(filteredTemplates) || !isEmpty(this.state.searchText) || !isEmpty(this.props.Templates.templateError) ? (
1768
1790
  <div className={!isEmpty(this.state.searchText) && isEmpty(cardDataList) ? '' : this.isFullMode() ? "v2-pagination-container" : "v2-pagination-container-half"}>
@@ -2975,9 +2997,9 @@ return (<div>
2975
2997
  const isMobilePushChannel = channel === MOBILE_PUSH;
2976
2998
  const isInAppChannel = channel === INAPP;
2977
2999
  const isFacebookChannel = channel === FACEBOOK;
2978
- if ([WECHAT, MOBILE_PUSH, INAPP, LINE, ZALO, WHATSAPP].includes(channel) && !isEmpty(weCrmAccounts) && !isFacebookChannel) {
3000
+ if ([WECHAT, MOBILE_PUSH, INAPP, LINE, ZALO, WHATSAPP, RCS].includes(channel) && !isEmpty(weCrmAccounts) && !isFacebookChannel) {
2979
3001
  forEach(weCrmAccounts, (account) => {
2980
- if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || [MOBILE_PUSH, INAPP, LINE, ZALO, WHATSAPP].includes(channel)) {
3002
+ if ((isWechatChannel && account.configs && account.configs.is_wecrm_enabled) || [MOBILE_PUSH, INAPP, LINE, ZALO, WHATSAPP, RCS].includes(channel)) {
2981
3003
  if (query.type === 'embedded' && (!query.module || (query.module && query.module !== 'library'))) {
2982
3004
  if (query.source_account_id && account.sourceAccountIdentifier === query.source_account_id) {
2983
3005
  accountOptions.push(
@@ -3056,12 +3078,17 @@ return (<div>
3056
3078
  noAccountHeader = messages.noAccountsPresentZalo;
3057
3079
  break;
3058
3080
  }
3081
+ case RCS: {
3082
+ accountHeader = formatMessage(messages.rcsAccount);
3083
+ noAccountHeader = messages.noAccountsPresentRcs;
3084
+ break;
3085
+ }
3059
3086
  default:
3060
3087
  break;
3061
3088
  }
3062
3089
  let showNoAccountHeader = isEmpty(weCrmAccounts) && !fetchingWeCrmAccounts;
3063
- // Zalo and Whatsapp has dependencies on domainProperties to get the hostName. Show loader until the domainProperties are fetched.
3064
- const isDomainPropertiesLoading = [WHATSAPP, ZALO].includes(channel) && senderDetails?.status === "REQUEST";
3090
+ // Zalo, Whatsapp and RCS have dependencies on domainProperties to get the hostName. Show loader until the domainProperties are fetched.
3091
+ const isDomainPropertiesLoading = [WHATSAPP, ZALO, RCS].includes(channel) && senderDetails?.status === "REQUEST";
3065
3092
  if (channel === FACEBOOK && !isEmpty(campaignSettings) ) {
3066
3093
  const fbSetting = get(campaignSettings, 'accountSettings.socialAccountSettings.facebookAccountSettings', []);
3067
3094
  const { orgUnitFacebookPageSettingsMap } = fbSetting[0] || {};
@@ -3131,6 +3158,7 @@ return (<div>
3131
3158
  [WHATSAPP]: 'whatsappAccount',
3132
3159
  [INAPP]: 'inappAccount',
3133
3160
  [ZALO]: 'zaloAccount',
3161
+ [RCS]: 'rcsAccount',
3134
3162
  };
3135
3163
  return formatMessage(messages[channelObj[channel?.toUpperCase()]]);
3136
3164
  }
@@ -3252,10 +3280,9 @@ return (<div>
3252
3280
  }
3253
3281
  }
3254
3282
 
3255
- const showForChannels = (![VIBER_CHANNEL, FACEBOOK_CHANNEL,RCS_LOWERCASE].includes(channel.toLowerCase()) ||
3283
+ const showForChannels = (![VIBER_CHANNEL, FACEBOOK_CHANNEL].includes(channel.toLowerCase()) ||
3256
3284
  (channel.toLowerCase() === VIBER_CHANNEL && !this.props.isFullMode && isEmpty(get(this.props, 'Templates.selectedViberAccount', {}))) ||
3257
- (channel.toLowerCase() === FACEBOOK_CHANNEL && showFbAds)||
3258
- (channel.toLowerCase() === RCS_LOWERCASE && isEmpty(get(this.props, 'Templates.selectedRcsAccount', {})))
3285
+ (channel.toLowerCase() === FACEBOOK_CHANNEL && showFbAds)
3259
3286
  );
3260
3287
 
3261
3288
 
@@ -3322,7 +3349,7 @@ return (<div>
3322
3349
  const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
3323
3350
  const showWhatsappCountWarning = templatesCount >= WARNING_WHATSAPP_TEMPLATES;
3324
3351
  const whatsappCountExceedText = <FormattedMessage {...messages.whatsappMaxTemplates} values={{ maxCount: MAX_WHATSAPP_TEMPLATES }}/>;
3325
- if (([WHATSAPP_LOWERCASE, ZALO_LOWERCASE].includes(this.state?.channel?.toLocaleLowerCase()) && isEmpty(this.state?.hostName))) {
3352
+ if (([WHATSAPP_LOWERCASE, ZALO_LOWERCASE, RCS_LOWERCASE].includes(this.state?.channel?.toLocaleLowerCase()) && isEmpty(this.state?.hostName))) {
3326
3353
  isfilterContentVisisble = false;
3327
3354
  }
3328
3355
  const filterContent = (( isfilterContentVisisble || [WECHAT, MOBILE_PUSH, INAPP].includes(this.state.channel.toUpperCase())) && <div className="action-container">
@@ -462,6 +462,10 @@ export default defineMessages({
462
462
  id: `${scope}.zaloAccount`,
463
463
  defaultMessage: 'Zalo account',
464
464
  },
465
+ "rcsAccount": {
466
+ id: `${scope}.rcsAccount`,
467
+ defaultMessage: 'RCS account',
468
+ },
465
469
  "all": {
466
470
  id: `${scope}.all`,
467
471
  defaultMessage: 'All',
@@ -494,6 +498,10 @@ export default defineMessages({
494
498
  id: `${scope}.noAccountsPresentZalo`,
495
499
  defaultMessage: "Zalo accounts are not setup for your brand",
496
500
  },
501
+ "noAccountsPresentRcs": {
502
+ id: `${scope}.noAccountsPresentRcs`,
503
+ defaultMessage: "RCS accounts are not setup for your brand",
504
+ },
497
505
  "promotional": {
498
506
  id: `${scope}.promotional`,
499
507
  defaultMessage: 'Promotional',
@@ -118,7 +118,7 @@ function templatesReducer(state = initialState, action) {
118
118
  .set('templates', []);
119
119
  case types.SET_RCS_ACCOUNT:
120
120
  return state
121
- .set('selectedRcsAccount', fromJS(action.rcsAccount))
121
+ .set('selectedRcsAccount', fromJS(action.account))
122
122
  .set('templates', []);
123
123
  case types.SET_FACEBOOK_ACCOUNT:
124
124
  return state
@@ -212,9 +212,9 @@ function templatesReducer(state = initialState, action) {
212
212
  return state.set('senderDetails', { status: 'REQUEST' });
213
213
  case types.GET_SENDER_DETAILS_SUCCESS: {
214
214
  const { channel, domainProperties = {} } = action?.payload || {};
215
- const isMultiAccountChannel = ['WHATSAPP', 'ZALO'].includes(channel);
215
+ const isMultiAccountChannel = ['WHATSAPP', 'ZALO', 'RCS'].includes(channel);
216
216
  const senderDetailsKey = isMultiAccountChannel ? 'domainProperties' : 'hostName';
217
- // For Whatsapp and Zalo we need to store domainProperties instead of only hostName
217
+ // For Whatsapp, Zalo and RCS we need to store domainProperties instead of only hostName
218
218
  return state.set('senderDetails', {
219
219
  status: 'SUCCESS',
220
220
  [senderDetailsKey]: isMultiAccountChannel ? domainProperties : action?.payload,
@@ -180,7 +180,7 @@ export function* getSenderDetails({
180
180
  if (!apiResponse?.errors?.length) {
181
181
  yield put({
182
182
  type: types.GET_SENDER_DETAILS_SUCCESS,
183
- payload: ['WHATSAPP', 'ZALO'].includes(channel) ?
183
+ payload: ['WHATSAPP', 'ZALO', 'RCS'].includes(channel) ?
184
184
  { channel,
185
185
  domainProperties: get(apiResponse, `entity.${channel}`, '')
186
186
  } :