@capillarytech/creatives-library 8.0.236 → 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.
package/config/app.js CHANGED
@@ -20,7 +20,7 @@ const config = {
20
20
  accountConfig: (strs, accountId) => `${window.location.origin}/org/config/AccountAdd?q=a&channelId=2&accountId=${accountId}&edit=1`,
21
21
  },
22
22
  development: {
23
- api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/creatives',
23
+ api_endpoint: 'http://localhost:2022/arya/api/v1/creatives',
24
24
  campaigns_api_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/campaigns',
25
25
  campaigns_api_org_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/iris/v2/org/campaign',
26
26
  auth_endpoint: 'https://crm-nightly-new.cc.capillarytech.com/arya/api/v1/auth',
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@capillarytech/creatives-library",
3
3
  "author": "meharaj",
4
- "version": "8.0.236",
4
+ "version": "8.0.237",
5
5
  "description": "Capillary creatives ui",
6
6
  "main": "./index.js",
7
7
  "module": "./index.es.js",
@@ -201,6 +201,8 @@ export const Rcs = (props) => {
201
201
 
202
202
  const [accountId, setAccountId] = useState('');
203
203
  const [accessToken, setAccessToken] = useState('');
204
+ const [hostName, setHostName] = useState('');
205
+ const [accountName, setAccountName] = useState('');
204
206
  useEffect(() => {
205
207
  const accountObj = accountData.selectedRcsAccount || {};
206
208
  if (!isEmpty(accountObj)) {
@@ -211,6 +213,8 @@ export const Rcs = (props) => {
211
213
 
212
214
  setAccountId(sourceAccountIdentifier);
213
215
  setAccessToken(configs.accessToken || '');
216
+ setHostName(accountObj.hostName || '');
217
+ setAccountName(accountObj.name || '');
214
218
  }
215
219
  }, [accountData.selectedRcsAccount]);
216
220
 
@@ -579,7 +583,6 @@ export const Rcs = (props) => {
579
583
 
580
584
  useEffect(() => {
581
585
  const details = isFullMode ? rcsData?.templateDetails : templateData;
582
-
583
586
  if (details && Object.keys(details).length > 0) {
584
587
  if (!isFullMode) {
585
588
  const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
@@ -2112,8 +2115,7 @@ const splitTemplateVarString = (str) => {
2112
2115
  }
2113
2116
  ],
2114
2117
  contentType: isFullMode ? templateType : RICHCARD,
2115
- ...(isFullMode && {isHostIcs: true}),
2116
- ...(isFullMode && {user: accountId, pass: accessToken}),
2118
+ ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2117
2119
  },
2118
2120
  smsFallBackContent: {
2119
2121
  message: fallbackMessage,
@@ -57,7 +57,7 @@ export function* createRCSTemplate({ template, callback }) {
57
57
  template,
58
58
  });
59
59
  if (result.status.code >= 400) {
60
- errorMsg = result.message;
60
+ errorMsg = result.message || result.status.message || `Error: ${result.status.code}`;
61
61
  throw errorMsg;
62
62
  }
63
63
  yield put({
@@ -72,7 +72,7 @@ export function* createRCSTemplate({ template, callback }) {
72
72
  } catch (error) {
73
73
  yield put({ type: CREATE_RCS_TEMPLATE_FAILURE, error, errorMsg });
74
74
  if (callback) {
75
- callback(null, errorMsg);
75
+ callback(null, errorMsg || error);
76
76
  }
77
77
  }
78
78
  }
@@ -91,7 +91,7 @@ export function* editTemplate({ template, callback }) {
91
91
  template,
92
92
  });
93
93
  if (result.status.code >= 400) {
94
- errorMsg = result.message;
94
+ errorMsg = result.message || result.status.message || `Error: ${result.status.code}`;
95
95
  throw errorMsg;
96
96
  }
97
97
  yield put({
@@ -106,7 +106,7 @@ export function* editTemplate({ template, callback }) {
106
106
  } catch (error) {
107
107
  yield put({ type: EDIT_RCS_TEMPLATE_FAILURE, error, errorMsg });
108
108
  if (callback) {
109
- callback(null, errorMsg);
109
+ callback(null, errorMsg || error);
110
110
  }
111
111
  }
112
112
  }
@@ -2750,7 +2750,6 @@ new message content.",
2750
2750
  },
2751
2751
  "cardType": "STANDALONE",
2752
2752
  "contentType": "text_message",
2753
- "isHostIcs": true,
2754
2753
  "smsFallBackContent": Object {
2755
2754
  "message": "Fallback SMS for text",
2756
2755
  },
@@ -14575,7 +14574,6 @@ new message content.",
14575
14574
  },
14576
14575
  "cardType": "STANDALONE",
14577
14576
  "contentType": "text_message",
14578
- "isHostIcs": true,
14579
14577
  "smsFallBackContent": Object {
14580
14578
  "message": "Fallback SMS for text",
14581
14579
  },
@@ -26554,7 +26552,6 @@ new message content.",
26554
26552
  },
26555
26553
  "cardType": "STANDALONE",
26556
26554
  "contentType": "text_message",
26557
- "isHostIcs": true,
26558
26555
  "smsFallBackContent": Object {
26559
26556
  "message": "Fallback SMS for text",
26560
26557
  },
@@ -38697,7 +38694,6 @@ new message content.",
38697
38694
  },
38698
38695
  "cardType": "STANDALONE",
38699
38696
  "contentType": "text_message",
38700
- "isHostIcs": true,
38701
38697
  "smsFallBackContent": Object {
38702
38698
  "message": "Fallback SMS for text",
38703
38699
  },
@@ -50518,7 +50514,6 @@ new message content.",
50518
50514
  },
50519
50515
  "cardType": "STANDALONE",
50520
50516
  "contentType": "text_message",
50521
- "isHostIcs": true,
50522
50517
  "smsFallBackContent": Object {
50523
50518
  "message": "Fallback SMS for text",
50524
50519
  },
@@ -63089,7 +63084,6 @@ new message content.",
63089
63084
  },
63090
63085
  "cardType": "STANDALONE",
63091
63086
  "contentType": "text_message",
63092
- "isHostIcs": true,
63093
63087
  "smsFallBackContent": Object {
63094
63088
  "message": "Fallback SMS for text",
63095
63089
  },
@@ -64611,7 +64605,6 @@ new message content.",
64611
64605
  },
64612
64606
  "cardType": "STANDALONE",
64613
64607
  "contentType": "text_message",
64614
- "isHostIcs": true,
64615
64608
  "smsFallBackContent": Object {
64616
64609
  "message": "Fallback SMS for text",
64617
64610
  },
@@ -64669,7 +64662,6 @@ new message content.",
64669
64662
  },
64670
64663
  "cardType": "STANDALONE",
64671
64664
  "contentType": "text_message",
64672
- "isHostIcs": true,
64673
64665
  "smsFallBackContent": Object {
64674
64666
  "message": "Fallback SMS for text",
64675
64667
  },
@@ -76581,7 +76573,6 @@ new message content.",
76581
76573
  },
76582
76574
  "cardType": "STANDALONE",
76583
76575
  "contentType": "text_message",
76584
- "isHostIcs": true,
76585
76576
  "smsFallBackContent": Object {
76586
76577
  "message": "Fallback SMS for text",
76587
76578
  },
@@ -78103,7 +78094,6 @@ new message content.",
78103
78094
  },
78104
78095
  "cardType": "STANDALONE",
78105
78096
  "contentType": "text_message",
78106
- "isHostIcs": true,
78107
78097
  "smsFallBackContent": Object {
78108
78098
  "message": "Fallback SMS for text",
78109
78099
  },
@@ -78161,7 +78151,6 @@ new message content.",
78161
78151
  },
78162
78152
  "cardType": "STANDALONE",
78163
78153
  "contentType": "text_message",
78164
- "isHostIcs": true,
78165
78154
  "smsFallBackContent": Object {
78166
78155
  "message": "Fallback SMS for text",
78167
78156
  },
@@ -90563,7 +90552,6 @@ new message content.",
90563
90552
  },
90564
90553
  "cardType": "STANDALONE",
90565
90554
  "contentType": "text_message",
90566
- "isHostIcs": true,
90567
90555
  "smsFallBackContent": Object {
90568
90556
  "message": "Fallback SMS for text",
90569
90557
  },
@@ -102388,7 +102376,6 @@ new message content.",
102388
102376
  },
102389
102377
  "cardType": "STANDALONE",
102390
102378
  "contentType": "text_message",
102391
- "isHostIcs": true,
102392
102379
  "smsFallBackContent": Object {
102393
102380
  "message": "Fallback SMS for text",
102394
102381
  },
@@ -114213,7 +114200,6 @@ new message content.",
114213
114200
  },
114214
114201
  "cardType": "STANDALONE",
114215
114202
  "contentType": "text_message",
114216
- "isHostIcs": true,
114217
114203
  "smsFallBackContent": Object {
114218
114204
  "message": "Fallback SMS for text",
114219
114205
  },
@@ -126038,7 +126024,6 @@ new message content.",
126038
126024
  },
126039
126025
  "cardType": "STANDALONE",
126040
126026
  "contentType": "text_message",
126041
- "isHostIcs": true,
126042
126027
  "smsFallBackContent": Object {
126043
126028
  "message": "Fallback SMS for text",
126044
126029
  },
@@ -138526,7 +138511,6 @@ new message content.",
138526
138511
  },
138527
138512
  "cardType": "STANDALONE",
138528
138513
  "contentType": "rich_card",
138529
- "isHostIcs": true,
138530
138514
  "smsFallBackContent": Object {
138531
138515
  "message": "Fallback SMS for image",
138532
138516
  },
@@ -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();
@@ -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
  });
@@ -1004,6 +1007,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1004
1007
  if (this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE) {
1005
1008
  this.setWhatsappQueryParams(queryParams, params);
1006
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
+ }
1007
1022
  this.props.actions.getAllTemplates(channel, queryParams,`${copyOf}`);
1008
1023
  } else if ((resetPage || (page === 1 && this.state.templatesCount === 0) || page <= (this.state.templatesCount / this.state.perPageLimit))) {
1009
1024
  if (getNextPage) {
@@ -1056,6 +1071,18 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1056
1071
  if (this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE) {
1057
1072
  this.setWhatsappQueryParams(queryParams, params);
1058
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
+ }
1059
1086
  this.setState({ page, templatesCount }, () => {
1060
1087
  queryParams.page = page;
1061
1088
  queryParams.perPage = this.state.perPageLimit;
@@ -1755,9 +1782,9 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1755
1782
  const noLoaderAndSearchText = isEmpty(this.state.searchText) && !isLoading;
1756
1783
 
1757
1784
  return (<div>
1758
- {[WECHAT, MOBILE_PUSH, INAPP, WHATSAPP, ZALO].includes(currentChannel) && this.showAccountName()}
1785
+ {[WECHAT, MOBILE_PUSH, INAPP, WHATSAPP, ZALO,RCS].includes(currentChannel) && this.showAccountName()}
1759
1786
  {filterContent}
1760
- {[WHATSAPP, ZALO].includes(currentChannel) && this.selectedFilters()}
1787
+ {[WHATSAPP, ZALO,RCS].includes(currentChannel) && this.selectedFilters()}
1761
1788
  {<div>
1762
1789
  {!isEmpty(filteredTemplates) || !isEmpty(this.state.searchText) || !isEmpty(this.props.Templates.templateError) ? (
1763
1790
  <div className={!isEmpty(this.state.searchText) && isEmpty(cardDataList) ? '' : this.isFullMode() ? "v2-pagination-container" : "v2-pagination-container-half"}>
@@ -2970,9 +2997,9 @@ return (<div>
2970
2997
  const isMobilePushChannel = channel === MOBILE_PUSH;
2971
2998
  const isInAppChannel = channel === INAPP;
2972
2999
  const isFacebookChannel = channel === FACEBOOK;
2973
- 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) {
2974
3001
  forEach(weCrmAccounts, (account) => {
2975
- 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)) {
2976
3003
  if (query.type === 'embedded' && (!query.module || (query.module && query.module !== 'library'))) {
2977
3004
  if (query.source_account_id && account.sourceAccountIdentifier === query.source_account_id) {
2978
3005
  accountOptions.push(
@@ -3051,12 +3078,17 @@ return (<div>
3051
3078
  noAccountHeader = messages.noAccountsPresentZalo;
3052
3079
  break;
3053
3080
  }
3081
+ case RCS: {
3082
+ accountHeader = formatMessage(messages.rcsAccount);
3083
+ noAccountHeader = messages.noAccountsPresentRcs;
3084
+ break;
3085
+ }
3054
3086
  default:
3055
3087
  break;
3056
3088
  }
3057
3089
  let showNoAccountHeader = isEmpty(weCrmAccounts) && !fetchingWeCrmAccounts;
3058
- // Zalo and Whatsapp has dependencies on domainProperties to get the hostName. Show loader until the domainProperties are fetched.
3059
- 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";
3060
3092
  if (channel === FACEBOOK && !isEmpty(campaignSettings) ) {
3061
3093
  const fbSetting = get(campaignSettings, 'accountSettings.socialAccountSettings.facebookAccountSettings', []);
3062
3094
  const { orgUnitFacebookPageSettingsMap } = fbSetting[0] || {};
@@ -3126,6 +3158,7 @@ return (<div>
3126
3158
  [WHATSAPP]: 'whatsappAccount',
3127
3159
  [INAPP]: 'inappAccount',
3128
3160
  [ZALO]: 'zaloAccount',
3161
+ [RCS]: 'rcsAccount',
3129
3162
  };
3130
3163
  return formatMessage(messages[channelObj[channel?.toUpperCase()]]);
3131
3164
  }
@@ -3247,10 +3280,9 @@ return (<div>
3247
3280
  }
3248
3281
  }
3249
3282
 
3250
- const showForChannels = (![VIBER_CHANNEL, FACEBOOK_CHANNEL,RCS_LOWERCASE].includes(channel.toLowerCase()) ||
3283
+ const showForChannels = (![VIBER_CHANNEL, FACEBOOK_CHANNEL].includes(channel.toLowerCase()) ||
3251
3284
  (channel.toLowerCase() === VIBER_CHANNEL && !this.props.isFullMode && isEmpty(get(this.props, 'Templates.selectedViberAccount', {}))) ||
3252
- (channel.toLowerCase() === FACEBOOK_CHANNEL && showFbAds)||
3253
- (channel.toLowerCase() === RCS_LOWERCASE && isEmpty(get(this.props, 'Templates.selectedRcsAccount', {})))
3285
+ (channel.toLowerCase() === FACEBOOK_CHANNEL && showFbAds)
3254
3286
  );
3255
3287
 
3256
3288
 
@@ -3317,7 +3349,7 @@ return (<div>
3317
3349
  const isWhatsappCountExeeded = templatesCount >= MAX_WHATSAPP_TEMPLATES;
3318
3350
  const showWhatsappCountWarning = templatesCount >= WARNING_WHATSAPP_TEMPLATES;
3319
3351
  const whatsappCountExceedText = <FormattedMessage {...messages.whatsappMaxTemplates} values={{ maxCount: MAX_WHATSAPP_TEMPLATES }}/>;
3320
- 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))) {
3321
3353
  isfilterContentVisisble = false;
3322
3354
  }
3323
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
  } :
@@ -11,8 +11,8 @@ exports[`Test Templates container Should render correct component for RCS channe
11
11
  }
12
12
  >
13
13
  <FormattedMessage
14
- defaultMessage="Push notifications are not setup for your brand"
15
- id="creatives.containersV2.Templates.noAccountsPresent"
14
+ defaultMessage="RCS accounts are not setup for your brand"
15
+ id="creatives.containersV2.Templates.noAccountsPresentRcs"
16
16
  values={Object {}}
17
17
  />
18
18
  </CapSkeleton>
@@ -145,8 +145,8 @@ exports[`Test Templates container Should render illustration when no RCS templat
145
145
  }
146
146
  >
147
147
  <FormattedMessage
148
- defaultMessage="Push notifications are not setup for your brand"
149
- id="creatives.containersV2.Templates.noAccountsPresent"
148
+ defaultMessage="RCS accounts are not setup for your brand"
149
+ id="creatives.containersV2.Templates.noAccountsPresentRcs"
150
150
  values={Object {}}
151
151
  />
152
152
  </CapSkeleton>
@@ -1004,8 +1004,8 @@ exports[`Test Templates container Should render templates when RCS templates are
1004
1004
  }
1005
1005
  >
1006
1006
  <FormattedMessage
1007
- defaultMessage="Push notifications are not setup for your brand"
1008
- id="creatives.containersV2.Templates.noAccountsPresent"
1007
+ defaultMessage="RCS accounts are not setup for your brand"
1008
+ id="creatives.containersV2.Templates.noAccountsPresentRcs"
1009
1009
  values={Object {}}
1010
1010
  />
1011
1011
  </CapSkeleton>
@@ -1022,8 +1022,8 @@ exports[`Test Templates container Should render templates when RCS templates are
1022
1022
  }
1023
1023
  >
1024
1024
  <FormattedMessage
1025
- defaultMessage="Push notifications are not setup for your brand"
1026
- id="creatives.containersV2.Templates.noAccountsPresent"
1025
+ defaultMessage="RCS accounts are not setup for your brand"
1026
+ id="creatives.containersV2.Templates.noAccountsPresentRcs"
1027
1027
  values={Object {}}
1028
1028
  />
1029
1029
  </CapSkeleton>
@@ -1,5 +1,6 @@
1
- import { getAllTemplates } from "../actions";
1
+ import { getAllTemplates, setChannelAccount } from "../actions";
2
2
  import * as types from "../constants";
3
+ import { RCS } from "../../CreativesContainer/constants";
3
4
 
4
5
  describe("Test zalo list actions", () => {
5
6
  test.concurrent("has a type of GET_ALL_TEMPLATES_REQUEST action", () => {
@@ -17,3 +18,37 @@ describe("Test zalo list actions", () => {
17
18
  expect(getAllTemplates(channel, queryParams, "test")).toEqual(expected);
18
19
  });
19
20
  });
21
+
22
+ describe("Test RCS list actions", () => {
23
+ test.concurrent("has a type of GET_ALL_TEMPLATES_REQUEST action for RCS", () => {
24
+ const channel = "Rcs";
25
+ const queryParams = {
26
+ page: 1,
27
+ perPage: 25,
28
+ accountId: "capillary_rcs",
29
+ accessToken: "YugrOlhdSPpM",
30
+ host: "rcsicsbulk",
31
+ };
32
+ const expected = {
33
+ type: types.GET_ALL_TEMPLATES_REQUEST,
34
+ channel,
35
+ intlCopyOf: "test",
36
+ queryParams,
37
+ };
38
+ expect(getAllTemplates(channel, queryParams, "test")).toEqual(expected);
39
+ });
40
+
41
+ test.concurrent("has a type of SET_RCS_ACCOUNT action", () => {
42
+ const account = {
43
+ sourceAccountIdentifier: "capillary_rcs",
44
+ name: "RCS Account",
45
+ configs: { accessToken: "YugrOlhdSPpM" },
46
+ hostName: "rcsicsbulk",
47
+ };
48
+ const expected = {
49
+ type: types.SET_RCS_ACCOUNT,
50
+ account,
51
+ };
52
+ expect(setChannelAccount(RCS, account)).toEqual(expected);
53
+ });
54
+ });
@@ -1,6 +1,7 @@
1
1
  import reducer, { initialState } from "../reducer";
2
2
  import * as types from "../constants";
3
3
  import * as mockData from "./mockData";
4
+ import { RCS } from "../../CreativesContainer/constants";
4
5
 
5
6
  describe("test reducer", () => {
6
7
  afterEach(() => {
@@ -277,10 +278,16 @@ describe("test reducer - SET account actions clear templates", () => {
277
278
 
278
279
  it.concurrent("should clear templates when SET_RCS_ACCOUNT is dispatched", () => {
279
280
  const stateWithTemplates = initialState.set('templates', [{ id: 1, name: "Template 1" }]);
280
- const mockAccount = { id: 1, name: "RCS Account", sourceAccountIdentifier: "rcs123" };
281
+ const mockAccount = {
282
+ id: 1,
283
+ name: "RCS Account",
284
+ sourceAccountIdentifier: "capillary_rcs",
285
+ configs: { accessToken: "YugrOlhdSPpM" },
286
+ hostName: "rcsicsbulk"
287
+ };
281
288
  const action = {
282
289
  type: types.SET_RCS_ACCOUNT,
283
- rcsAccount: mockAccount,
290
+ account: mockAccount,
284
291
  };
285
292
  const result = reducer(stateWithTemplates, action).toJS();
286
293
  expect(result.selectedRcsAccount).toEqual(mockAccount);
@@ -17,6 +17,16 @@ import {
17
17
  deleteRcsTemplate,
18
18
  watchDeleteRcsTemplate,
19
19
  watchForGetTemplateInfoById,
20
+ deleteTemplate,
21
+ watchDeleteTemplate,
22
+ getTemplateDetails,
23
+ watchGetTemplateDetails,
24
+ getDefaultBeeTemplates,
25
+ watchGetDefaultBeeTemplates,
26
+ getOrgLevelCampaignSettings,
27
+ watchGetOrgLevelCampaignSettings,
28
+ watchSendingFile,
29
+ watchFetchWeCrmAccounts,
20
30
  } from '../sagas';
21
31
 
22
32
  import * as mockData from './mockData';
@@ -88,6 +98,30 @@ describe('watchForGetJourneyList saga', () => {
88
98
  });
89
99
  });
90
100
 
101
+ describe('getOrgLevelCampaignSettings saga', () => {
102
+ it('handles success', () => {
103
+ const apiResult = { ok: true };
104
+ const generator = getOrgLevelCampaignSettings();
105
+ expect(generator.next().value).toEqual(call(api.getOrgLevelCampaignSettings));
106
+ expect(generator.next(apiResult).value).toEqual(put({
107
+ type: types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_SUCCESS,
108
+ result: apiResult,
109
+ }));
110
+ expect(generator.next().done).toBe(true);
111
+ });
112
+
113
+ it('handles failure', () => {
114
+ const error = new Error('oops');
115
+ const generator = getOrgLevelCampaignSettings();
116
+ expect(generator.next().value).toEqual(call(api.getOrgLevelCampaignSettings));
117
+ expect(generator.throw(error).value).toEqual(put({
118
+ type: types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_FAILURE,
119
+ error,
120
+ }));
121
+ expect(generator.next().done).toBe(true);
122
+ });
123
+ });
124
+
91
125
  describe('templateList saga', () => {
92
126
  it('handle valid response from api', () => {
93
127
  expectSaga(getAllTemplates, mockData.getAllTemplatesListSuccess)
@@ -122,16 +156,16 @@ describe('templateList saga', () => {
122
156
  .run();
123
157
  });
124
158
  it('handles error thrown from api', () => {
125
- expectSaga(getAllTemplates, mockData.getAllTemplatesListFailure)
126
- .provide([
127
- [
128
- call(api.getAllTemplates),
129
- mockData.getAllTemplatesListFailure,
130
- ],
131
- ])
159
+ const failurePayload = {
160
+ channel: { channel: 'RCS', queryParams: { page: 1 } },
161
+ queryParams: { page: 1 },
162
+ };
163
+ const error = new Error('fail');
164
+ expectSaga(getAllTemplates, failurePayload)
165
+ .provide([[matchers.call.fn(api.getAllTemplates), throwError(error)]])
132
166
  .put({
133
167
  type: types.GET_ALL_TEMPLATES_FAILURE,
134
- res: mockData.getAllTemplatesListFailure,
168
+ error,
135
169
  })
136
170
  .run();
137
171
  });
@@ -146,6 +180,39 @@ describe('watchForTemplates saga', () => {
146
180
  });
147
181
  });
148
182
 
183
+ describe('getDefaultBeeTemplates saga', () => {
184
+ it('handles success', () => {
185
+ const apiResult = { response: [{ id: 1 }] };
186
+ const generator = getDefaultBeeTemplates();
187
+ expect(generator.next().value).toEqual(call(api.getDefaultBeeTemplates));
188
+ expect(generator.next(apiResult).value).toEqual(put({
189
+ type: types.GET_DEAFULT_BEE_TEMPLATES_SUCCESS,
190
+ data: apiResult.response,
191
+ }));
192
+ expect(generator.next().done).toBe(true);
193
+ });
194
+
195
+ it('handles failure', () => {
196
+ const error = new Error('bee');
197
+ const generator = getDefaultBeeTemplates();
198
+ expect(generator.next().value).toEqual(call(api.getDefaultBeeTemplates));
199
+ expect(generator.throw(error).value).toEqual(put({
200
+ type: types.GET_DEAFULT_BEE_TEMPLATES_FAILURE,
201
+ error,
202
+ }));
203
+ expect(generator.next().done).toBe(true);
204
+ });
205
+ });
206
+
207
+ describe('watchGetDefaultBeeTemplates saga', () => {
208
+ const generator = watchGetDefaultBeeTemplates();
209
+ it('should call watchers functions', () => {
210
+ expect(generator.next().value).toEqual(
211
+ takeLatest(types.GET_DEAFULT_BEE_TEMPLATES_REQUEST, getDefaultBeeTemplates),
212
+ );
213
+ });
214
+ });
215
+
149
216
  describe('getSenderDetails Saga', () => {
150
217
  const channel = 'someChannel';
151
218
  const orgUnitId = 'someOrgUnitId';
@@ -194,6 +261,103 @@ describe('getSenderDetails Saga', () => {
194
261
  })
195
262
  );
196
263
  });
264
+
265
+ it('should handle WHATSAPP channel with domainProperties format', () => {
266
+ const whatsappChannel = 'WHATSAPP';
267
+ const whatsappOrgUnitId = 'org123';
268
+ const whatsappAction = { channel: whatsappChannel, orgUnitId: whatsappOrgUnitId };
269
+ const apiResponse = {
270
+ entity: {
271
+ WHATSAPP: { accountId: 'wa-123', phoneNumber: '+1234567890' },
272
+ },
273
+ };
274
+
275
+ const generator = getSenderDetails(whatsappAction);
276
+ expect(generator.next().value).toEqual(call(api.getSenderDetails, whatsappChannel, whatsappOrgUnitId));
277
+ expect(generator.next(apiResponse).value).toEqual(
278
+ put({
279
+ type: types.GET_SENDER_DETAILS_SUCCESS,
280
+ payload: {
281
+ channel: whatsappChannel,
282
+ domainProperties: apiResponse.entity.WHATSAPP,
283
+ },
284
+ })
285
+ );
286
+ });
287
+
288
+ it('should handle ZALO channel with domainProperties format', () => {
289
+ const zaloChannel = 'ZALO';
290
+ const zaloOrgUnitId = 'org456';
291
+ const zaloAction = { channel: zaloChannel, orgUnitId: zaloOrgUnitId };
292
+ const apiResponse = {
293
+ entity: {
294
+ ZALO: { oaId: 'zalo-oa-123', token: 'zalo-token' },
295
+ },
296
+ };
297
+
298
+ const generator = getSenderDetails(zaloAction);
299
+ expect(generator.next().value).toEqual(call(api.getSenderDetails, zaloChannel, zaloOrgUnitId));
300
+ expect(generator.next(apiResponse).value).toEqual(
301
+ put({
302
+ type: types.GET_SENDER_DETAILS_SUCCESS,
303
+ payload: {
304
+ channel: zaloChannel,
305
+ domainProperties: apiResponse.entity.ZALO,
306
+ },
307
+ })
308
+ );
309
+ });
310
+
311
+ it('should handle RCS channel with domainProperties format', () => {
312
+ const rcsChannel = 'RCS';
313
+ const rcsOrgUnitId = 'org789';
314
+ const rcsAction = { channel: rcsChannel, orgUnitId: rcsOrgUnitId };
315
+ const apiResponse = {
316
+ entity: {
317
+ RCS: { accountId: 'rcs-account-123', hostName: 'rcs.example.com' },
318
+ },
319
+ };
320
+
321
+ const generator = getSenderDetails(rcsAction);
322
+ expect(generator.next().value).toEqual(call(api.getSenderDetails, rcsChannel, rcsOrgUnitId));
323
+ expect(generator.next(apiResponse).value).toEqual(
324
+ put({
325
+ type: types.GET_SENDER_DETAILS_SUCCESS,
326
+ payload: {
327
+ channel: rcsChannel,
328
+ domainProperties: apiResponse.entity.RCS,
329
+ },
330
+ })
331
+ );
332
+ });
333
+
334
+ it('should handle apiResponse with errors', () => {
335
+ const apiResponse = {
336
+ errors: ['Error 1', 'Error 2'],
337
+ };
338
+
339
+ const generator = getSenderDetails(action);
340
+ expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
341
+ expect(generator.next(apiResponse).value).toEqual(
342
+ put({
343
+ type: types.GET_SENDER_DETAILS_FAILURE,
344
+ payload: apiResponse.errors,
345
+ })
346
+ );
347
+ });
348
+
349
+ it('should handle exception thrown during API call', () => {
350
+ const error = new Error('Network error');
351
+
352
+ const generator = getSenderDetails(action);
353
+ expect(generator.next().value).toEqual(call(api.getSenderDetails, channel, orgUnitId));
354
+ expect(generator.throw(error).value).toEqual(
355
+ put({
356
+ type: types.GET_SENDER_DETAILS_FAILURE,
357
+ payload: [],
358
+ })
359
+ );
360
+ });
197
361
  });
198
362
 
199
363
  describe('fetchWeCrmAccounts Saga', () => {
@@ -271,7 +435,7 @@ describe('sendZippedFile Saga', () => {
271
435
  ])
272
436
  .put({
273
437
  type: types.SEND_ZIPPED_FILE_FAILURE,
274
- data: error
438
+ data: ''
275
439
  })
276
440
  .run();
277
441
  });
@@ -430,6 +594,95 @@ describe('watchDeleteRcsTemplate saga', () => {
430
594
  });
431
595
  });
432
596
 
597
+ describe('deleteTemplate saga', () => {
598
+ const channel = { channel: 'RCS' };
599
+ const id = 'tpl-1';
600
+ it('handles success', () => {
601
+ const apiResult = { response: { ok: true } };
602
+ const generator = deleteTemplate(channel, id);
603
+ expect(generator.next().value).toEqual(call(api.deleteTemplate, channel, id));
604
+ expect(generator.next(apiResult).value).toEqual(put({
605
+ type: types.DELETE_TEMPLATE_SUCCESS,
606
+ data: apiResult.response,
607
+ }));
608
+ expect(generator.next().done).toBe(true);
609
+ });
610
+
611
+ it('handles failure', () => {
612
+ const error = new Error('fail');
613
+ const generator = deleteTemplate(channel, id);
614
+ expect(generator.next().value).toEqual(call(api.deleteTemplate, channel, id));
615
+ expect(generator.throw(error).value).toEqual(put({
616
+ type: types.DELETE_TEMPLATE_FAILURE,
617
+ error,
618
+ }));
619
+ expect(generator.next().done).toBe(true);
620
+ });
621
+ });
622
+
623
+ describe('watchDeleteTemplate saga', () => {
624
+ const generator = watchDeleteTemplate();
625
+ it('should call watchers functions', () => {
626
+ expect(generator.next().value).toEqual(
627
+ takeLatest(types.DELETE_TEMPLATE_REQUEST, deleteTemplate),
628
+ );
629
+ });
630
+ });
631
+
632
+ describe('getTemplateDetails saga', () => {
633
+ it('handles success', () => {
634
+ const id = '1';
635
+ const channel = { channel: 'RCS' };
636
+ const apiResult = { response: { id: 1 } };
637
+ const generator = getTemplateDetails(id, channel);
638
+ expect(generator.next().value).toEqual(call(api.getTemplateDetails, id, channel));
639
+ expect(generator.next(apiResult).value).toEqual(put({
640
+ type: types.GET_TEMPLATE_DETAILS_SUCCESS,
641
+ data: apiResult.response,
642
+ }));
643
+ expect(generator.next().done).toBe(true);
644
+ });
645
+
646
+ it('handles failure', () => {
647
+ const id = '1';
648
+ const channel = { channel: 'RCS' };
649
+ const error = new Error('oops');
650
+ const generator = getTemplateDetails(id, channel);
651
+ expect(generator.next().value).toEqual(call(api.getTemplateDetails, id, channel));
652
+ expect(generator.throw(error).value).toEqual(put({
653
+ type: types.GET_TEMPLATE_DETAILS_FAILURE,
654
+ error,
655
+ }));
656
+ expect(generator.next().done).toBe(true);
657
+ });
658
+ });
659
+
660
+ describe('watchGetTemplateDetails saga', () => {
661
+ const generator = watchGetTemplateDetails();
662
+ it('should call watchers functions', () => {
663
+ expect(generator.next().value).toEqual(
664
+ takeLatest(types.GET_TEMPLATE_DETAILS_REQUEST, getTemplateDetails),
665
+ );
666
+ });
667
+ });
668
+
669
+ describe('watchFetchWeCrmAccounts saga', () => {
670
+ const generator = watchFetchWeCrmAccounts();
671
+ it('should call watchers functions', () => {
672
+ expect(generator.next().value).toEqual(
673
+ takeLatest(types.GET_WECRM_ACCOUNTS_REQUEST, fetchWeCrmAccounts),
674
+ );
675
+ });
676
+ });
677
+
678
+ describe('watchSendingFile saga', () => {
679
+ const generator = watchSendingFile();
680
+ it('should call watchers functions', () => {
681
+ expect(generator.next().value).toEqual(
682
+ takeLatest(types.SEND_ZIPPED_FILE_REQUEST, sendZippedFile),
683
+ );
684
+ });
685
+ });
433
686
 
434
687
  describe('watchForGetTemplateInfoById saga', () => {
435
688
  it('should call getTemplateInfoById when preview flag is true (preview request)', () => {