@capillarytech/creatives-library 9.0.14 → 9.0.15-alpha.1

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 (175) hide show
  1. package/constants/unified.js +32 -0
  2. package/global-styles.js +1 -1
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/common.js +8 -0
  6. package/utils/commonUtils.js +19 -1
  7. package/utils/rcsPayloadUtils.js +92 -0
  8. package/utils/templateVarUtils.js +201 -0
  9. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  10. package/utils/tests/templateVarUtils.test.js +204 -0
  11. package/v2Components/CapActionButton/constants.js +7 -0
  12. package/v2Components/CapActionButton/index.js +168 -109
  13. package/v2Components/CapActionButton/index.scss +156 -5
  14. package/v2Components/CapActionButton/messages.js +19 -3
  15. package/v2Components/CapActionButton/tests/index.test.js +42 -18
  16. package/v2Components/CapImageUpload/index.js +2 -2
  17. package/v2Components/CapTagList/index.js +10 -0
  18. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -48
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +60 -11
  25. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  26. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +201 -23
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +355 -76
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +155 -8
  29. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  30. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  31. package/v2Components/CommonTestAndPreview/index.js +761 -222
  32. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  33. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  34. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  35. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +1 -0
  36. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -267
  37. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  38. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  39. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  40. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  41. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  42. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  43. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  44. package/v2Components/CommonTestAndPreview/tests/index.test.js +133 -4
  45. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  46. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  47. package/v2Components/CommonTestAndPreview/utils.js +84 -0
  48. package/v2Components/FormBuilder/Classic.js +4487 -0
  49. package/v2Components/FormBuilder/Functional/FormBuilderShell.js +366 -0
  50. package/v2Components/FormBuilder/Functional/channels/registry.js +17 -0
  51. package/v2Components/FormBuilder/Functional/channels/sms/buildSubmitPayload.js +9 -0
  52. package/v2Components/FormBuilder/Functional/channels/sms/config.js +30 -0
  53. package/v2Components/FormBuilder/Functional/channels/sms/getEditorErrorDescriptor.js +46 -0
  54. package/v2Components/FormBuilder/Functional/channels/sms/getLiquidContent.js +13 -0
  55. package/v2Components/FormBuilder/Functional/channels/sms/index.js +22 -0
  56. package/v2Components/FormBuilder/Functional/channels/sms/tests/getEditorErrorDescriptor.test.js +52 -0
  57. package/v2Components/FormBuilder/Functional/channels/sms/tests/getLiquidContent.test.js +25 -0
  58. package/v2Components/FormBuilder/Functional/channels/sms/tests/validate.test.js +87 -0
  59. package/v2Components/FormBuilder/Functional/channels/sms/validate.js +89 -0
  60. package/v2Components/FormBuilder/Functional/constants.js +42 -0
  61. package/v2Components/FormBuilder/Functional/core/schema/fieldRegistry.js +38 -0
  62. package/v2Components/FormBuilder/Functional/core/schema/initializeFormState.js +85 -0
  63. package/v2Components/FormBuilder/Functional/core/store/formReducer.js +81 -0
  64. package/v2Components/FormBuilder/Functional/core/store/selectors.js +30 -0
  65. package/v2Components/FormBuilder/Functional/core/store/toLegacyFormData.js +91 -0
  66. package/v2Components/FormBuilder/Functional/index.js +26 -0
  67. package/v2Components/FormBuilder/Functional/layout/FieldSlot.js +59 -0
  68. package/v2Components/FormBuilder/Functional/layout/SchemaForm.js +31 -0
  69. package/v2Components/FormBuilder/Functional/layout/Section.js +116 -0
  70. package/v2Components/FormBuilder/Functional/renderers/smsRenderers.js +258 -0
  71. package/v2Components/FormBuilder/Functional/tests/channelRegistry.test.js +21 -0
  72. package/v2Components/FormBuilder/Functional/tests/fieldRegistry.test.js +65 -0
  73. package/v2Components/FormBuilder/Functional/tests/fieldSlot.test.js +97 -0
  74. package/v2Components/FormBuilder/Functional/tests/fixtures/smsParityCases.js +192 -0
  75. package/v2Components/FormBuilder/Functional/tests/formReducer.test.js +129 -0
  76. package/v2Components/FormBuilder/Functional/tests/initializeFormState.test.js +132 -0
  77. package/v2Components/FormBuilder/Functional/tests/schemaForm.test.js +40 -0
  78. package/v2Components/FormBuilder/Functional/tests/section.test.js +99 -0
  79. package/v2Components/FormBuilder/Functional/tests/selectors.test.js +67 -0
  80. package/v2Components/FormBuilder/Functional/tests/sms.crossFlowParity.test.js +155 -0
  81. package/v2Components/FormBuilder/Functional/tests/sms.liquid.test.js +172 -0
  82. package/v2Components/FormBuilder/Functional/tests/sms.rollout.test.js +122 -0
  83. package/v2Components/FormBuilder/Functional/tests/sms.shell.parity.test.js +329 -0
  84. package/v2Components/FormBuilder/Functional/tests/smsRenderers.test.js +160 -0
  85. package/v2Components/FormBuilder/Functional/tests/toLegacyFormData.test.js +95 -0
  86. package/v2Components/FormBuilder/_formBuilder.scss +5 -0
  87. package/v2Components/FormBuilder/index.js +41 -4479
  88. package/v2Components/FormBuilder/tests/__snapshots__/sms.characterization.test.js.snap +114 -0
  89. package/v2Components/FormBuilder/tests/entryGate.test.js +106 -0
  90. package/v2Components/FormBuilder/tests/sms.characterization.test.js +336 -0
  91. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  92. package/v2Components/SmsFallback/constants.js +94 -0
  93. package/v2Components/SmsFallback/index.js +958 -0
  94. package/v2Components/SmsFallback/index.scss +266 -0
  95. package/v2Components/SmsFallback/messages.js +78 -0
  96. package/v2Components/SmsFallback/smsFallbackUtils.js +120 -0
  97. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  98. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  99. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  100. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +208 -0
  101. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  102. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  103. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  104. package/v2Components/TemplatePreview/_templatePreview.scss +41 -22
  105. package/v2Components/TemplatePreview/constants.js +2 -0
  106. package/v2Components/TemplatePreview/index.js +143 -31
  107. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  108. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  109. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  110. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  111. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  112. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  113. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  114. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  115. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +4 -1
  116. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +4 -0
  117. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  118. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  119. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  120. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  121. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  122. package/v2Containers/CreativesContainer/constants.js +11 -0
  123. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  124. package/v2Containers/CreativesContainer/index.js +323 -104
  125. package/v2Containers/CreativesContainer/index.scss +83 -1
  126. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  127. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  128. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  129. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  130. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +333 -90
  131. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  132. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  133. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  134. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  135. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  136. package/v2Containers/Rcs/constants.js +131 -11
  137. package/v2Containers/Rcs/index.js +2602 -807
  138. package/v2Containers/Rcs/index.scss +289 -8
  139. package/v2Containers/Rcs/messages.js +34 -3
  140. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +227 -0
  141. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +73535 -38537
  142. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -8
  143. package/v2Containers/Rcs/tests/index.test.js +147 -36
  144. package/v2Containers/Rcs/tests/mockData.js +38 -0
  145. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  146. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  147. package/v2Containers/Rcs/utils.js +478 -11
  148. package/v2Containers/Sms/Create/index.js +115 -48
  149. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  150. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  151. package/v2Containers/SmsTrai/Create/index.js +9 -4
  152. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  153. package/v2Containers/SmsTrai/Edit/index.js +678 -169
  154. package/v2Containers/SmsTrai/Edit/index.scss +126 -0
  155. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  156. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +5615 -3014
  157. package/v2Containers/SmsWrapper/index.js +37 -8
  158. package/v2Containers/TagList/index.js +6 -0
  159. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  160. package/v2Containers/Templates/_templates.scss +171 -12
  161. package/v2Containers/Templates/actions.js +11 -0
  162. package/v2Containers/Templates/constants.js +2 -0
  163. package/v2Containers/Templates/index.js +125 -55
  164. package/v2Containers/Templates/sagas.js +57 -13
  165. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  166. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1060 -1015
  167. package/v2Containers/Templates/tests/sagas.test.js +199 -16
  168. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  169. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  170. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  171. package/v2Containers/TemplatesV2/index.js +86 -23
  172. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  173. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  174. package/v2Containers/Whatsapp/index.js +3 -20
  175. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -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';
@@ -41,13 +41,14 @@ import { makeSelectAuthenticated, selectCurrentOrgDetails } from "../../v2Contai
41
41
  import {
42
42
  CALL_TASK,
43
43
  COMMON_CHANNELS,
44
+ LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK,
44
45
  LOYALTY_SUPPORTED_ACTION,
45
46
  MOBILE_PUSH,
46
47
  NORMALIZED_CHANNEL_ALIASES,
47
48
  SMS,
48
49
  } from "../CreativesContainer/constants";
49
50
 
50
- const {CapCustomCardList} = CapCustomCard;
51
+ const { CapCustomCardList } = CapCustomCard;
51
52
 
52
53
  const StyledCapTab = withStyles(CapTab, CapTabStyle);
53
54
  export class TemplatesV2 extends React.Component { // eslint-disable-line react/prefer-stateless-function
@@ -124,9 +125,9 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
124
125
  return !normalizedChannelsToHideSet.has(paneKey);
125
126
  });
126
127
 
127
- if (isFullMode) {
128
- filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: 'assets' });
129
- } else {
128
+ if (isFullMode && !normalizedChannelsToHideSet.has(normalizeChannel(ASSETS))) {
129
+ filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.gallery), key: ASSETS });
130
+ } else if (!isFullMode) {
130
131
  // Add special-mode panes only when not hidden (use normalized checks)
131
132
  if (!normalizedChannelsToHideSet.has(CALL_TASK.toLowerCase())) {
132
133
  filteredPanes.push({ content: <div></div>, tab: intl.formatMessage(messages.callTask), key: CALL_TASK.toLowerCase() });
@@ -227,7 +228,8 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
227
228
  this.setState({selectedChannel: nextProps.channel, panes });
228
229
  }
229
230
  }
230
- getTemplateDataForGrid = ({templates, handlers, filterContent, channel, isLoading, loadingTip}) => {
231
+
232
+ getTemplateDataForGrid = ({ templates, handlers, filterContent, channel, isLoading, loadingTip }) => {
231
233
  const currentChannel = channel.toUpperCase();
232
234
  const cardDataList = templates.map((template) => {
233
235
  const templateData =
@@ -253,7 +255,8 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
253
255
  </CapSpin>
254
256
 
255
257
  </div>);
256
- }
258
+ };
259
+
257
260
  getGalleryComponent = (location) => <Gallery location={location} isFullMode={this.props.isFullMode}/>
258
261
  getCallTaskComponent = () => (
259
262
  <CallTask
@@ -317,6 +320,29 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
317
320
  if (messageStrategy !== "X_ENGAGE" && channel === 'facebook' && !isFullMode) {
318
321
  return this.getFacebookComponent();
319
322
  }
323
+ const localConfig = this.props.localTemplatesConfig || pick(this.props, LOCAL_TEMPLATE_CONFIG_KEYS_FOR_PICK);
324
+ const useLocalTemplates = localConfig.useLocalTemplates;
325
+ if (useLocalTemplates && channel === (this.props.channel || 'sms')) {
326
+ // Reuse full Templates component (same UI as Redux flow) with local data only
327
+ const location = { pathname: `/${channel}`, search: '', query: !this.props.isFullMode ? { type: 'embedded', module: 'library' } : {} };
328
+ return (
329
+ <Templates
330
+ key={`${channel}-local`}
331
+ location={location}
332
+ route={{ name: channel }}
333
+ router={this.props.router}
334
+ isFullMode={this.props.isFullMode}
335
+ createNew={this.props.createNew}
336
+ onSelectTemplate={this.props.onSelectTemplate}
337
+ handlePeviewTemplate={this.props.handlePeviewTemplate}
338
+ messageStrategy={this.props.messageStrategy}
339
+ smsRegister={this.props.smsRegister}
340
+ hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
341
+ localTemplatesConfig={localConfig}
342
+ />
343
+ );
344
+ }
345
+
320
346
  const location = {pathname: `/${channel}`, search: '', query};
321
347
  switch (channel) {
322
348
  case 'call_task':
@@ -366,29 +392,55 @@ export class TemplatesV2 extends React.Component { // eslint-disable-line react/
366
392
  }
367
393
  render() {
368
394
  const { isFullMode, className, cap = {}, Global = {}} = this.props;
395
+ const useLocalTemplates = get(this.props, 'localTemplatesConfig.useLocalTemplates', false);
369
396
  const { accessiblePermissions = []} = cap.user || Global.user || {};
370
397
  let isCreativeAccessible = true;
371
398
  if (!accessiblePermissions.includes(CREATIVES_UI_VIEW)) {
372
399
  isCreativeAccessible = false;
373
400
  }
401
+ // Recompute active pane content every render so local-list mode updates
402
+ // (templates/loading/search UI) are not stuck with the initial cached pane.
403
+ const panes = this.setChannelContent(this.state.selectedChannel, this.state.panes);
404
+ const hideChannelTabsForLocalSms = useLocalTemplates && panes.length === 1;
405
+ const activeLocalPane = hideChannelTabsForLocalSms
406
+ ? (panes.find(
407
+ (p) => String(p.key).toLowerCase() === String(this.state.selectedChannel).toLowerCase(),
408
+ ) || panes[0])
409
+ : null;
374
410
  return (
375
411
  !isCreativeAccessible ? <AccessForbidden /> : (
376
- <div className={`${className} creatives-templates-container ${isFullMode ? 'fullmode' : 'library-mode'}`} data-testid="cap-wrapper">
377
- {isFullMode && <Helmet
378
- title={this.props.intl.formatMessage(messages.creatives)}
379
- meta={[
380
- { name: 'description', content: this.props.intl.formatMessage(messages.creativesDesc) },
381
- ]}
382
- />}
383
- <div className="component-wrapper">
384
- {isFullMode && <CapHeader title={<FormattedMessage {...messages.creatives}/>} description={<FormattedMessage {...messages.creativesDesc}/>}/>}
385
- <StyledCapTab
386
- panes={this.state.panes}
387
- onChange={this.channelChange}
388
- activeKey={this.state.selectedChannel}
389
- defaultActiveKey={this.state.selectedChannel}
390
- isFullMode={isFullMode}
412
+ <div
413
+ className={`${className} creatives-templates-container ${isFullMode ? 'fullmode' : 'library-mode'}${useLocalTemplates ? ' creatives-templates-container--local-sms' : ''}`}
414
+ data-testid="cap-wrapper"
415
+ >
416
+ {isFullMode && !useLocalTemplates && (
417
+ <Helmet
418
+ title={this.props.intl.formatMessage(messages.creatives)}
419
+ meta={[
420
+ { name: 'description', content: this.props.intl.formatMessage(messages.creativesDesc) },
421
+ ]}
391
422
  />
423
+ )}
424
+ <div className="component-wrapper">
425
+ {isFullMode && (
426
+ <CapHeader
427
+ title={<FormattedMessage {...messages.creatives} />}
428
+ {...(!useLocalTemplates && {
429
+ description: <FormattedMessage {...messages.creativesDesc} />,
430
+ })}
431
+ />
432
+ )}
433
+ {hideChannelTabsForLocalSms ? (
434
+ <div className="templates-v2-local-sms-pane">{activeLocalPane?.content}</div>
435
+ ) : (
436
+ <StyledCapTab
437
+ panes={panes}
438
+ onChange={this.channelChange}
439
+ activeKey={this.state.selectedChannel}
440
+ defaultActiveKey={this.state.selectedChannel}
441
+ isFullMode={isFullMode}
442
+ />
443
+ )}
392
444
  </div>
393
445
  </div>
394
446
  )
@@ -420,6 +472,17 @@ TemplatesV2.propTypes = {
420
472
  currentOrgDetails: PropTypes.object,
421
473
  restrictPersonalization: PropTypes.bool,
422
474
  isAnonymousType: PropTypes.bool,
475
+ // Optional: reuse grid UI with local template list (e.g. SMS fallback). Pass object or same keys as individual props.
476
+ localTemplatesConfig: PropTypes.shape({
477
+ useLocalTemplates: PropTypes.bool,
478
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
479
+ localTemplatesLoading: PropTypes.bool,
480
+ localTemplatesLoadingTip: PropTypes.string,
481
+ localTemplatesFilterContent: PropTypes.node,
482
+ localTemplatesFooterContent: PropTypes.node,
483
+ localTemplatesOnPageChange: PropTypes.func,
484
+ localTemplatesUseSkeleton: PropTypes.bool,
485
+ }),
423
486
  };
424
487
 
425
488
  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
  });
@@ -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