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

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 (136) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +35 -20
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  8. package/utils/tests/templateVarUtils.test.js +204 -0
  9. package/v2Components/CapActionButton/constants.js +7 -0
  10. package/v2Components/CapActionButton/index.js +166 -108
  11. package/v2Components/CapActionButton/index.scss +157 -6
  12. package/v2Components/CapActionButton/messages.js +19 -3
  13. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  14. package/v2Components/CapTagList/index.js +10 -0
  15. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +213 -21
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  22. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +0 -17
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +346 -146
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +138 -48
  27. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  28. package/v2Components/CommonTestAndPreview/constants.js +38 -4
  29. package/v2Components/CommonTestAndPreview/index.js +691 -235
  30. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  31. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  32. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  33. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  36. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  37. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  38. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +0 -159
  40. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  41. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -256
  42. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -2
  43. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -198
  44. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  45. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +36 -26
  46. package/v2Components/FormBuilder/index.js +11 -6
  47. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  48. package/v2Components/SmsFallback/constants.js +73 -0
  49. package/v2Components/SmsFallback/index.js +956 -0
  50. package/v2Components/SmsFallback/index.scss +265 -0
  51. package/v2Components/SmsFallback/messages.js +78 -0
  52. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  53. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  54. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  55. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  56. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  57. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  58. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  59. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  60. package/v2Components/TemplatePreview/_templatePreview.scss +38 -23
  61. package/v2Components/TemplatePreview/constants.js +2 -0
  62. package/v2Components/TemplatePreview/index.js +143 -31
  63. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  64. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  65. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  66. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  67. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  68. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  69. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  70. package/v2Containers/App/constants.js +0 -3
  71. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  72. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  73. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  74. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  75. package/v2Containers/CreativesContainer/constants.js +9 -0
  76. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  77. package/v2Containers/CreativesContainer/index.js +322 -103
  78. package/v2Containers/CreativesContainer/index.scss +51 -1
  79. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  80. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  81. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  82. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  85. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  86. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  87. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  88. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  89. package/v2Containers/Rcs/constants.js +119 -10
  90. package/v2Containers/Rcs/index.js +2445 -813
  91. package/v2Containers/Rcs/index.scss +280 -8
  92. package/v2Containers/Rcs/messages.js +34 -3
  93. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  94. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  95. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  96. package/v2Containers/Rcs/tests/index.test.js +152 -121
  97. package/v2Containers/Rcs/tests/mockData.js +38 -0
  98. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  99. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  100. package/v2Containers/Rcs/utils.js +478 -11
  101. package/v2Containers/Sms/Create/index.js +106 -40
  102. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  103. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  104. package/v2Containers/SmsTrai/Create/index.js +9 -4
  105. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  106. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  107. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  108. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  109. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  110. package/v2Containers/SmsWrapper/index.js +37 -8
  111. package/v2Containers/TagList/index.js +6 -0
  112. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  113. package/v2Containers/Templates/_templates.scss +166 -9
  114. package/v2Containers/Templates/actions.js +11 -0
  115. package/v2Containers/Templates/constants.js +2 -0
  116. package/v2Containers/Templates/index.js +122 -120
  117. package/v2Containers/Templates/sagas.js +56 -12
  118. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  119. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  120. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  121. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  122. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  123. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  124. package/v2Containers/TemplatesV2/index.js +86 -23
  125. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  126. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  127. package/v2Containers/WebPush/Create/index.js +8 -91
  128. package/v2Containers/WebPush/Create/index.scss +0 -7
  129. package/v2Containers/Whatsapp/index.js +3 -20
  130. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  131. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +0 -169
  132. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +0 -522
  133. package/v2Containers/App/tests/constants.test.js +0 -61
  134. package/v2Containers/Templates/tests/webpush.test.js +0 -375
  135. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +0 -338
  136. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +0 -325
@@ -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, CapSpin, CapIcon, CapTooltip } from '@capillarytech/cap-ui-library';
14
- import { find, get } from 'lodash';
13
+ import { CapTab, CapCustomCard, CapButton, CapHeader, CapIcon, CapSpin, CapTooltip } from '@capillarytech/cap-ui-library';
14
+ import { find, get, pick } from 'lodash';
15
15
  import Helmet from 'react-helmet';
16
16
 
17
17
  import { UserIsAuthenticated } from '../../utils/authWrapper';
@@ -36,13 +36,14 @@ import { makeSelectAuthenticated, selectCurrentOrgDetails } from "../../v2Contai
36
36
  import {
37
37
  CALL_TASK,
38
38
  COMMON_CHANNELS,
39
+ LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK,
39
40
  LOYALTY_SUPPORTED_ACTION,
40
41
  MOBILE_PUSH,
41
42
  NORMALIZED_CHANNEL_ALIASES,
42
43
  SMS,
43
44
  } from "../CreativesContainer/constants";
44
45
 
45
- const {CapCustomCardList} = CapCustomCard;
46
+ const { CapCustomCardList } = CapCustomCard;
46
47
 
47
48
  const StyledCapTab = withStyles(CapTab, CapTabStyle);
48
49
  export class TemplatesV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
@@ -119,9 +120,9 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
119
120
  return !normalizedChannelsToHideSet.has(paneKey);
120
121
  });
121
122
 
122
- if (isFullMode) {
123
- filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
124
- } else {
123
+ if (isFullMode && !normalizedChannelsToHideSet.has(normalizeChannel(ASSETS))) {
124
+ filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: ASSETS });
125
+ } else if (!isFullMode) {
125
126
  // Add special-mode panes only when not hidden (use normalized checks)
126
127
  if (!normalizedChannelsToHideSet.has(CALL_TASK.toLowerCase())) {
127
128
  filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: CALL_TASK.toLowerCase() });
@@ -222,7 +223,8 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
222
223
  this.setState({selectedChannel: nextProps.channel, panes });
223
224
  }
224
225
  }
225
- getTemplateDataForGrid = ({templates, handlers, filterContent, channel, isLoading, loadingTip}) => {
226
+
227
+ getTemplateDataForGrid = ({ templates, handlers, filterContent, channel, isLoading, loadingTip }) => {
226
228
  const currentChannel = channel.toUpperCase();
227
229
  const cardDataList = templates.map((template) => {
228
230
  const templateData =
@@ -248,7 +250,8 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
248
250
  </CapSpin>
249
251
 
250
252
  </div>);
251
- }
253
+ };
254
+
252
255
  getGalleryComponent = (location) => <Gallery location={location} isFullMode={this.props.isFullMode}/>
253
256
  getCallTaskComponent = () => (
254
257
  <CallTask
@@ -312,6 +315,29 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
312
315
  if (messageStrategy !== "X_ENGAGE" && channel === 'facebook' && !isFullMode) {
313
316
  return this.getFacebookComponent();
314
317
  }
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
+
315
341
  const location = {pathname: `/${channel}`, search: '', query};
316
342
  switch (channel) {
317
343
  case 'call_task':
@@ -361,29 +387,55 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
361
387
  }
362
388
  render() {
363
389
  const { isFullMode, className, cap = {}, Global = {}} = this.props;
390
+ const useLocalTemplates = get(this.props, 'localTemplatesConfig.useLocalTemplates', false);
364
391
  const { accessiblePermissions = []} = cap.user || Global.user || {};
365
392
  let isCreativeAccessible = true;
366
393
  if (!accessiblePermissions.includes(CREATIVES_UI_VIEW)) {
367
394
  isCreativeAccessible = false;
368
395
  }
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;
369
405
  return (
370
406
  !isCreativeAccessible ? <AccessForbidden /> : (
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
- />}
378
- <div className="component-wrapper">
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}
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
+ ]}
386
417
  />
418
+ )}
419
+ <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
+ )}
387
439
  </div>
388
440
  </div>
389
441
  )
@@ -415,6 +467,17 @@ TemplatesV2.propTypes = {
415
467
  currentOrgDetails: PropTypes.object,
416
468
  restrictPersonalization: PropTypes.bool,
417
469
  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
+ }),
418
481
  };
419
482
 
420
483
  TemplatesV2.defaultProps = {
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Embedded SMS template list: localTemplatesConfig + SMS-only channel visibility (RCS SMS fallback).
3
+ */
4
+ import React from 'react';
5
+ import { injectIntl } from 'react-intl';
6
+ import '@testing-library/jest-dom';
7
+ import cloneDeep from 'lodash/cloneDeep';
8
+ import { Provider } from 'react-redux';
9
+ import { configureStore } from '@capillarytech/vulcan-react-sdk/utils';
10
+ import history from '../../../utils/history';
11
+ import { initialReducer } from '../../../initialReducer';
12
+ import { render, screen } from '../../../utils/test-utils';
13
+ import { TemplatesV2 } from '../index';
14
+ import { Templates, authData, currentOrgDetails as currentOrgDetailsMock } from './mockData';
15
+ import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../../v2Components/SmsFallback/constants';
16
+
17
+ const mockTemplates = jest.fn(() => <div data-testid="templates-mock">Templates</div>);
18
+ jest.mock('v2Containers/Templates', () => ({
19
+ __esModule: true,
20
+ default: (props) => mockTemplates(props),
21
+ }));
22
+
23
+ jest.mock('../../../utils/authWrapper', () => ({
24
+ UserIsAuthenticated: jest.fn((config) => config),
25
+ }));
26
+
27
+ const ComponentToRender = injectIntl(TemplatesV2);
28
+ const renderComponent = (p) => {
29
+ const store = configureStore({}, initialReducer, history);
30
+ return render(
31
+ <Provider store={store}>
32
+ <ComponentToRender {...p} />
33
+ </Provider>,
34
+ );
35
+ };
36
+
37
+ describe('TemplatesV2 local SMS templates (embedded)', () => {
38
+ const templateActions = {
39
+ templateActions: jest.fn(),
40
+ deleteTemplate: jest.fn(),
41
+ getAccountsSettings: jest.fn(),
42
+ getAllTemplates: jest.fn(),
43
+ getCdnTransformationConfig: jest.fn(),
44
+ getDefaultBeeTemplates: jest.fn(),
45
+ getSenderDetails: jest.fn(),
46
+ getTemplateDetails: jest.fn(),
47
+ getUserList: jest.fn(),
48
+ getWeCrmAccounts: jest.fn(),
49
+ handleHtmlUpload: jest.fn(),
50
+ handleZipUpload: jest.fn(),
51
+ resetAccount: jest.fn(),
52
+ resetTemplate: jest.fn(),
53
+ resetTemplateData: jest.fn(),
54
+ resetTemplateStoreData: jest.fn(),
55
+ resetUploadData: jest.fn(),
56
+ setBEETemplate: jest.fn(),
57
+ setChannelAccount: jest.fn(),
58
+ setEdmTemplate: jest.fn(),
59
+ setFacebookAccount: jest.fn(),
60
+ setViberAccount: jest.fn(),
61
+ setWeChatAccount: jest.fn(),
62
+ };
63
+
64
+ const baseProps = {
65
+ cap: {
66
+ user: { accessiblePermissions: ['CREATIVES_UI_VIEW'] },
67
+ },
68
+ actions: { defaultAction: jest.fn(), getTemplates: jest.fn() },
69
+ Templates,
70
+ TemplatesList: Templates?.templates,
71
+ authData,
72
+ templateActions,
73
+ isFullMode: false,
74
+ className: 'embed-test',
75
+ channel: 'sms',
76
+ channelsToHide: CHANNELS_TO_HIDE_FOR_SMS_ONLY,
77
+ channelsToDisable: [],
78
+ onChannelChange: jest.fn(),
79
+ enableNewChannels: [],
80
+ /** Without JP_LOCALE_HIDE_FEATURE so SMS panes are not stripped to Email/Line/Gallery only */
81
+ currentOrgDetails: {
82
+ ...currentOrgDetailsMock,
83
+ accessibleFeatures: (currentOrgDetailsMock.accessibleFeatures || []).filter(
84
+ (f) => f !== 'JP_LOCALE_HIDE_FEATURE',
85
+ ),
86
+ },
87
+ location: {
88
+ pathname: 'v2',
89
+ basename: '/creatives/ui/',
90
+ query: {},
91
+ },
92
+ router: { push: jest.fn() },
93
+ };
94
+
95
+ beforeEach(() => {
96
+ mockTemplates.mockClear();
97
+ });
98
+
99
+ it('adds local-sms container class and single-pane layout when only SMS is visible', () => {
100
+ const p = cloneDeep(baseProps);
101
+ p.localTemplatesConfig = {
102
+ useLocalTemplates: true,
103
+ localTemplates: [],
104
+ localTemplatesLoading: false,
105
+ };
106
+ renderComponent(p);
107
+
108
+ const wrapper = screen.getByTestId('cap-wrapper');
109
+ expect(wrapper).toHaveClass('creatives-templates-container--local-sms');
110
+ expect(document.querySelector('.templates-v2-local-sms-pane')).toBeTruthy();
111
+ expect(mockTemplates).toHaveBeenCalled();
112
+ });
113
+
114
+ it('passes localTemplatesConfig into Templates for the SMS pane', () => {
115
+ const localConfig = {
116
+ useLocalTemplates: true,
117
+ localTemplates: [{ _id: '1', name: 'A' }],
118
+ localTemplatesLoading: false,
119
+ };
120
+ const p = cloneDeep(baseProps);
121
+ p.localTemplatesConfig = localConfig;
122
+ renderComponent(p);
123
+
124
+ expect(mockTemplates).toHaveBeenCalled();
125
+ const passed = mockTemplates.mock.calls.find(
126
+ (call) => call[0] && call[0].localTemplatesConfig && call[0].localTemplatesConfig.useLocalTemplates,
127
+ );
128
+ expect(passed).toBeTruthy();
129
+ expect(passed[0].localTemplatesConfig).toMatchObject(localConfig);
130
+ });
131
+ });
@@ -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.concurrent('handles fetching default WeChat templates successfully', () => {
14
+ it('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.concurrent('handles failure in fetching default WeChat templates', () => {
34
+ it('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.concurrent('handles creating a WeChat template successfully', () => {
51
+ test('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.concurrent('handles failure when creating a WeChat template', () => {
67
+ test('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.concurrent('handles fetching WeCrm accounts successfully', () => {
82
+ it('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.concurrent('handles failure in fetching WeCrm accounts', () => {
98
+ it('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.concurrent('handles fetching template details successfully', () => {
116
+ it('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.concurrent('handles failure in fetching template details', () => {
132
+ it('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.concurrent('should initialize all WeChat-related watcher sagas without error', () => {
151
+ it('should initialize all WeChat-related watcher sagas without error', () => {
152
152
  return expectSaga(v2WechatMapTemplatesSagas)
153
153
  .run();
154
154
  });
@@ -45,8 +45,6 @@ import {
45
45
  ACTION_TYPES,
46
46
  NOTIFICATION_TITLE_MAX_LENGTH,
47
47
  MESSAGE_MAX_LENGTH,
48
- EXTERNAL_URL,
49
- SITE_URL,
50
48
  } from '../constants';
51
49
  import * as actions from '../actions';
52
50
  import {
@@ -75,9 +73,6 @@ import {
75
73
  import './index.scss';
76
74
  import { WEBPUSH } from '../../CreativesContainer/constants';
77
75
  import { isAiContentBotDisabled } from '../../../utils/common';
78
- import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
79
- import creativesMessages from '../../CreativesContainer/messages';
80
- import CapButton from '@capillarytech/cap-ui-library/CapButton';
81
76
 
82
77
  // Memoized TagList wrapper components for better performance
83
78
  const MemoizedTagList = memo(({
@@ -187,7 +182,6 @@ const WebPushCreate = ({
187
182
  const [redirectUrlError, setRedirectUrlError] = useState('');
188
183
  const [activeUploadField, setActiveUploadField] = useState(null);
189
184
  const [templateIdError, setTemplateIdError] = useState('');
190
- const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
191
185
 
192
186
  // Refs
193
187
  const titleCountRef = useRef(null);
@@ -575,67 +569,6 @@ const WebPushCreate = ({
575
569
  return !(templateNameInvalid || titleValidation || messageValidation);
576
570
  };
577
571
 
578
- const getTemplateContent = useCallback(() => {
579
- let cta = null;
580
- if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.REDIRECT_TO_URL && redirectUrl) {
581
- cta = { type: EXTERNAL_URL, actionLink: redirectUrl.trim() };
582
- } else if (onClickBehaviour === ON_CLICK_BEHAVIOUR_OPTIONS.SITE_URL && websiteLink) {
583
- cta = { type: SITE_URL, actionLink: websiteLink };
584
- }
585
-
586
- const hasImage = mediaType === WEBPUSH_MEDIA_TYPES.IMAGE && imageSrc;
587
- const hasCtas = buttons && buttons.length > 0;
588
- let expandableDetails = null;
589
- if (hasImage || hasCtas) {
590
- expandableDetails = {
591
- media: hasImage ? [{ url: imageSrc, type: WEBPUSH_MEDIA_TYPES.IMAGE }] : [],
592
- ctas: hasCtas ? buttons.map((btn) => ({
593
- type: EXTERNAL_URL,
594
- action: '',
595
- title: btn.text || '',
596
- actionLink: btn.url || '',
597
- })) : [],
598
- };
599
- }
600
-
601
- const iconImageUrl = brandIconOption !== BRAND_ICON_OPTIONS.DONT_SHOW && brandIconSrc ? brandIconSrc : undefined;
602
-
603
- return {
604
- channel: WEBPUSH,
605
- accountId,
606
- content: {
607
- title: notificationTitle || '',
608
- message: message || '',
609
- ...(iconImageUrl ? { iconImageUrl } : {}),
610
- ...(cta ? { cta } : {}),
611
- ...(expandableDetails ? { expandableDetails } : {}),
612
- },
613
- messageSubject: templateName || notificationTitle || '',
614
- offers: [],
615
- };
616
- }, [
617
- notificationTitle,
618
- message,
619
- accountId,
620
- mediaType,
621
- imageSrc,
622
- brandIconOption,
623
- brandIconSrc,
624
- buttons,
625
- onClickBehaviour,
626
- redirectUrl,
627
- websiteLink,
628
- templateName,
629
- ]);
630
-
631
- const handleTestAndPreview = useCallback(() => {
632
- setShowTestAndPreviewSlidebox(true);
633
- }, []);
634
-
635
- const handleCloseTestAndPreview = useCallback(() => {
636
- setShowTestAndPreviewSlidebox(false);
637
- }, []);
638
-
639
572
  const isFormValid = () => {
640
573
  const templateNameInvalid = isFullMode && validateTemplateName(templateName);
641
574
  const titleValidation = validateTitle(notificationTitle);
@@ -1107,23 +1040,14 @@ const WebPushCreate = ({
1107
1040
  formatMessage={formatMessage}
1108
1041
  messages={messages}
1109
1042
  />
1110
- <CapRow className="webpush-test-preview-action">
1111
- <FormActions
1112
- onSave={handleSave}
1113
- isSaveDisabled={isSaveDisabled}
1114
- errorText={errorText}
1115
- accountErrorText={accountErrorText}
1116
- formatMessage={formatMessage}
1117
- messages={messages}
1118
- />
1119
- <CapButton
1120
- type="secondary"
1121
- onClick={handleTestAndPreview}
1122
- className="webpush-test-preview-btn"
1123
- >
1124
- {formatMessage(creativesMessages.testAndPreview)}
1125
- </CapButton>
1126
- </CapRow>
1043
+ <FormActions
1044
+ onSave={handleSave}
1045
+ isSaveDisabled={isSaveDisabled}
1046
+ errorText={errorText}
1047
+ accountErrorText={accountErrorText}
1048
+ formatMessage={formatMessage}
1049
+ messages={messages}
1050
+ />
1127
1051
  </CapColumn>
1128
1052
  <CapColumn className="preview-section" span={10}>
1129
1053
  <WebPushPreview
@@ -1135,13 +1059,6 @@ const WebPushCreate = ({
1135
1059
  buttons={buttons}
1136
1060
  />
1137
1061
  </CapColumn>
1138
- <TestAndPreviewSlidebox
1139
- show={showTestAndPreviewSlidebox}
1140
- onClose={handleCloseTestAndPreview}
1141
- content={getTemplateContent()}
1142
- currentChannel={WEBPUSH}
1143
- accountId={accountId || null}
1144
- />
1145
1062
  </CapRow>
1146
1063
  );
1147
1064
  };
@@ -135,12 +135,5 @@
135
135
  font-family: 'Roboto', open-sans, sans-serif;
136
136
  }
137
137
  }
138
-
139
- .webpush-test-preview-action {
140
- display: flex;
141
- .webpush-test-preview-btn {
142
- margin-left: $CAP_SPACE_12;
143
- }
144
- }
145
138
  }
146
139
 
@@ -119,6 +119,7 @@ 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';
122
123
  import { capitalizeString } from '../../utils/Formatter';
123
124
  import CapWhatsappCTA from '../../v2Components/CapWhatsappCTA';
124
125
  import {
@@ -496,28 +497,10 @@ export const Whatsapp = (props) => {
496
497
  );
497
498
  };
498
499
 
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
-
517
500
  const computeTextMessage = (msg, varMap, regex) => {
518
501
  const validVarArr = msg?.match(regex) || [];
519
502
  //conerting msg string to variable arr
520
- const templateHeaderArray = converStringToVarArr(validVarArr, msg);
503
+ const templateHeaderArray = splitContentByOrderedVarTokens(validVarArr, msg);
521
504
  if (templateHeaderArray?.length !== 0) {
522
505
  let clonedVarMap = {};
523
506
  if (!isEmpty(varMap)) {
@@ -571,7 +554,7 @@ export const Whatsapp = (props) => {
571
554
  setUnsubscribeRequired(true);
572
555
  }
573
556
  //converting msg string to variable arr
574
- const templateMessageArray = converStringToVarArr(validVarArr, msg);
557
+ const templateMessageArray = splitContentByOrderedVarTokens(validVarArr, msg);
575
558
  updateTempMsgArray(templateMessageArray.filter((i) => i === 0 || i));
576
559
  };
577
560