@capillarytech/creatives-library 8.0.319 → 8.0.321

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 (139) hide show
  1. package/constants/unified.js +14 -0
  2. package/package.json +1 -1
  3. package/utils/templateVarUtils.js +172 -0
  4. package/utils/tests/tagValidations.test.js +34 -0
  5. package/utils/tests/templateVarUtils.test.js +160 -0
  6. package/v2Components/CapTagList/index.js +25 -22
  7. package/v2Components/CapTagList/style.scss +48 -0
  8. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  9. package/v2Components/CapTagListWithInput/index.js +4 -0
  10. package/v2Components/CapWhatsappCTA/index.js +2 -0
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  21. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  22. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  23. package/v2Components/CommonTestAndPreview/index.js +693 -155
  24. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  25. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  26. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  31. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  32. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  33. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  34. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  35. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  36. package/v2Components/FormBuilder/index.js +14 -1
  37. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  38. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  39. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  40. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  41. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  42. package/v2Components/SmsFallback/constants.js +73 -0
  43. package/v2Components/SmsFallback/index.js +956 -0
  44. package/v2Components/SmsFallback/index.scss +265 -0
  45. package/v2Components/SmsFallback/messages.js +78 -0
  46. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  47. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  48. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  49. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  50. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  51. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  52. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  53. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  54. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  55. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  56. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  57. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  58. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  59. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  60. package/v2Containers/BeeEditor/index.js +3 -0
  61. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  62. package/v2Containers/CreativesContainer/SlideBoxContent.js +64 -5
  63. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  64. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  65. package/v2Containers/CreativesContainer/constants.js +9 -0
  66. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  67. package/v2Containers/CreativesContainer/index.js +292 -99
  68. package/v2Containers/CreativesContainer/index.scss +51 -1
  69. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  70. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  71. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  72. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  73. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  74. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  75. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  76. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  77. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  78. package/v2Containers/Email/index.js +1 -0
  79. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
  80. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  81. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  82. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  83. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
  84. package/v2Containers/EmailWrapper/index.js +4 -0
  85. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  86. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  87. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  88. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  89. package/v2Containers/InAppWrapper/index.js +3 -0
  90. package/v2Containers/MobilePush/Create/index.js +2 -0
  91. package/v2Containers/MobilePush/Edit/index.js +2 -0
  92. package/v2Containers/MobilepushWrapper/index.js +3 -1
  93. package/v2Containers/Rcs/constants.js +32 -1
  94. package/v2Containers/Rcs/index.js +951 -873
  95. package/v2Containers/Rcs/index.scss +85 -6
  96. package/v2Containers/Rcs/messages.js +10 -1
  97. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  98. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  99. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  100. package/v2Containers/Rcs/tests/index.test.js +41 -38
  101. package/v2Containers/Rcs/tests/mockData.js +38 -0
  102. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  103. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  104. package/v2Containers/Rcs/utils.js +358 -10
  105. package/v2Containers/Sms/Create/index.js +83 -36
  106. package/v2Containers/Sms/Edit/index.js +2 -0
  107. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  108. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  109. package/v2Containers/SmsTrai/Create/index.js +9 -4
  110. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  111. package/v2Containers/SmsTrai/Edit/index.js +611 -128
  112. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  113. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  114. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  115. package/v2Containers/SmsWrapper/index.js +39 -8
  116. package/v2Containers/TagList/index.js +47 -2
  117. package/v2Containers/TagList/messages.js +4 -0
  118. package/v2Containers/TagList/tests/TagList.test.js +122 -20
  119. package/v2Containers/TagList/tests/mockdata.js +17 -0
  120. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  121. package/v2Containers/Templates/_templates.scss +61 -2
  122. package/v2Containers/Templates/actions.js +11 -0
  123. package/v2Containers/Templates/constants.js +2 -0
  124. package/v2Containers/Templates/index.js +90 -40
  125. package/v2Containers/Templates/sagas.js +57 -12
  126. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  127. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  128. package/v2Containers/Templates/tests/sagas.test.js +193 -12
  129. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  130. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  131. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  132. package/v2Containers/TemplatesV2/index.js +86 -23
  133. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  134. package/v2Containers/Viber/index.js +5 -0
  135. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  136. package/v2Containers/WebPush/Create/index.js +9 -1
  137. package/v2Containers/Whatsapp/index.js +8 -20
  138. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +598 -34
  139. package/v2Containers/Zalo/index.js +2 -0
@@ -468,7 +468,13 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
468
468
  if (this.props.location.query.type === 'embedded') {
469
469
  this.props.actions.resetAccount();
470
470
  }
471
- if (['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill'].includes((this.state.channel || '').toLowerCase())) {
471
+ // When using local templates (e.g. SMS fallback selector), do not fetch from API or we overwrite global store and break background RCS list
472
+ const useLocalTemplates = get(
473
+ this.props,
474
+ 'localTemplatesConfig.useLocalTemplates',
475
+ get(this.props, 'useLocalTemplates', false),
476
+ );
477
+ if (!useLocalTemplates && ['line', VIBER_CHANNEL, FACEBOOK_CHANNEL, 'sms', 'email', 'ebill'].includes((this.state.channel || '').toLowerCase())) {
472
478
  const queryParams = {
473
479
  // name: this.state.searchText,
474
480
  // sortBy: this.state.sortBy,
@@ -981,8 +987,16 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
981
987
 
982
988
  componentWillUnmount() {
983
989
  window.removeEventListener("message", this.handleFrameTasks);
984
- this.props.actions.resetTemplateStoreData();
985
- this.props.globalActions.clearMetaEntities();
990
+ // When using local templates (e.g. SMS fallback selector), do not clear global store or background RCS list is wiped
991
+ const useLocalTemplates = get(
992
+ this.props,
993
+ 'localTemplatesConfig.useLocalTemplates',
994
+ get(this.props, 'useLocalTemplates', false),
995
+ );
996
+ if (!useLocalTemplates) {
997
+ this.props.actions.resetTemplateStoreData();
998
+ this.props.globalActions.clearMetaEntities();
999
+ }
986
1000
  // Clear any pending timeouts to prevent memory leaks
987
1001
  if (this._clearEditTimeout) {
988
1002
  clearTimeout(this._clearEditTimeout);
@@ -1947,7 +1961,7 @@ export class Templates extends React.Component { // eslint-disable-line react/pr
1947
1961
  style={{ marginRight: "16px" }}
1948
1962
  type="eye"
1949
1963
  onClick={() => {
1950
- if (!this.props.isFullMode || this.props.isDltFromRcs) {
1964
+ if (!this.props.isFullMode || this.props.isDltFromRcs || this.props.isSmsFallbackFromRcs) {
1951
1965
  if (!get(template, "versions.base.content.zalo.previewUrl", "")) {
1952
1966
  this.setState({ zaloPreviewItemId: template?._id });
1953
1967
  }
@@ -3282,6 +3296,13 @@ return (<div>
3282
3296
  this.setState({modeType});
3283
3297
  }
3284
3298
  const { _id: id } = template;
3299
+ const {
3300
+ localTemplatesConfig,
3301
+ fbAdManager,
3302
+ isDltFromRcs,
3303
+ isSmsFallbackFromRcs,
3304
+ onSelectTemplate,
3305
+ } = this.props;
3285
3306
  const type = this.props.location.query.type;
3286
3307
  const module = this.props.location.query.module;
3287
3308
  const isLanguageSupport = (this.props.location.query.isLanguageSupport) ? this.props.location.query.isLanguageSupport : false;
@@ -3367,10 +3388,12 @@ return (<div>
3367
3388
  }
3368
3389
  if (this.isEnabledInLibraryModule("callSelectFromProps")) {
3369
3390
  let data = id;
3370
- if (this.props.fbAdManager || this.props.isDltFromRcs) {
3391
+ if (localTemplatesConfig?.useLocalTemplates) {
3392
+ data = template;
3393
+ } else if (fbAdManager || isDltFromRcs || isSmsFallbackFromRcs) {
3371
3394
  data = this.selectTemplate(id);
3372
3395
  }
3373
- this.props.onSelectTemplate(data, this.props.fbAdManager);
3396
+ onSelectTemplate(data, fbAdManager);
3374
3397
  } else {
3375
3398
  timeTracker.startTimer(CHANNEL_EDIT_TRACK_MAPPING[this.state.channel.toLowerCase()]);
3376
3399
  if (this.state.channel.toLowerCase() === 'ebill') {
@@ -4214,17 +4237,23 @@ return (<div>
4214
4237
  if (([WHATSAPP_LOWERCASE, ZALO_LOWERCASE, RCS_LOWERCASE].includes(this.state?.channel?.toLocaleLowerCase()) && isEmpty(this.state?.hostName))) {
4215
4238
  isfilterContentVisisble = false;
4216
4239
  }
4217
- const filterContent = (( isfilterContentVisisble || [WECHAT, MOBILE_PUSH, INAPP].includes(this.state.channel.toUpperCase())) && <div className="action-container">
4218
- {isfilterContentVisisble && <CapInput.Search
4219
- className="search-text"
4220
- style={{width: '210px'}}
4221
- placeholder={this.props.intl.formatMessage(messages.searchText)}
4222
- value={this.state.searchText}
4223
- onChange={(e) => this.searchTemplate(e.target.value, this.state.channel)}
4224
- disabled={this.checkSearchDisabled()}
4225
- onClear={() => this.searchTemplate('', this.state.channel)}
4226
- onScroll={(e) => e.stopPropagation()}
4227
- />}
4240
+
4241
+ const useLocalTemplates = this.props.localTemplatesConfig?.useLocalTemplates;
4242
+ const builtFilterContent = ((isfilterContentVisisble || [WECHAT, MOBILE_PUSH, INAPP].includes(this.state.channel.toUpperCase())) && (
4243
+ <div className="action-container">
4244
+ <div className="action-container__toolbar-row">
4245
+ {isfilterContentVisisble ? (
4246
+ <CapInput.Search
4247
+ className="search-text"
4248
+ placeholder={this.props.intl.formatMessage(messages.searchText)}
4249
+ value={this.state.searchText}
4250
+ onChange={(e) => this.searchTemplate(e.target.value, this.state.channel)}
4251
+ onSearch={() => this.searchTemplate(this.state.searchText, this.state.channel)}
4252
+ onClear={() => this.searchTemplate('', this.state.channel)}
4253
+ onScroll={(e) => e.stopPropagation()}
4254
+ disabled={this.checkSearchDisabled()}
4255
+ />
4256
+ ) : null}
4228
4257
  {
4229
4258
  channel.toUpperCase() === WECHAT && <CapRadio.CapRadioGroup className="wechat-filters" defaultValue={wechatFilter} onChange={this.setWechatFilter}>
4230
4259
  <CapRadio.Button value={WECHAT_FILTERS.ALL}><CapLabel type="label2">
@@ -4370,20 +4399,27 @@ return (<div>
4370
4399
  </div>
4371
4400
  )
4372
4401
  }
4373
- <div style={{display: "flex", justifyContent: "space-between", alignItems: 'center'}}>
4374
- {
4375
- this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE && (isWhatsappCountExeeded)? (
4376
- <CapTooltip title={whatsappCountExceedText}>
4377
- <div className="button-disabled-tooltip-wrapper">
4378
- {createButton}
4379
- </div>
4380
- </CapTooltip>
4381
- )
4382
- : isfilterContentVisisble && !isWechatEmbedded && !this.props.isDltFromRcs && createButton
4383
- }
4402
+ </div>
4403
+ <div>
4404
+ <div className="action-container__create-row">
4405
+ {
4406
+ this.state?.channel?.toLowerCase() === WHATSAPP_LOWERCASE && (isWhatsappCountExeeded) ? (
4407
+ <CapTooltip title={whatsappCountExceedText}>
4408
+ <div className="button-disabled-tooltip-wrapper">
4409
+ {createButton}
4410
+ </div>
4411
+ </CapTooltip>
4412
+ )
4413
+ : isfilterContentVisisble && !isWechatEmbedded && !this.props.isDltFromRcs && !this.props.isSmsFallbackFromRcs && createButton
4414
+ }
4415
+ </div>
4416
+ </div>
4384
4417
  </div>
4385
-
4386
- </div>);
4418
+ ));
4419
+ const localTemplatesFilterContent = get(this.props, 'localTemplatesConfig.localTemplatesFilterContent', null);
4420
+ const filterContent = (useLocalTemplates && localTemplatesFilterContent) != null
4421
+ ? localTemplatesFilterContent
4422
+ : builtFilterContent;
4387
4423
  let htmlPreviewContent = "";
4388
4424
  if (this.state.channel.toLowerCase() === 'ebill') {
4389
4425
  htmlPreviewContent = this.state.previewTemplate && this.state.previewTemplate.versions && this.state.previewTemplate.versions.base && this.state.previewTemplate.versions.base['ebill-editor'];
@@ -4393,7 +4429,10 @@ return (<div>
4393
4429
 
4394
4430
 
4395
4431
  const creativesParams = this.getCreativesParams();
4396
- const templates = this.props.TemplatesList || [];
4432
+ const templates = useLocalTemplates
4433
+ ? (this.props.localTemplatesConfig?.localTemplates || [])
4434
+ : (this.props.TemplatesList || []);
4435
+ const isLoadingWhenLocal = useLocalTemplates && !!this.props.localTemplatesConfig?.localTemplatesLoading;
4397
4436
  const {route} = this.props;
4398
4437
  const loadingTipMap = {
4399
4438
  sendingFile: 'uploadingFile',
@@ -4408,9 +4447,11 @@ return (<div>
4408
4447
  (deleteRcsTemplateInProgress && 'deletingTemplate') ||
4409
4448
  (this.props.EmailCreate.duplicateTemplateInProgress && 'duplicatingTemplate');
4410
4449
 
4411
- const loadingTip = messages[loadingTipIntl] ? this.props.intl.formatMessage(messages[loadingTipIntl]) : this.props.intl.formatMessage(messages.gettingAllTemplates);
4450
+ const loadingTip = useLocalTemplates && this.props.localTemplatesConfig?.localTemplatesLoadingTip
4451
+ ? this.props.localTemplatesConfig.localTemplatesLoadingTip
4452
+ : (messages[loadingTipIntl] ? this.props.intl.formatMessage(messages[loadingTipIntl]) : this.props.intl.formatMessage(messages.gettingAllTemplates));
4412
4453
  const showNoTemplatesFoundZalo = this.state.channel.toUpperCase() === ZALO && isEmpty(this.state.searchedZaloTemplates) && this.state.searchingZaloTemplate;
4413
- const showNoTemplatesFoundOther = ![ZALO].includes(this.state.channel.toUpperCase()) && isEmpty(this.props.TemplatesList) && !this.props.Templates.getAllTemplatesInProgress && !isEmpty(this.state.searchText);
4454
+ const showNoTemplatesFoundOther = ![ZALO].includes(this.state.channel.toUpperCase()) && isEmpty(templates) && (useLocalTemplates ? !isLoadingWhenLocal : !this.props.Templates.getAllTemplatesInProgress) && (useLocalTemplates || !isEmpty(this.state.searchText));
4414
4455
  const showNoTemplatesFound = showNoTemplatesFoundZalo || showNoTemplatesFoundOther;
4415
4456
 
4416
4457
  return (
@@ -4455,22 +4496,22 @@ return (<div>
4455
4496
  ) : null}
4456
4497
  <CapRow>
4457
4498
  <Pagination
4458
- templateInProgress={
4459
- this.props.Templates.getAllTemplatesInProgress
4460
- }
4499
+ templateInProgress={useLocalTemplates ? isLoadingWhenLocal : this.props.Templates.getAllTemplatesInProgress}
4461
4500
  onPageChange={
4462
- templates.length ? this.onPaginationChange : () => {}
4501
+ templates.length
4502
+ ? (useLocalTemplates ? (this.props.localTemplatesConfig?.localTemplatesOnPageChange || (() => {})) : this.onPaginationChange)
4503
+ : () => {}
4463
4504
  }
4464
4505
  >
4465
4506
  {this.getTemplateDataForGrid({
4466
4507
  previewTemplateId: this.state.zaloPreviewItemId,
4467
- isLoading,
4468
- isInitialLoading,
4508
+ isLoading: useLocalTemplates ? isLoadingWhenLocal : isLoading,
4509
+ isInitialLoading: useLocalTemplates ? isLoadingWhenLocal && templates.length === 0 : isInitialLoading,
4469
4510
  loadingTip,
4470
4511
  channel: this.state.channel,
4471
4512
  templates: this.state.searchingZaloTemplate
4472
4513
  ? this.state.searchedZaloTemplates
4473
- : this.props.TemplatesList,
4514
+ : templates,
4474
4515
  filterContent,
4475
4516
  handlers: {
4476
4517
  handlePreviewClick: this.handlePreviewClick,
@@ -4653,6 +4694,15 @@ Templates.propTypes = {
4653
4694
  WebPush: PropTypes.object,
4654
4695
  smsRegister: PropTypes.any,
4655
4696
  isDltFromRcs: PropTypes.bool,
4697
+ isSmsFallbackFromRcs: PropTypes.bool,
4698
+ localTemplatesConfig: PropTypes.shape({
4699
+ useLocalTemplates: PropTypes.bool,
4700
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
4701
+ localTemplatesLoading: PropTypes.bool,
4702
+ localTemplatesLoadingTip: PropTypes.string,
4703
+ localTemplatesFilterContent: PropTypes.node,
4704
+ localTemplatesOnPageChange: PropTypes.func,
4705
+ }),
4656
4706
  };
4657
4707
 
4658
4708
  const mapStateToProps = createStructuredSelector({
@@ -1,5 +1,5 @@
1
1
  import {
2
- call, put, takeLatest, all,
2
+ call, put, takeLatest, takeEvery, all,
3
3
  } from 'redux-saga/effects';
4
4
  import get from 'lodash/get';
5
5
  // import { schema, normalize } from 'normalizr';
@@ -7,31 +7,69 @@ import * as Api from '../../services/api';
7
7
  import * as types from './constants';
8
8
  import { saveCdnConfigs, removeAllCdnLocalStorageItems } from '../../utils/cdnTransformation';
9
9
  import { COPY_OF } from '../../constants/unified';
10
+ import { fetchSmsTemplatesFromQuery } from './utils/smsTemplatesListApi';
10
11
  import { ZALO_TEMPLATE_INFO_REQUEST } from '../Zalo/constants';
11
12
  import { getTemplateInfoById } from '../Zalo/saga';
12
- // Individual exports for testing
13
- export function* getAllTemplates(channel, queryParams) {
13
+
14
+ export function* getLocalSmsTemplates(action) {
14
15
  try {
15
- const result = yield call(Api.getAllTemplates, channel, queryParams);
16
- const channelTemplates = (channel.channel === 'wechat') ? { templates: [...result.response.mapped, ...result.response.richmedia] } : result.response;
17
- // const sidebar = result.response.sidebar;
18
- if (channel.channel === 'wechat' && channel.queryParams && channel.queryParams.sortBy && channel.queryParams.sortBy.toLocaleLowerCase() === ("Most Recent").toLocaleLowerCase()) {
16
+ const fetched = yield call(
17
+ fetchSmsTemplatesFromQuery,
18
+ action.queryParams,
19
+ action.intlCopyOf,
20
+ );
21
+ if (typeof action.onSuccess === 'function') {
22
+ yield call(action.onSuccess, fetched);
23
+ }
24
+ } catch (error) {
25
+ if (typeof action.onFailure === 'function') {
26
+ yield call(action.onFailure, error);
27
+ }
28
+ }
29
+ }
30
+
31
+ export function* getAllTemplates(action) {
32
+ try {
33
+ if (action.channel && String(action.channel).toLowerCase() === 'sms') {
34
+ const fetched = yield call(
35
+ fetchSmsTemplatesFromQuery,
36
+ action.queryParams,
37
+ action.intlCopyOf,
38
+ );
39
+ yield put({
40
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
41
+ data: fetched.channelTemplates,
42
+ weCRMTemplate: fetched.weCRMTemplate,
43
+ isReset: get(action, 'queryParams.page') === 1,
44
+ });
45
+ return;
46
+ }
47
+
48
+ const result = yield call(Api.getAllTemplates, action);
49
+ const channelTemplates = (action.channel === 'wechat')
50
+ ? { templates: [...result.response.mapped, ...result.response.richmedia] }
51
+ : result.response;
52
+ if (action.channel === 'wechat' && action.queryParams && action.queryParams.sortBy && action.queryParams.sortBy.toLocaleLowerCase() === ("Most Recent").toLocaleLowerCase()) {
19
53
  channelTemplates.templates.sort((a, b) => {
20
54
  const dateA = new Date(a.updatedAt);
21
55
  const dateB = new Date(b.updatedAt);
22
56
  return dateB - dateA;
23
57
  });
24
- } else if (channel.channel === 'wechat' && channel.queryParams && channel.queryParams.sortBy && channel.queryParams.sortBy.toLocaleLowerCase() === ("Alphabetically").toLocaleLowerCase()) {
58
+ } else if (action.channel === 'wechat' && action.queryParams && action.queryParams.sortBy && action.queryParams.sortBy.toLocaleLowerCase() === ("Alphabetically").toLocaleLowerCase()) {
25
59
  channelTemplates.templates.sort((a, b) => b.name - a.name);
26
60
  }
27
- // Update the "name" property in each template
28
- if (channel.intlCopyOf && channelTemplates?.templates) {
61
+ if (action.intlCopyOf && channelTemplates?.templates) {
29
62
  channelTemplates.templates = channelTemplates.templates.map((template) => ({
30
63
  ...template,
31
- name: template.name.replace(new RegExp(COPY_OF, 'g'), channel.intlCopyOf),
64
+ name: template.name.replace(new RegExp(COPY_OF, 'g'), action.intlCopyOf),
32
65
  }));
33
66
  }
34
- yield put({ type: types.GET_ALL_TEMPLATES_SUCCESS, data: channelTemplates, weCRMTemplate: result.response.unMapped, isReset: channel.queryParams.page === 1 });
67
+ yield put({
68
+ type: types.GET_ALL_TEMPLATES_SUCCESS,
69
+ data: channelTemplates,
70
+ weCRMTemplate: result.response.unMapped,
71
+ isReset: get(action, 'queryParams.page') === 1,
72
+ });
35
73
  } catch (error) {
36
74
  yield put({ type: types.GET_ALL_TEMPLATES_FAILURE, error });
37
75
  }
@@ -205,6 +243,11 @@ export function* watchGetAllTemplates() {
205
243
  yield takeLatest(types.GET_ALL_TEMPLATES_REQUEST, getAllTemplates);
206
244
  }
207
245
 
246
+
247
+ export function* watchGetLocalSmsTemplates() {
248
+ yield takeEvery(types.GET_LOCAL_SMS_TEMPLATES_REQUEST, getLocalSmsTemplates);
249
+ }
250
+
208
251
  export function* watchDeleteTemplate() {
209
252
  yield takeLatest(types.DELETE_TEMPLATE_REQUEST, deleteTemplate);
210
253
  }
@@ -258,6 +301,7 @@ export function* watchForGetTemplateInfoById() {
258
301
  // All sagas to be loaded
259
302
  export default [
260
303
  watchGetAllTemplates,
304
+ watchGetLocalSmsTemplates,
261
305
  watchDeleteTemplate,
262
306
  watchDeleteRcsTemplate,
263
307
  watchGetUserList,
@@ -274,6 +318,7 @@ export default [
274
318
  export function* v2TemplateSaga() {
275
319
  yield all([
276
320
  watchGetAllTemplates(),
321
+ watchGetLocalSmsTemplates(),
277
322
  watchDeleteTemplate(),
278
323
  watchDeleteRcsTemplate(),
279
324
  watchGetUserList(),
@@ -0,0 +1,120 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import React from 'react';
5
+ import { render, screen, fireEvent } from '@testing-library/react';
6
+ import '@testing-library/jest-dom';
7
+ import TemplatesActionBar from '../TemplatesActionBar';
8
+
9
+ jest.mock('@capillarytech/cap-ui-library/CapInput', () => {
10
+ const React = require('react');
11
+ function Search(props) {
12
+ return React.createElement('input', {
13
+ 'data-testid': 'cap-input-search',
14
+ value: props?.value,
15
+ placeholder: props?.placeholder,
16
+ onChange: props?.onChange,
17
+ onKeyDown: (e) => {
18
+ if (e.key === 'Enter' && props.onPressEnter) {
19
+ props.onPressEnter(e);
20
+ }
21
+ },
22
+ });
23
+ }
24
+ function CapInput() {
25
+ return null;
26
+ }
27
+ CapInput.Search = Search;
28
+ return { __esModule: true, default: CapInput };
29
+ });
30
+
31
+ jest.mock('@capillarytech/cap-ui-library/CapButton', () => {
32
+ const React = require('react');
33
+ return function CapButton(props) {
34
+ return React.createElement('button', {
35
+ type: 'button',
36
+ 'data-testid': 'cta',
37
+ onClick: props?.onClick,
38
+ disabled: props?.disabled,
39
+ }, props?.children);
40
+ };
41
+ });
42
+
43
+ describe('TemplatesActionBar', () => {
44
+ it('renders search when searchPlaceholder is set', () => {
45
+ render(
46
+ <TemplatesActionBar
47
+ searchPlaceholder="Find templates"
48
+ searchValue="hi"
49
+ ctaLabel="Create"
50
+ />,
51
+ );
52
+ expect(screen.getByTestId('cap-input-search')).toHaveAttribute('placeholder', 'Find templates');
53
+ });
54
+
55
+ it('omits search when searchPlaceholder is empty', () => {
56
+ const { container } = render(
57
+ <TemplatesActionBar searchPlaceholder="" ctaLabel="Go" />,
58
+ );
59
+ expect(container.querySelector('[data-testid="cap-input-search"]')).toBeNull();
60
+ });
61
+
62
+ it('fires onSearchChange and onCtaClick', () => {
63
+ const onSearchChange = jest.fn();
64
+ const onCtaClick = jest.fn();
65
+ render(
66
+ <TemplatesActionBar
67
+ searchPlaceholder="S"
68
+ onSearchChange={onSearchChange}
69
+ ctaLabel="New"
70
+ onCtaClick={onCtaClick}
71
+ />,
72
+ );
73
+ fireEvent.change(screen.getByTestId('cap-input-search'), { target: { value: 'x' } });
74
+ fireEvent.click(screen.getByTestId('cta'));
75
+ expect(onSearchChange).toHaveBeenCalled();
76
+ expect(onCtaClick).toHaveBeenCalled();
77
+ });
78
+
79
+ it('fires onSearch when Enter is pressed (antd Input has no native onSearch)', () => {
80
+ const onSearch = jest.fn();
81
+ render(
82
+ <TemplatesActionBar
83
+ searchPlaceholder="S"
84
+ searchValue="query"
85
+ onSearch={onSearch}
86
+ ctaLabel="New"
87
+ />,
88
+ );
89
+ const input = screen.getByTestId('cap-input-search');
90
+ fireEvent.keyDown(input, { key: 'Enter', code: 'Enter' });
91
+ expect(onSearch).toHaveBeenCalledWith('query');
92
+ });
93
+
94
+ it('renders ctaNode instead of default button when provided', () => {
95
+ render(
96
+ <TemplatesActionBar
97
+ searchPlaceholder="S"
98
+ ctaNode={<span data-testid="custom-cta">Custom</span>}
99
+ />,
100
+ );
101
+ expect(screen.getByTestId('custom-cta')).toBeInTheDocument();
102
+ expect(screen.queryByTestId('cta')).toBeNull();
103
+ });
104
+
105
+ it('hides CTA area when showCta is false', () => {
106
+ const { container } = render(
107
+ <TemplatesActionBar searchPlaceholder="S" showCta={false} ctaLabel="X" />,
108
+ );
109
+ expect(container.querySelector('[data-testid="cta"]')).toBeNull();
110
+ });
111
+
112
+ it('renders children in toolbar row', () => {
113
+ render(
114
+ <TemplatesActionBar searchPlaceholder="S" ctaLabel="C">
115
+ <span data-testid="child">extra</span>
116
+ </TemplatesActionBar>,
117
+ );
118
+ expect(screen.getByTestId('child')).toBeInTheDocument();
119
+ });
120
+ });