@capillarytech/creatives-library 8.0.345-alpha.12 → 8.0.345-alpha.14

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 (129) hide show
  1. package/constants/unified.js +0 -29
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +0 -13
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +109 -167
  7. package/v2Components/CapActionButton/index.scss +6 -157
  8. package/v2Components/CapActionButton/messages.js +3 -19
  9. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  10. package/v2Components/CapTagList/index.js +0 -10
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -160
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -341
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  22. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  23. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  24. package/v2Components/CommonTestAndPreview/index.js +186 -676
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -49
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  31. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  32. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  33. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  34. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  35. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  36. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  37. package/v2Components/FormBuilder/index.js +10 -8
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -33
  39. package/v2Components/TemplatePreview/index.js +28 -143
  40. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  41. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  42. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  43. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  44. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  46. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  47. package/v2Containers/CreativesContainer/constants.js +0 -9
  48. package/v2Containers/CreativesContainer/index.js +103 -300
  49. package/v2Containers/CreativesContainer/index.scss +1 -51
  50. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  51. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  52. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  53. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  54. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  55. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  56. package/v2Containers/Email/reducer.js +11 -3
  57. package/v2Containers/Email/sagas.js +9 -5
  58. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
  59. package/v2Containers/Email/tests/sagas.test.js +21 -3
  60. package/v2Containers/Rcs/constants.js +8 -119
  61. package/v2Containers/Rcs/index.js +812 -2375
  62. package/v2Containers/Rcs/index.scss +6 -276
  63. package/v2Containers/Rcs/messages.js +3 -38
  64. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70345 -98302
  65. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  66. package/v2Containers/Rcs/tests/index.test.js +121 -152
  67. package/v2Containers/Rcs/tests/mockData.js +0 -38
  68. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  69. package/v2Containers/Rcs/utils.js +11 -478
  70. package/v2Containers/Sms/Create/index.js +40 -100
  71. package/v2Containers/SmsTrai/Create/index.js +4 -9
  72. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  73. package/v2Containers/SmsTrai/Edit/index.js +130 -636
  74. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  75. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  76. package/v2Containers/SmsWrapper/index.js +8 -37
  77. package/v2Containers/TagList/index.js +0 -6
  78. package/v2Containers/Templates/_templates.scss +2 -163
  79. package/v2Containers/Templates/actions.js +0 -11
  80. package/v2Containers/Templates/constants.js +0 -2
  81. package/v2Containers/Templates/index.js +54 -119
  82. package/v2Containers/Templates/sagas.js +12 -57
  83. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  84. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  85. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  86. package/v2Containers/TemplatesV2/index.js +23 -86
  87. package/v2Containers/Whatsapp/index.js +20 -3
  88. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  89. package/utils/rcsPayloadUtils.js +0 -92
  90. package/utils/templateVarUtils.js +0 -201
  91. package/utils/tests/templateVarUtils.test.js +0 -204
  92. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +0 -18
  93. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  94. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  95. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  96. package/v2Components/SmsFallback/constants.js +0 -73
  97. package/v2Components/SmsFallback/index.js +0 -955
  98. package/v2Components/SmsFallback/index.scss +0 -265
  99. package/v2Components/SmsFallback/messages.js +0 -78
  100. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  101. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  102. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  103. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  104. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  105. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  106. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  107. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  108. package/v2Components/TemplatePreview/constants.js +0 -2
  109. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  110. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  111. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  112. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  113. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  114. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  115. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  116. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  117. package/v2Containers/Rcs/index.js.rej +0 -1336
  118. package/v2Containers/Rcs/index.scss.rej +0 -74
  119. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  120. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +0 -128
  121. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  122. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  123. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  124. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  125. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  126. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  127. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  128. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  129. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,7 +1,5 @@
1
1
  import { expectSaga } from 'redux-saga-test-plan';
2
- import {
3
- take, call, takeLatest, takeEvery, put,
4
- } from 'redux-saga/effects';
2
+ import { take, call, takeLatest, takeEvery, put } from 'redux-saga/effects';
5
3
  import * as matchers from 'redux-saga-test-plan/matchers';
6
4
  import * as api from '../../../services/api';
7
5
  import * as types from '../constants';
@@ -10,13 +8,13 @@ import * as cdnUtils from '../../../utils/cdnTransformation';
10
8
  import {
11
9
  getCdnTransformationConfig,
12
10
  getAllTemplates,
13
- getLocalSmsTemplates,
14
11
  watchGetCdnTransformationConfig,
15
12
  watchGetAllTemplates,
16
13
  getSenderDetails,
17
14
  fetchWeCrmAccounts,
18
15
  sendZippedFile,
19
16
  fetchUserList,
17
+ watchGetUserList,
20
18
  deleteRcsTemplate,
21
19
  watchDeleteRcsTemplate,
22
20
  watchForGetTemplateInfoById,
@@ -33,7 +31,6 @@ import {
33
31
  } from '../sagas';
34
32
 
35
33
  import * as mockData from './mockData';
36
- import { fetchSmsTemplatesFromQuery } from '../utils/smsTemplatesListApi';
37
34
  import { ZALO } from '../../CreativesContainer/constants';
38
35
  import { VIET_GUYS, ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
39
36
  import { throwError } from 'redux-saga-test-plan/providers';
@@ -136,8 +133,8 @@ describe('templateList saga', () => {
136
133
  ],
137
134
  ]).put({
138
135
  type: types.GET_ALL_TEMPLATES_SUCCESS,
139
- data: mockData.getAllTemplatesListSuccess.response,
140
- weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
136
+ data: mockData.getAllTemplatesListSuccess,
137
+ weCRMTemplate: mockData.getAllTemplatesListSuccess,
141
138
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
142
139
  })
143
140
  .run();
@@ -153,8 +150,8 @@ describe('templateList saga', () => {
153
150
  ],
154
151
  ]).put({
155
152
  type: types.GET_ALL_TEMPLATES_SUCCESS,
156
- data: mockData.getAllTemplatesListSuccess.response,
157
- weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
153
+ data: mockData.getAllTemplatesListSuccess,
154
+ weCRMTemplate: mockData.getAllTemplatesListSuccess,
158
155
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
159
156
  })
160
157
  .run();
@@ -173,163 +170,6 @@ describe('templateList saga', () => {
173
170
  })
174
171
  .run();
175
172
  });
176
-
177
- it('SMS channel uses fetchSmsTemplatesFromQuery', () => {
178
- const smsAction = {
179
- type: types.GET_ALL_TEMPLATES_REQUEST,
180
- channel: 'Sms',
181
- queryParams: { page: 1, perPage: 25, name: '', sortBy: 'Most Recent' },
182
- intlCopyOf: 'Kopie',
183
- };
184
- const fetched = {
185
- channelTemplates: { templates: [{ name: 'Kopie Foo' }], totalCount: 1 },
186
- weCRMTemplate: undefined,
187
- raw: {},
188
- };
189
- return expectSaga(getAllTemplates, smsAction)
190
- .provide([
191
- [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), fetched],
192
- ])
193
- .put({
194
- type: types.GET_ALL_TEMPLATES_SUCCESS,
195
- data: fetched.channelTemplates,
196
- weCRMTemplate: fetched.weCRMTemplate,
197
- isReset: true,
198
- })
199
- .run();
200
- });
201
-
202
- it('SMS channel propagates fetchSmsTemplatesFromQuery errors', () => {
203
- const smsAction = {
204
- type: types.GET_ALL_TEMPLATES_REQUEST,
205
- channel: 'SMS',
206
- queryParams: { page: 2, perPage: 25 },
207
- intlCopyOf: '',
208
- };
209
- const error = new Error('sms list fail');
210
- return expectSaga(getAllTemplates, smsAction)
211
- .provide([
212
- [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), throwError(error)],
213
- ])
214
- .put({
215
- type: types.GET_ALL_TEMPLATES_FAILURE,
216
- error,
217
- })
218
- .run();
219
- });
220
-
221
- it('wechat channel merges mapped and richmedia and applies Most Recent sort', () => {
222
- const wechatApiResponse = {
223
- response: {
224
- mapped: [
225
- { name: 'B', updatedAt: '2024-01-02T00:00:00Z' },
226
- { name: 'A', updatedAt: '2024-01-01T00:00:00Z' },
227
- ],
228
- richmedia: [{ name: 'C', updatedAt: '2024-01-03T00:00:00Z' }],
229
- unMapped: [],
230
- },
231
- };
232
- const action = {
233
- channel: 'wechat',
234
- queryParams: { sortBy: 'Most Recent', page: 1 },
235
- intlCopyOf: '',
236
- };
237
- return expectSaga(getAllTemplates, action)
238
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
239
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
240
- .run();
241
- });
242
-
243
- it('wechat channel applies Alphabetically sort', () => {
244
- const wechatApiResponse = {
245
- response: {
246
- mapped: [{ name: 'Zebra', updatedAt: '2024-01-02T00:00:00Z' }],
247
- richmedia: [{ name: 'Apple', updatedAt: '2024-01-01T00:00:00Z' }],
248
- unMapped: [],
249
- },
250
- };
251
- const action = {
252
- channel: 'wechat',
253
- queryParams: { sortBy: 'Alphabetically', page: 1 },
254
- intlCopyOf: '',
255
- };
256
- return expectSaga(getAllTemplates, action)
257
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
258
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
259
- .run();
260
- });
261
-
262
- it('wechat channel replaces Copy-of prefix when intlCopyOf is set', () => {
263
- const wechatApiResponse = {
264
- response: {
265
- mapped: [{ name: 'Copy of Promo', updatedAt: '2024-01-01T00:00:00Z' }],
266
- richmedia: [],
267
- unMapped: [],
268
- },
269
- };
270
- const action = {
271
- channel: 'wechat',
272
- queryParams: { page: 1 },
273
- intlCopyOf: 'Kopia av ',
274
- };
275
- return expectSaga(getAllTemplates, action)
276
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
277
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
278
- .run();
279
- });
280
- });
281
-
282
- describe('getLocalSmsTemplates saga', () => {
283
- it('invokes onSuccess with fetched data', () => {
284
- const onSuccess = jest.fn();
285
- const action = {
286
- queryParams: { page: 1 },
287
- intlCopyOf: '',
288
- onSuccess,
289
- };
290
- const fetched = { channelTemplates: { templates: [] }, raw: {} };
291
- return expectSaga(getLocalSmsTemplates, action)
292
- .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
293
- .call(onSuccess, fetched)
294
- .run();
295
- });
296
-
297
- it('invokes onFailure when fetch throws', () => {
298
- const onFailure = jest.fn();
299
- const error = new Error('local list fail');
300
- const action = {
301
- queryParams: { page: 1 },
302
- intlCopyOf: 'Kopie',
303
- onFailure,
304
- };
305
- return expectSaga(getLocalSmsTemplates, action)
306
- .provide([
307
- [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(error)],
308
- ])
309
- .call(onFailure, error)
310
- .run();
311
- });
312
-
313
- it('does not throw when callbacks are omitted', () => {
314
- const action = { queryParams: { page: 1 }, intlCopyOf: '' };
315
- return expectSaga(getLocalSmsTemplates, action)
316
- .provide([
317
- [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(new Error('x'))],
318
- ])
319
- .run();
320
- });
321
-
322
- it('does not call onSuccess when it is not a function', () => {
323
- const action = {
324
- queryParams: { page: 1 },
325
- intlCopyOf: '',
326
- onSuccess: 'not-a-fn',
327
- };
328
- const fetched = { channelTemplates: { templates: [] } };
329
- return expectSaga(getLocalSmsTemplates, action)
330
- .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
331
- .run();
332
- });
333
173
  });
334
174
 
335
175
  describe('watchForTemplates saga', () => {
@@ -523,7 +363,7 @@ describe('getSenderDetails Saga', () => {
523
363
 
524
364
  describe('fetchWeCrmAccounts Saga', () => {
525
365
  const action = { source: 'CRM' };
526
- it('handles successful fetching of WeCRM accounts', () => {
366
+ test.concurrent('handles successful fetching of WeCRM accounts', () => {
527
367
  const fakeResponse = {
528
368
  response: [{ id: 1, name: 'Account One' }]
529
369
  };
@@ -536,11 +376,14 @@ describe('fetchWeCrmAccounts Saga', () => {
536
376
  })
537
377
  );
538
378
  });
539
- it('handles error thrown from api', () => {
379
+ test.concurrent('handles error thrown from api', () => {
540
380
  const error = new Error('Fetch failed');
541
- expectSaga(fetchWeCrmAccounts, action)
381
+ expectSaga(fetchWeCrmAccounts, action.source)
542
382
  .provide([
543
- [call(api.fetchWeCrmAccounts, action.source), throwError(error)],
383
+ [
384
+ call(api.fetchWeCrmAccounts),
385
+ throwError(error)
386
+ ],
544
387
  ])
545
388
  .put({
546
389
  type: types.GET_WECRM_ACCOUNTS_FAILURE,
@@ -582,29 +425,6 @@ describe('sendZippedFile Saga', () => {
582
425
  expect(generator.next().value).toEqual(call(api.sendZippedFile, action.selectedFile));
583
426
  });
584
427
 
585
- it('calls errorHandler with message and returns early when isError is true', () => {
586
- const errorHandler = jest.fn();
587
- const successHandler = jest.fn();
588
- const errorAction = {
589
- selectedFile: 'upload.zip',
590
- errorHandler,
591
- successHandler,
592
- };
593
- const fakeErrorResponse = {
594
- status: { isError: true },
595
- message: 'Upload failed',
596
- };
597
- const generator = sendZippedFile(errorAction);
598
- expect(generator.next().value).toEqual(call(api.sendZippedFile, errorAction.selectedFile));
599
- // Advancing with isError response causes the saga to call errorHandler and yield
600
- const yieldStep = generator.next(fakeErrorResponse);
601
- expect(errorHandler).toHaveBeenCalledWith('Upload failed');
602
- expect(yieldStep.done).toBe(false);
603
- // After resuming from the yield, the return statement completes the generator
604
- expect(generator.next().done).toBe(true);
605
- expect(successHandler).not.toHaveBeenCalled();
606
- });
607
-
608
428
  it('handles error thrown from api', () => {
609
429
  const error = new Error('Fetch failed');
610
430
  expectSaga(sendZippedFile, action)
@@ -909,4 +729,114 @@ describe('watchForGetTemplateInfoById saga', () => {
909
729
  .not.call(getTemplateInfoById, action)
910
730
  .run();
911
731
  });
732
+ });
733
+
734
+ describe('watchGetOrgLevelCampaignSettings saga', () => {
735
+ it('should take latest GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST', () => {
736
+ const generator = watchGetOrgLevelCampaignSettings();
737
+ expect(generator.next().value).toEqual(
738
+ takeLatest(types.GET_ORG_LEVEL_CAMPAIGN_SETTINGS_REQUEST, getOrgLevelCampaignSettings),
739
+ );
740
+ });
741
+ });
742
+
743
+ describe('watchGetUserList saga', () => {
744
+ it('should take latest GET_USER_LIST_REQUEST', () => {
745
+ const generator = watchGetUserList();
746
+ expect(generator.next().value).toEqual(
747
+ takeLatest(types.GET_USER_LIST_REQUEST, fetchUserList),
748
+ );
749
+ });
750
+ });
751
+
752
+ describe('getAllTemplates wechat channel', () => {
753
+ it('should combine mapped and richmedia and sort by Most Recent (desc updatedAt)', () => {
754
+ const wechatChannel = {
755
+ channel: 'wechat',
756
+ queryParams: { page: 1, sortBy: 'Most Recent' },
757
+ };
758
+ const apiResult = {
759
+ response: {
760
+ mapped: [{ name: 'Template B', updatedAt: '2020-01-01' }],
761
+ richmedia: [{ name: 'Template A', updatedAt: '2023-06-01' }],
762
+ unMapped: {},
763
+ },
764
+ };
765
+ const generator = getAllTemplates(wechatChannel);
766
+ generator.next(); // yield call(Api.getAllTemplates, ...)
767
+ const putEffect = generator.next(apiResult).value;
768
+ // Most recent first: Template A (2023) before Template B (2020)
769
+ expect(putEffect).toEqual(
770
+ put({
771
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
772
+ data: {
773
+ templates: [
774
+ { name: 'Template A', updatedAt: '2023-06-01' },
775
+ { name: 'Template B', updatedAt: '2020-01-01' },
776
+ ],
777
+ },
778
+ weCRMTemplate: {},
779
+ isReset: true,
780
+ }),
781
+ );
782
+ expect(generator.next().done).toBe(true);
783
+ });
784
+
785
+ it('should combine mapped and richmedia and sort Alphabetically for wechat channel', () => {
786
+ const wechatChannel = {
787
+ channel: 'wechat',
788
+ queryParams: { page: 1, sortBy: 'Alphabetically' },
789
+ };
790
+ const apiResult = {
791
+ response: {
792
+ mapped: [{ name: 'Z Template' }, { name: 'A Template' }],
793
+ richmedia: [],
794
+ unMapped: {},
795
+ },
796
+ };
797
+ const generator = getAllTemplates(wechatChannel);
798
+ generator.next(); // yield call(Api.getAllTemplates, ...)
799
+ // b.name - a.name on strings gives NaN; sort order is unchanged from original
800
+ expect(generator.next(apiResult).value).toEqual(
801
+ put({
802
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
803
+ data: { templates: [{ name: 'Z Template' }, { name: 'A Template' }] },
804
+ weCRMTemplate: {},
805
+ isReset: true,
806
+ }),
807
+ );
808
+ expect(generator.next().done).toBe(true);
809
+ });
810
+
811
+ it('should replace COPY_OF string in template names when intlCopyOf is set', () => {
812
+ const channelWithCopy = {
813
+ channel: 'email',
814
+ queryParams: { page: 1 },
815
+ intlCopyOf: 'Copia de',
816
+ };
817
+ const apiResult = {
818
+ response: {
819
+ templates: [{ name: 'Copy of Template 1' }, { name: 'Regular Template' }],
820
+ unMapped: {},
821
+ },
822
+ };
823
+ const generator = getAllTemplates(channelWithCopy);
824
+ generator.next();
825
+ const putEffect = generator.next(apiResult).value;
826
+ expect(putEffect).toEqual(
827
+ put({
828
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
829
+ data: {
830
+ templates: [
831
+ { name: 'Copia de Template 1' },
832
+ { name: 'Regular Template' },
833
+ ],
834
+ unMapped: {},
835
+ },
836
+ weCRMTemplate: {},
837
+ isReset: true,
838
+ }),
839
+ );
840
+ expect(generator.next().done).toBe(true);
841
+ });
912
842
  });
@@ -10,7 +10,7 @@ export default css`
10
10
 
11
11
  .component-wrapper {
12
12
  ${(props) => props.isFullMode ? `
13
- max-width: 71.25rem;
13
+ max-width: 1140px;
14
14
  margin: 0 auto;
15
15
  width: 100%;
16
16
  padding: ${CAP_SPACE_24} 0;
@@ -23,77 +23,6 @@ export default css`
23
23
  height: calc(100vh - 11.25rem);
24
24
  } `}
25
25
  }
26
-
27
- /* SMS fallback / local list: single pane skips .cap-tab-v2, so flex-fill the grid instead of viewport-fixed pagination height */
28
- .creatives-templates-container--local-sms.library-mode {
29
- display: flex;
30
- flex-direction: column;
31
- flex: 1;
32
- min-height: 0;
33
- height: 100%;
34
-
35
- .component-wrapper {
36
- display: flex;
37
- flex-direction: column;
38
- flex: 1;
39
- min-height: 0;
40
- height: 100%;
41
- }
42
-
43
- .templates-v2-local-sms-pane {
44
- display: flex;
45
- flex-direction: column;
46
- flex: 1;
47
- min-height: 0;
48
- overflow: hidden;
49
- height: 100%;
50
- }
51
-
52
- .templates-v2-local-sms-pane .creatives-templates-list.library-mode {
53
- display: flex;
54
- flex-direction: column;
55
- flex: 1;
56
- min-height: 0;
57
- overflow: hidden;
58
- height: 100%;
59
- }
60
-
61
- .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type {
62
- flex: 1;
63
- min-height: 0;
64
- display: flex;
65
- flex-direction: column;
66
- overflow: hidden;
67
- }
68
-
69
- .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type > div {
70
- flex: 1;
71
- min-height: 0;
72
- display: flex;
73
- flex-direction: column;
74
- overflow: hidden;
75
- }
76
-
77
- .templates-v2-local-sms-pane .creatives-templates-list.library-mode > .cap-row:first-of-type > div > div:first-child {
78
- flex: 1;
79
- min-height: 0;
80
- display: flex;
81
- flex-direction: column;
82
- overflow: hidden;
83
- }
84
-
85
- .templates-v2-local-sms-pane .v2-pagination-container,
86
- .templates-v2-local-sms-pane .v2-pagination-container-half {
87
- /* Match _templates local-SMS: bounded height so overflow-y scroll works (flex+100% alone often doesn’t) */
88
- flex: 0 1 auto;
89
- min-height: 0;
90
- height: calc(100vh - 12rem);
91
- max-height: calc(100vh - 12rem);
92
- overflow-y: auto;
93
- overflow-x: hidden;
94
- -webkit-overflow-scrolling: touch;
95
- }
96
- }
97
26
  `;
98
27
 
99
28
  export const CapTabStyle = css`
@@ -10,8 +10,8 @@ import { connect } from 'react-redux';
10
10
  import { injectIntl, intlShape, FormattedMessage } from 'react-intl';
11
11
  import { createStructuredSelector } from 'reselect';
12
12
  import { bindActionCreators, compose } from 'redux';
13
- import { CapTab, CapCustomCard, CapButton, CapHeader, CapIcon, CapSpin, CapTooltip } from '@capillarytech/cap-ui-library';
14
- import { find, get, pick } from 'lodash';
13
+ import { CapTab, CapCustomCard, CapButton, CapHeader, CapSpin, CapIcon, CapTooltip } from '@capillarytech/cap-ui-library';
14
+ import { find, get } from 'lodash';
15
15
  import Helmet from 'react-helmet';
16
16
 
17
17
  import { UserIsAuthenticated } from '../../utils/authWrapper';
@@ -36,14 +36,13 @@ import { makeSelectAuthenticated, selectCurrentOrgDetails } from "../../v2Contai
36
36
  import {
37
37
  CALL_TASK,
38
38
  COMMON_CHANNELS,
39
- LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK,
40
39
  LOYALTY_SUPPORTED_ACTION,
41
40
  MOBILE_PUSH,
42
41
  NORMALIZED_CHANNEL_ALIASES,
43
42
  SMS,
44
43
  } from "../CreativesContainer/constants";
45
44
 
46
- const { CapCustomCardList } = CapCustomCard;
45
+ const {CapCustomCardList} = CapCustomCard;
47
46
 
48
47
  const StyledCapTab = withStyles(CapTab, CapTabStyle);
49
48
  export class TemplatesV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
@@ -120,9 +119,9 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
120
119
  return !normalizedChannelsToHideSet.has(paneKey);
121
120
  });
122
121
 
123
- if (isFullMode && !normalizedChannelsToHideSet.has(normalizeChannel(ASSETS))) {
124
- filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: ASSETS });
125
- } else if (!isFullMode) {
122
+ if (isFullMode) {
123
+ filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
124
+ } else {
126
125
  // Add special-mode panes only when not hidden (use normalized checks)
127
126
  if (!normalizedChannelsToHideSet.has(CALL_TASK.toLowerCase())) {
128
127
  filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: CALL_TASK.toLowerCase() });
@@ -223,8 +222,7 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
223
222
  this.setState({selectedChannel: nextProps.channel, panes });
224
223
  }
225
224
  }
226
-
227
- getTemplateDataForGrid = ({ templates, handlers, filterContent, channel, isLoading, loadingTip }) => {
225
+ getTemplateDataForGrid = ({templates, handlers, filterContent, channel, isLoading, loadingTip}) => {
228
226
  const currentChannel = channel.toUpperCase();
229
227
  const cardDataList = templates.map((template) => {
230
228
  const templateData =
@@ -250,8 +248,7 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
250
248
  </CapSpin>
251
249
 
252
250
  </div>);
253
- };
254
-
251
+ }
255
252
  getGalleryComponent = (location) => <Gallery location={location} isFullMode={this.props.isFullMode}/>
256
253
  getCallTaskComponent = () => (
257
254
  <CallTask
@@ -315,29 +312,6 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
315
312
  if (messageStrategy !== "X_ENGAGE" && channel === 'facebook' && !isFullMode) {
316
313
  return this.getFacebookComponent();
317
314
  }
318
- const localConfig = this.props.localTemplatesConfig || pick(this.props, LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK);
319
- const useLocalTemplates = localConfig.useLocalTemplates;
320
- if (useLocalTemplates && channel === (this.props.channel || 'sms')) {
321
- // Reuse full Templates component (same UI as Redux flow) with local data only
322
- const location = { pathname: `/${channel}`, search: '', query: !this.props.isFullMode ? { type: 'embedded', module: 'library' } : {} };
323
- return (
324
- <Templates
325
- key={`${channel}-local`}
326
- location={location}
327
- route={{ name: channel }}
328
- router={this.props.router}
329
- isFullMode={this.props.isFullMode}
330
- createNew={this.props.createNew}
331
- onSelectTemplate={this.props.onSelectTemplate}
332
- handlePeviewTemplate={this.props.handlePeviewTemplate}
333
- messageStrategy={this.props.messageStrategy}
334
- smsRegister={this.props.smsRegister}
335
- hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
336
- localTemplatesConfig={localConfig}
337
- />
338
- );
339
- }
340
-
341
315
  const location = {pathname: `/${channel}`, search: '', query};
342
316
  switch (channel) {
343
317
  case 'call_task':
@@ -387,55 +361,29 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
387
361
  }
388
362
  render() {
389
363
  const { isFullMode, className, cap = {}, Global = {}} = this.props;
390
- const useLocalTemplates = get(this.props, 'localTemplatesConfig.useLocalTemplates', false);
391
364
  const { accessiblePermissions = []} = cap.user || Global.user || {};
392
365
  let isCreativeAccessible = true;
393
366
  if (!accessiblePermissions.includes(CREATIVES_UI_VIEW)) {
394
367
  isCreativeAccessible = false;
395
368
  }
396
- // Recompute active pane content every render so local-list mode updates
397
- // (templates/loading/search UI) are not stuck with the initial cached pane.
398
- const panes = this.setChannelContent(this.state.selectedChannel, this.state.panes);
399
- const hideChannelTabsForLocalSms = useLocalTemplates && panes.length === 1;
400
- const activeLocalPane = hideChannelTabsForLocalSms
401
- ? (panes.find(
402
- (p) => String(p.key).toLowerCase() === String(this.state.selectedChannel).toLowerCase(),
403
- ) || panes[0])
404
- : null;
405
369
  return (
406
370
  !isCreativeAccessible ? <AccessForbidden /> : (
407
- <div
408
- className={`${className} creatives-templates-container ${isFullMode ? 'fullmode' : 'library-mode'}${useLocalTemplates ? ' creatives-templates-container--local-sms' : ''}`}
409
- data-testid="cap-wrapper"
410
- >
411
- {isFullMode && !useLocalTemplates && (
412
- <Helmet
413
- title={this.props.intl.formatMessage(messages.creatives)}
414
- meta={[
415
- { name: 'description', content: this.props.intl.formatMessage(messages.creativesDesc) },
416
- ]}
417
- />
418
- )}
371
+ <div className={`${className} creatives-templates-container ${isFullMode ? 'fullmode' : 'library-mode'}`} data-testid="cap-wrapper">
372
+ {isFullMode && <Helmet
373
+ title={this.props.intl.formatMessage(messages.creatives)}
374
+ meta={[
375
+ { name: 'description', content: this.props.intl.formatMessage(messages.creativesDesc) },
376
+ ]}
377
+ />}
419
378
  <div className="component-wrapper">
420
- {isFullMode && (
421
- <CapHeader
422
- title={<FormattedMessage {...messages.creatives} />}
423
- {...(!useLocalTemplates && {
424
- description: <FormattedMessage {...messages.creativesDesc} />,
425
- })}
426
- />
427
- )}
428
- {hideChannelTabsForLocalSms ? (
429
- <div className="templates-v2-local-sms-pane">{activeLocalPane?.content}</div>
430
- ) : (
431
- <StyledCapTab
432
- panes={panes}
433
- onChange={this.channelChange}
434
- activeKey={this.state.selectedChannel}
435
- defaultActiveKey={this.state.selectedChannel}
436
- isFullMode={isFullMode}
437
- />
438
- )}
379
+ {isFullMode && <CapHeader title={<FormattedMessage {...messages.creatives}/>} description={<FormattedMessage {...messages.creativesDesc}/>}/>}
380
+ <StyledCapTab
381
+ panes={this.state.panes}
382
+ onChange={this.channelChange}
383
+ activeKey={this.state.selectedChannel}
384
+ defaultActiveKey={this.state.selectedChannel}
385
+ isFullMode={isFullMode}
386
+ />
439
387
  </div>
440
388
  </div>
441
389
  )
@@ -467,17 +415,6 @@ TemplatesV2.propTypes = {
467
415
  currentOrgDetails: PropTypes.object,
468
416
  restrictPersonalization: PropTypes.bool,
469
417
  isAnonymousType: PropTypes.bool,
470
- // Optional: reuse grid UI with local template list (e.g. SMS fallback). Pass object or same keys as individual props.
471
- localTemplatesConfig: PropTypes.shape({
472
- useLocalTemplates: PropTypes.bool,
473
- localTemplates: PropTypes.arrayOf(PropTypes.object),
474
- localTemplatesLoading: PropTypes.bool,
475
- localTemplatesLoadingTip: PropTypes.string,
476
- localTemplatesFilterContent: PropTypes.node,
477
- localTemplatesFooterContent: PropTypes.node,
478
- localTemplatesOnPageChange: PropTypes.func,
479
- localTemplatesUseSkeleton: PropTypes.bool,
480
- }),
481
418
  };
482
419
 
483
420
  TemplatesV2.defaultProps = {
@@ -118,7 +118,6 @@ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
118
118
  import CapImageUpload from '../../v2Components/CapImageUpload';
119
119
  import TagList from '../TagList';
120
120
  import { validateTags } from '../../utils/tagValidations';
121
- import { splitContentByOrderedVarTokens } from '../../utils/templateVarUtils';
122
121
  import { capitalizeString } from '../../utils/Formatter';
123
122
  import CapWhatsappCTA from '../../v2Components/CapWhatsappCTA';
124
123
  import {
@@ -484,10 +483,28 @@ export const Whatsapp = (props) => {
484
483
  );
485
484
  };
486
485
 
486
+ const converStringToVarArr = (validVarArr, content) => {
487
+ const templateVarArray = [];
488
+ while (content?.length !== 0) {
489
+ //converting content string to an array split at var
490
+ const index = content.indexOf(validVarArr?.[0]);
491
+ if (index !== -1) {
492
+ templateVarArray.push(content.substring(0, index)); //push string before var
493
+ templateVarArray.push(validVarArr?.[0]); //push var
494
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length); //remaining str
495
+ validVarArr?.shift(); //remove considered var
496
+ } else {
497
+ templateVarArray.push(content); //remaining str
498
+ break;
499
+ }
500
+ }
501
+ return templateVarArray;
502
+ }
503
+
487
504
  const computeTextMessage = (msg, varMap, regex) => {
488
505
  const validVarArr = msg?.match(regex) || [];
489
506
  //conerting msg string to variable arr
490
- const templateHeaderArray = splitContentByOrderedVarTokens(validVarArr, msg);
507
+ const templateHeaderArray = converStringToVarArr(validVarArr, msg);
491
508
  if (templateHeaderArray?.length !== 0) {
492
509
  let clonedVarMap = {};
493
510
  if (!isEmpty(varMap)) {
@@ -541,7 +558,7 @@ export const Whatsapp = (props) => {
541
558
  setUnsubscribeRequired(true);
542
559
  }
543
560
  //converting msg string to variable arr
544
- const templateMessageArray = splitContentByOrderedVarTokens(validVarArr, msg);
561
+ const templateMessageArray = converStringToVarArr(validVarArr, msg);
545
562
  updateTempMsgArray(templateMessageArray.filter((i) => i === 0 || i));
546
563
  };
547
564