@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.353

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 (124) hide show
  1. package/constants/unified.js +0 -29
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +20 -35
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +108 -166
  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 -72
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
  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 -157
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
  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 -691
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  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 +26 -36
  37. package/v2Components/FormBuilder/index.js +168 -63
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
  39. package/v2Components/TemplatePreview/index.js +31 -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 +163 -346
  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/MobilePush/Create/test/saga.test.js +2 -2
  57. package/v2Containers/Rcs/constants.js +10 -119
  58. package/v2Containers/Rcs/index.js +818 -2450
  59. package/v2Containers/Rcs/index.scss +8 -280
  60. package/v2Containers/Rcs/messages.js +3 -34
  61. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  62. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  63. package/v2Containers/Rcs/tests/index.test.js +121 -152
  64. package/v2Containers/Rcs/tests/mockData.js +0 -38
  65. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  66. package/v2Containers/Rcs/utils.js +11 -478
  67. package/v2Containers/Sms/Create/index.js +40 -106
  68. package/v2Containers/SmsTrai/Create/index.js +4 -9
  69. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  70. package/v2Containers/SmsTrai/Edit/index.js +130 -640
  71. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  72. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  73. package/v2Containers/SmsWrapper/index.js +8 -37
  74. package/v2Containers/TagList/index.js +0 -6
  75. package/v2Containers/Templates/_templates.scss +9 -166
  76. package/v2Containers/Templates/actions.js +0 -11
  77. package/v2Containers/Templates/constants.js +0 -2
  78. package/v2Containers/Templates/index.js +52 -120
  79. package/v2Containers/Templates/sagas.js +12 -56
  80. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
  81. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  82. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  83. package/v2Containers/TemplatesV2/index.js +23 -86
  84. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  85. package/v2Containers/Whatsapp/index.js +20 -3
  86. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  87. package/utils/rcsPayloadUtils.js +0 -92
  88. package/utils/templateVarUtils.js +0 -201
  89. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  90. package/utils/tests/templateVarUtils.test.js +0 -204
  91. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  92. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  93. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  94. package/v2Components/SmsFallback/constants.js +0 -73
  95. package/v2Components/SmsFallback/index.js +0 -956
  96. package/v2Components/SmsFallback/index.scss +0 -265
  97. package/v2Components/SmsFallback/messages.js +0 -78
  98. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
  99. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  100. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  101. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  102. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -223
  103. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  104. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  105. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  106. package/v2Components/TemplatePreview/constants.js +0 -2
  107. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  108. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  109. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  110. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  111. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  112. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  113. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  114. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  115. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  116. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  117. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  118. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  119. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  120. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  121. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  122. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  123. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  124. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,22 +1,20 @@
1
1
  import { expectSaga } from 'redux-saga-test-plan';
2
- import {
3
- take, call, takeLatest, takeEvery, put,
4
- } from 'redux-saga/effects';
5
2
  import * as matchers from 'redux-saga-test-plan/matchers';
6
3
  import { throwError } from 'redux-saga-test-plan/providers';
4
+ import { call, takeLatest, put } from 'redux-saga/effects';
7
5
  import * as api from '../../../services/api';
8
6
  import * as types from '../constants';
9
7
  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,
@@ -41,14 +39,10 @@ import {
41
39
  watchUnarchiveTemplate,
42
40
  watchBulkArchive,
43
41
  watchBulkUnarchive,
44
- watchGetUserList,
45
42
  } from '../sagas';
46
-
47
- import * as mockData from './mockData';
48
- import { fetchSmsTemplatesFromQuery } from '../utils/smsTemplatesListApi';
49
- import { ZALO } from '../../CreativesContainer/constants';
50
- import { VIET_GUYS, ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
51
43
  import { getTemplateInfoById } from '../../Zalo/saga';
44
+ import { ZALO_TEMPLATE_INFO_REQUEST } from '../../Zalo/constants';
45
+ import * as mockData from './mockData';
52
46
 
53
47
  jest.mock('@capillarytech/cap-ui-library', () => ({
54
48
  CapNotification: {
@@ -156,8 +150,8 @@ describe('templateList saga', () => {
156
150
  ],
157
151
  ]).put({
158
152
  type: types.GET_ALL_TEMPLATES_SUCCESS,
159
- data: mockData.getAllTemplatesListSuccess.response,
160
- weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
153
+ data: mockData.getAllTemplatesListSuccess,
154
+ weCRMTemplate: mockData.getAllTemplatesListSuccess,
161
155
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
162
156
  })
163
157
  .run();
@@ -173,8 +167,8 @@ describe('templateList saga', () => {
173
167
  ],
174
168
  ]).put({
175
169
  type: types.GET_ALL_TEMPLATES_SUCCESS,
176
- data: mockData.getAllTemplatesListSuccess.response,
177
- weCRMTemplate: mockData.getAllTemplatesListSuccess.response?.unMapped,
170
+ data: mockData.getAllTemplatesListSuccess,
171
+ weCRMTemplate: mockData.getAllTemplatesListSuccess,
178
172
  isReset: mockData.getAllTemplatesListSuccess?.queryParams?.page === 1,
179
173
  })
180
174
  .run();
@@ -193,163 +187,6 @@ describe('templateList saga', () => {
193
187
  })
194
188
  .run();
195
189
  });
196
-
197
- it('SMS channel uses fetchSmsTemplatesFromQuery', () => {
198
- const smsAction = {
199
- type: types.GET_ALL_TEMPLATES_REQUEST,
200
- channel: 'Sms',
201
- queryParams: { page: 1, perPage: 25, name: '', sortBy: 'Most Recent' },
202
- intlCopyOf: 'Kopie',
203
- };
204
- const fetched = {
205
- channelTemplates: { templates: [{ name: 'Kopie Foo' }], totalCount: 1 },
206
- weCRMTemplate: undefined,
207
- raw: {},
208
- };
209
- return expectSaga(getAllTemplates, smsAction)
210
- .provide([
211
- [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), fetched],
212
- ])
213
- .put({
214
- type: types.GET_ALL_TEMPLATES_SUCCESS,
215
- data: fetched.channelTemplates,
216
- weCRMTemplate: fetched.weCRMTemplate,
217
- isReset: true,
218
- })
219
- .run();
220
- });
221
-
222
- it('SMS channel propagates fetchSmsTemplatesFromQuery errors', () => {
223
- const smsAction = {
224
- type: types.GET_ALL_TEMPLATES_REQUEST,
225
- channel: 'SMS',
226
- queryParams: { page: 2, perPage: 25 },
227
- intlCopyOf: '',
228
- };
229
- const error = new Error('sms list fail');
230
- return expectSaga(getAllTemplates, smsAction)
231
- .provide([
232
- [call(fetchSmsTemplatesFromQuery, smsAction.queryParams, smsAction.intlCopyOf), throwError(error)],
233
- ])
234
- .put({
235
- type: types.GET_ALL_TEMPLATES_FAILURE,
236
- error,
237
- })
238
- .run();
239
- });
240
-
241
- it('wechat channel merges mapped and richmedia and applies Most Recent sort', () => {
242
- const wechatApiResponse = {
243
- response: {
244
- mapped: [
245
- { name: 'B', updatedAt: '2024-01-02T00:00:00Z' },
246
- { name: 'A', updatedAt: '2024-01-01T00:00:00Z' },
247
- ],
248
- richmedia: [{ name: 'C', updatedAt: '2024-01-03T00:00:00Z' }],
249
- unMapped: [],
250
- },
251
- };
252
- const action = {
253
- channel: 'wechat',
254
- queryParams: { sortBy: 'Most Recent', page: 1 },
255
- intlCopyOf: '',
256
- };
257
- return expectSaga(getAllTemplates, action)
258
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
259
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
260
- .run();
261
- });
262
-
263
- it('wechat channel applies Alphabetically sort', () => {
264
- const wechatApiResponse = {
265
- response: {
266
- mapped: [{ name: 'Zebra', updatedAt: '2024-01-02T00:00:00Z' }],
267
- richmedia: [{ name: 'Apple', updatedAt: '2024-01-01T00:00:00Z' }],
268
- unMapped: [],
269
- },
270
- };
271
- const action = {
272
- channel: 'wechat',
273
- queryParams: { sortBy: 'Alphabetically', page: 1 },
274
- intlCopyOf: '',
275
- };
276
- return expectSaga(getAllTemplates, action)
277
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
278
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
279
- .run();
280
- });
281
-
282
- it('wechat channel replaces Copy-of prefix when intlCopyOf is set', () => {
283
- const wechatApiResponse = {
284
- response: {
285
- mapped: [{ name: 'Copy of Promo', updatedAt: '2024-01-01T00:00:00Z' }],
286
- richmedia: [],
287
- unMapped: [],
288
- },
289
- };
290
- const action = {
291
- channel: 'wechat',
292
- queryParams: { page: 1 },
293
- intlCopyOf: 'Kopia av ',
294
- };
295
- return expectSaga(getAllTemplates, action)
296
- .provide([[matchers.call.fn(api.getAllTemplates), wechatApiResponse]])
297
- .put.actionType(types.GET_ALL_TEMPLATES_SUCCESS)
298
- .run();
299
- });
300
- });
301
-
302
- describe('getLocalSmsTemplates saga', () => {
303
- it('invokes onSuccess with fetched data', () => {
304
- const onSuccess = jest.fn();
305
- const action = {
306
- queryParams: { page: 1 },
307
- intlCopyOf: '',
308
- onSuccess,
309
- };
310
- const fetched = { channelTemplates: { templates: [] }, raw: {} };
311
- return expectSaga(getLocalSmsTemplates, action)
312
- .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
313
- .call(onSuccess, fetched)
314
- .run();
315
- });
316
-
317
- it('invokes onFailure when fetch throws', () => {
318
- const onFailure = jest.fn();
319
- const error = new Error('local list fail');
320
- const action = {
321
- queryParams: { page: 1 },
322
- intlCopyOf: 'Kopie',
323
- onFailure,
324
- };
325
- return expectSaga(getLocalSmsTemplates, action)
326
- .provide([
327
- [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(error)],
328
- ])
329
- .call(onFailure, error)
330
- .run();
331
- });
332
-
333
- it('does not throw when callbacks are omitted', () => {
334
- const action = { queryParams: { page: 1 }, intlCopyOf: '' };
335
- return expectSaga(getLocalSmsTemplates, action)
336
- .provide([
337
- [call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), throwError(new Error('x'))],
338
- ])
339
- .run();
340
- });
341
-
342
- it('does not call onSuccess when it is not a function', () => {
343
- const action = {
344
- queryParams: { page: 1 },
345
- intlCopyOf: '',
346
- onSuccess: 'not-a-fn',
347
- };
348
- const fetched = { channelTemplates: { templates: [] } };
349
- return expectSaga(getLocalSmsTemplates, action)
350
- .provide([[call(fetchSmsTemplatesFromQuery, action.queryParams, action.intlCopyOf), fetched]])
351
- .run();
352
- });
353
190
  });
354
191
 
355
192
  describe('watchForTemplates saga', () => {
@@ -543,7 +380,7 @@ describe('getSenderDetails Saga', () => {
543
380
 
544
381
  describe('fetchWeCrmAccounts Saga', () => {
545
382
  const action = { source: 'CRM' };
546
- it('handles successful fetching of WeCRM accounts', () => {
383
+ test.concurrent('handles successful fetching of WeCRM accounts', () => {
547
384
  const fakeResponse = {
548
385
  response: [{ id: 1, name: 'Account One' }]
549
386
  };
@@ -556,11 +393,14 @@ describe('fetchWeCrmAccounts Saga', () => {
556
393
  })
557
394
  );
558
395
  });
559
- it('handles error thrown from api', () => {
396
+ test.concurrent('handles error thrown from api', () => {
560
397
  const error = new Error('Fetch failed');
561
- expectSaga(fetchWeCrmAccounts, action)
398
+ expectSaga(fetchWeCrmAccounts, action.source)
562
399
  .provide([
563
- [call(api.fetchWeCrmAccounts, action.source), throwError(error)],
400
+ [
401
+ call(api.fetchWeCrmAccounts),
402
+ throwError(error)
403
+ ],
564
404
  ])
565
405
  .put({
566
406
  type: types.GET_WECRM_ACCOUNTS_FAILURE,
@@ -602,29 +442,6 @@ describe('sendZippedFile Saga', () => {
602
442
  expect(generator.next().value).toEqual(call(api.sendZippedFile, action.selectedFile));
603
443
  });
604
444
 
605
- it('calls errorHandler with message and returns early when isError is true', () => {
606
- const errorHandler = jest.fn();
607
- const successHandler = jest.fn();
608
- const errorAction = {
609
- selectedFile: 'upload.zip',
610
- errorHandler,
611
- successHandler,
612
- };
613
- const fakeErrorResponse = {
614
- status: { isError: true },
615
- message: 'Upload failed',
616
- };
617
- const generator = sendZippedFile(errorAction);
618
- expect(generator.next().value).toEqual(call(api.sendZippedFile, errorAction.selectedFile));
619
- // Advancing with isError response causes the saga to call errorHandler and yield
620
- const yieldStep = generator.next(fakeErrorResponse);
621
- expect(errorHandler).toHaveBeenCalledWith('Upload failed');
622
- expect(yieldStep.done).toBe(false);
623
- // After resuming from the yield, the return statement completes the generator
624
- expect(generator.next().done).toBe(true);
625
- expect(successHandler).not.toHaveBeenCalled();
626
- });
627
-
628
445
  it('handles error thrown from api', () => {
629
446
  const error = new Error('Fetch failed');
630
447
  expectSaga(sendZippedFile, action)
@@ -1390,7 +1207,7 @@ describe('v2TemplateSaga', () => {
1390
1207
  // all() returns an IO object with an ALL key containing the array of effects
1391
1208
  expect(step.value).toHaveProperty('@@redux-saga/IO', true);
1392
1209
  expect(step.value).toHaveProperty('ALL');
1393
- expect(step.value.ALL).toHaveLength(15);
1210
+ expect(step.value.ALL).toHaveLength(14);
1394
1211
  expect(step.done).toBe(false);
1395
1212
  });
1396
1213
  });
@@ -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: 0.714rem 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 = {
@@ -11,7 +11,7 @@ describe('WeChat Template Management Sagas', () => {
11
11
  describe('getDefaultWeChatTemplates Saga', () => {
12
12
  const params = { channel: 'WeChat', queryParams: { type: 'default' } };
13
13
 
14
- it('handles fetching default WeChat templates successfully', () => {
14
+ it.concurrent('handles fetching default WeChat templates successfully', () => {
15
15
  const fakeResponse = {
16
16
  response: {
17
17
  unMapped: [{ id: 1, name: 'Template One' }],
@@ -31,7 +31,7 @@ describe('WeChat Template Management Sagas', () => {
31
31
  .run();
32
32
  });
33
33
 
34
- it('handles failure in fetching default WeChat templates', () => {
34
+ it.concurrent('handles failure in fetching default WeChat templates', () => {
35
35
  const error = new Error('Fetch failed');
36
36
 
37
37
  return expectSaga(sagas.getDefaultWeChatTemplates, params)
@@ -48,7 +48,7 @@ describe('WeChat Template Management Sagas', () => {
48
48
 
49
49
  describe('createTemplate Saga', () => {
50
50
  const template = { name: 'New WeChat Template' };
51
- test('handles creating a WeChat template successfully', () => {
51
+ test.concurrent('handles creating a WeChat template successfully', () => {
52
52
  const fakeResponse = {
53
53
  response: template,
54
54
  status: { code: 200 }
@@ -64,7 +64,7 @@ describe('WeChat Template Management Sagas', () => {
64
64
  })
65
65
  .run();
66
66
  });
67
- test('handles failure when creating a WeChat template', () => {
67
+ test.concurrent('handles failure when creating a WeChat template', () => {
68
68
  const error = new Error('Creation failed');
69
69
  return expectSaga(sagas.createTemplate, template)
70
70
  .provide([
@@ -79,7 +79,7 @@ describe('WeChat Template Management Sagas', () => {
79
79
  });
80
80
 
81
81
  describe('fetchWeCrmAccounts Saga', () => {
82
- it('handles fetching WeCrm accounts successfully', () => {
82
+ it.concurrent('handles fetching WeCrm accounts successfully', () => {
83
83
  const fakeResponse = {
84
84
  response: [{ id: 1, name: 'Account One' }]
85
85
  };
@@ -95,7 +95,7 @@ describe('WeChat Template Management Sagas', () => {
95
95
  .run();
96
96
  });
97
97
 
98
- it('handles failure in fetching WeCrm accounts', () => {
98
+ it.concurrent('handles failure in fetching WeCrm accounts', () => {
99
99
  const error = new Error('Fetch failed');
100
100
 
101
101
  return expectSaga(sagas.fetchWeCrmAccounts)
@@ -113,7 +113,7 @@ describe('WeChat Template Management Sagas', () => {
113
113
  describe('getTemplateDetails Saga', () => {
114
114
  const id = '123';
115
115
 
116
- it('handles fetching template details successfully', () => {
116
+ it.concurrent('handles fetching template details successfully', () => {
117
117
  const fakeResponse = {
118
118
  response: { id: 123, name: 'Detailed Template' }
119
119
  };
@@ -129,7 +129,7 @@ describe('WeChat Template Management Sagas', () => {
129
129
  .run();
130
130
  });
131
131
 
132
- it('handles failure in fetching template details', () => {
132
+ it.concurrent('handles failure in fetching template details', () => {
133
133
  const error = new Error('Fetch failed');
134
134
 
135
135
  return expectSaga(sagas.getTemplateDetails, id)
@@ -148,7 +148,7 @@ describe('WeChat Template Management Sagas', () => {
148
148
 
149
149
  // Test combined saga
150
150
  describe('Combined v2WechatMapTemplatesSagas', () => {
151
- it('should initialize all WeChat-related watcher sagas without error', () => {
151
+ it.concurrent('should initialize all WeChat-related watcher sagas without error', () => {
152
152
  return expectSaga(v2WechatMapTemplatesSagas)
153
153
  .run();
154
154
  });
@@ -119,7 +119,6 @@ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
119
119
  import CapImageUpload from '../../v2Components/CapImageUpload';
120
120
  import TagList from '../TagList';
121
121
  import { validateTags } from '../../utils/tagValidations';
122
- import { splitContentByOrderedVarTokens } from '../../utils/templateVarUtils';
123
122
  import { capitalizeString } from '../../utils/Formatter';
124
123
  import CapWhatsappCTA from '../../v2Components/CapWhatsappCTA';
125
124
  import {
@@ -497,10 +496,28 @@ export const Whatsapp = (props) => {
497
496
  );
498
497
  };
499
498
 
499
+ const converStringToVarArr = (validVarArr, content) => {
500
+ const templateVarArray = [];
501
+ while (content?.length !== 0) {
502
+ //converting content string to an array split at var
503
+ const index = content.indexOf(validVarArr?.[0]);
504
+ if (index !== -1) {
505
+ templateVarArray.push(content.substring(0, index)); //push string before var
506
+ templateVarArray.push(validVarArr?.[0]); //push var
507
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length); //remaining str
508
+ validVarArr?.shift(); //remove considered var
509
+ } else {
510
+ templateVarArray.push(content); //remaining str
511
+ break;
512
+ }
513
+ }
514
+ return templateVarArray;
515
+ }
516
+
500
517
  const computeTextMessage = (msg, varMap, regex) => {
501
518
  const validVarArr = msg?.match(regex) || [];
502
519
  //conerting msg string to variable arr
503
- const templateHeaderArray = splitContentByOrderedVarTokens(validVarArr, msg);
520
+ const templateHeaderArray = converStringToVarArr(validVarArr, msg);
504
521
  if (templateHeaderArray?.length !== 0) {
505
522
  let clonedVarMap = {};
506
523
  if (!isEmpty(varMap)) {
@@ -554,7 +571,7 @@ export const Whatsapp = (props) => {
554
571
  setUnsubscribeRequired(true);
555
572
  }
556
573
  //converting msg string to variable arr
557
- const templateMessageArray = splitContentByOrderedVarTokens(validVarArr, msg);
574
+ const templateMessageArray = converStringToVarArr(validVarArr, msg);
558
575
  updateTempMsgArray(templateMessageArray.filter((i) => i === 0 || i));
559
576
  };
560
577