@capillarytech/creatives-library 8.0.318 → 8.0.320

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 +15 -0
  2. package/package.json +1 -1
  3. package/services/api.js +6 -0
  4. package/services/tests/api.test.js +7 -0
  5. package/utils/common.js +6 -1
  6. package/utils/templateVarUtils.js +172 -0
  7. package/utils/tests/templateVarUtils.test.js +160 -0
  8. package/v2Components/CapTagList/index.js +10 -0
  9. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  16. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  18. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  19. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  20. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  21. package/v2Components/CommonTestAndPreview/index.js +693 -155
  22. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  23. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  24. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  31. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  32. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  33. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  34. package/v2Components/FormBuilder/index.js +7 -1
  35. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  36. package/v2Components/SmsFallback/constants.js +73 -0
  37. package/v2Components/SmsFallback/index.js +956 -0
  38. package/v2Components/SmsFallback/index.scss +265 -0
  39. package/v2Components/SmsFallback/messages.js +78 -0
  40. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  41. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  42. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  43. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  44. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  45. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  46. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  47. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  48. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  49. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  50. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  51. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  52. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  53. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  54. package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  55. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  56. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  57. package/v2Containers/CommunicationFlow/constants.js +200 -0
  58. package/v2Containers/CommunicationFlow/index.js +102 -0
  59. package/v2Containers/CommunicationFlow/messages.js +346 -0
  60. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  61. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  62. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  63. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  64. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  65. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  66. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  67. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  68. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  69. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  70. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  71. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  72. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  73. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  74. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  75. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  76. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  77. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  78. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  79. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  80. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  81. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  82. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  83. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  84. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  85. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  86. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  87. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  88. package/v2Containers/CreativesContainer/constants.js +12 -0
  89. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  90. package/v2Containers/CreativesContainer/index.js +289 -99
  91. package/v2Containers/CreativesContainer/index.scss +51 -1
  92. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  93. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  94. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  95. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  96. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  97. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  98. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  99. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  100. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  101. package/v2Containers/Rcs/constants.js +32 -1
  102. package/v2Containers/Rcs/index.js +950 -873
  103. package/v2Containers/Rcs/index.scss +85 -6
  104. package/v2Containers/Rcs/messages.js +10 -1
  105. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  106. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  107. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  108. package/v2Containers/Rcs/tests/index.test.js +41 -38
  109. package/v2Containers/Rcs/tests/mockData.js +38 -0
  110. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  111. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  112. package/v2Containers/Rcs/utils.js +358 -10
  113. package/v2Containers/Sms/Create/index.js +81 -36
  114. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  115. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  116. package/v2Containers/SmsTrai/Create/index.js +9 -4
  117. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  118. package/v2Containers/SmsTrai/Edit/index.js +609 -128
  119. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  120. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  121. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  122. package/v2Containers/SmsWrapper/index.js +37 -8
  123. package/v2Containers/TagList/index.js +6 -0
  124. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  125. package/v2Containers/Templates/_templates.scss +61 -2
  126. package/v2Containers/Templates/actions.js +11 -0
  127. package/v2Containers/Templates/constants.js +2 -0
  128. package/v2Containers/Templates/index.js +90 -40
  129. package/v2Containers/Templates/sagas.js +57 -12
  130. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  131. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  132. package/v2Containers/Templates/tests/sagas.test.js +193 -12
  133. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  134. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  135. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  136. package/v2Containers/TemplatesV2/index.js +86 -23
  137. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  138. package/v2Containers/Whatsapp/index.js +3 -20
  139. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -10,12 +10,14 @@ import { isTraiDLTEnable } from '../../utils/common';
10
10
  import SmsEdit from '../Sms/Edit';
11
11
  import SmsTraiCreate from '../SmsTrai/Create';
12
12
  import SmsTraiEdit from '../SmsTrai/Edit';
13
+
13
14
  const SmsWrapper = (props) => {
14
15
  const {
15
16
  isCreateSms,
16
17
  isEditSms,
17
18
  setIsLoadingContent,
18
19
  location,
20
+ route: routeFromProps,
19
21
  isGetFormData,
20
22
  getFormSubscriptionData,
21
23
  isFullMode,
@@ -37,15 +39,33 @@ const SmsWrapper = (props) => {
37
39
  handleCloseTestAndPreview,
38
40
  isTestAndPreviewMode,
39
41
  onValidationFail,
42
+ embeddedSmsFallback = false,
43
+ onEmbeddedSmsFooterValidity,
44
+ forceFullTagContext = false,
40
45
  } = props;
41
46
 
47
+ /** FormBuilder / SMS Create assume `location.query`; connected-router shapes may omit it. */
48
+ const smsLocation = (() => {
49
+ const loc = location || {};
50
+ const q = loc.query;
51
+ if (q && typeof q === 'object') {
52
+ return { ...loc, query: { ...q } };
53
+ }
54
+ return {
55
+ pathname: loc.pathname || '/sms/create',
56
+ search: loc.search || '',
57
+ query: { type: 'embedded', module: 'library' },
58
+ };
59
+ })();
60
+
42
61
  const smsProps = {
43
62
  onCreateComplete,
44
63
  setIsLoadingContent,
45
- location,
46
- route: { name: 'sms' },
64
+ location: smsLocation,
65
+ route: routeFromProps || { name: 'sms' },
47
66
  isGetFormData,
48
67
  getFormSubscriptionData,
68
+ templateData,
49
69
  getDefaultTags,
50
70
  isFullMode,
51
71
  forwardedTags,
@@ -60,24 +80,33 @@ const SmsWrapper = (props) => {
60
80
  handleCloseTestAndPreview,
61
81
  isTestAndPreviewMode,
62
82
  onValidationFail,
83
+ forceFullTagContext,
84
+ embeddedSmsFallback,
85
+ onEmbeddedSmsFooterValidity,
86
+ ...(embeddedSmsFallback
87
+ ? {
88
+ tagListGetPopupContainer: () => document.body,
89
+ tagListPopoverOverlayStyle: { zIndex: 10020 },
90
+ tagListPopoverOverlayClassName: 'sms-fallback-taglist-popover rcs-sms-fallback-taglist-popover',
91
+ }
92
+ : {}),
63
93
  };
64
- const isTraiDlt = isTraiDLTEnable(isFullMode, smsRegister);
94
+ const useTraiSmsFlow = isTraiDLTEnable(isFullMode, smsRegister);
65
95
  return <>
66
96
  {
67
- isCreateSms && (isTraiDlt ?
97
+ isCreateSms && (useTraiSmsFlow ?
68
98
  <SmsTraiCreate
69
99
  isComponent
70
100
  {...smsProps}
71
101
  onShowTemplates={onShowTemplates}
72
102
  /> :
73
103
  <SmsCreate
74
- isComponent {
75
- ...smsProps
76
- }
104
+ isComponent
105
+ {...smsProps}
77
106
  />
78
107
  )
79
108
  }
80
- {isEditSms && (isTraiDlt ?
109
+ {isEditSms && (useTraiSmsFlow ?
81
110
  <SmsTraiEdit
82
111
  {...smsProps}
83
112
  params={{id: templateData._id}}
@@ -427,6 +427,9 @@ export class TagList extends React.Component { // eslint-disable-line react/pref
427
427
  disableTooltipMsg={tooltipMsg}
428
428
  fetchingSchemaError={this?.state?.tagsError}
429
429
  popoverPlacement={this.props.popoverPlacement}
430
+ overlayStyle={this.props.popoverOverlayStyle}
431
+ overlayClassName={this.props.popoverOverlayClassName}
432
+ getPopupContainer={this.props.getPopupContainer}
430
433
  />
431
434
  </div>
432
435
  );
@@ -461,6 +464,9 @@ TagList.propTypes = {
461
464
  // message to show when Add Label button is disabled (e.g. personalization restriction)
462
465
  disableTooltipMsg: PropTypes.string,
463
466
  restrictPersonalization: PropTypes.bool,
467
+ popoverOverlayStyle: PropTypes.object,
468
+ popoverOverlayClassName: PropTypes.string,
469
+ getPopupContainer: PropTypes.func,
464
470
  intl: PropTypes.shape({
465
471
  formatMessage: PropTypes.func.isRequired,
466
472
  locale: PropTypes.string,
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapInput from '@capillarytech/cap-ui-library/CapInput';
4
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
5
+
6
+ /**
7
+ * Reusable action bar for template pickers.
8
+ * Layout and spacing live in `_templates.scss` (`.action-container`, `.action-container__toolbar-row`).
9
+ * Default search field width: `.action-container__toolbar-row .search-text` (13.125rem).
10
+ * Pass `searchInputClassName` / `searchInputStyle` to override (defaults match Templates list: 210px per master).
11
+ */
12
+ const TemplatesActionBar = ({
13
+ searchValue,
14
+ onSearchChange,
15
+ onSearch,
16
+ onClear,
17
+ searchPlaceholder,
18
+ searchInputClassName,
19
+ searchInputStyle,
20
+ ctaLabel,
21
+ onCtaClick,
22
+ ctaDisabled,
23
+ ctaClassName,
24
+ ctaNode,
25
+ children,
26
+ showCta = true,
27
+ }) => (
28
+ <div className="action-container">
29
+ <div className="action-container__toolbar-row">
30
+ {searchPlaceholder && (
31
+ <CapInput.Search
32
+ className={['search-text', searchInputClassName].filter(Boolean).join(' ')}
33
+ placeholder={searchPlaceholder}
34
+ value={searchValue}
35
+ onChange={onSearchChange}
36
+ /* antd `Input` (used by CapInput.Search) has no `onSearch`; only `Input.Search` does. */
37
+ onPressEnter={(e) => {
38
+ if (onSearch) {
39
+ const v = e?.target && 'value' in e.target ? e.target.value : searchValue;
40
+ onSearch(v);
41
+ }
42
+ }}
43
+ onSearch={onSearch}
44
+ onClear={onClear}
45
+ onScroll={(e) => e.stopPropagation()}
46
+ />
47
+ )}
48
+ {children}
49
+ </div>
50
+ {showCta && (
51
+ <div>
52
+ {ctaNode || (
53
+ <CapButton
54
+ className={ctaClassName}
55
+ type="primary"
56
+ disabled={ctaDisabled}
57
+ onClick={onCtaClick}
58
+ >
59
+ {ctaLabel}
60
+ </CapButton>
61
+ )}
62
+ </div>
63
+ )}
64
+ </div>
65
+ );
66
+
67
+ TemplatesActionBar.propTypes = {
68
+ searchValue: PropTypes.string,
69
+ onSearchChange: PropTypes.func,
70
+ onSearch: PropTypes.func,
71
+ onClear: PropTypes.func,
72
+ searchPlaceholder: PropTypes.string,
73
+ searchInputClassName: PropTypes.string,
74
+ searchInputStyle: PropTypes.object,
75
+ ctaLabel: PropTypes.node,
76
+ onCtaClick: PropTypes.func,
77
+ ctaDisabled: PropTypes.bool,
78
+ ctaClassName: PropTypes.string,
79
+ ctaNode: PropTypes.node,
80
+ children: PropTypes.node,
81
+ showCta: PropTypes.bool,
82
+ };
83
+
84
+ TemplatesActionBar.defaultProps = {
85
+ searchValue: '',
86
+ onSearchChange: () => {},
87
+ onSearch: () => {},
88
+ onClear: () => {},
89
+ searchPlaceholder: '',
90
+ searchInputClassName: '',
91
+ searchInputStyle: { width: '210px' },
92
+ ctaLabel: null,
93
+ onCtaClick: () => {},
94
+ ctaDisabled: false,
95
+ ctaClassName: '',
96
+ ctaNode: null,
97
+ children: null,
98
+ };
99
+
100
+ export default TemplatesActionBar;
101
+
@@ -659,11 +659,27 @@
659
659
  }
660
660
 
661
661
  .action-container{
662
- margin-top: 8px;
663
- margin-bottom: 16px;
662
+ margin-top: $CAP_SPACE_08;
663
+ margin-bottom: $CAP_SPACE_16;
664
664
  display: flex;
665
665
  justify-content: space-between;
666
666
  align-items: center;
667
+
668
+ &__toolbar-row {
669
+ display: flex;
670
+ align-items: center;
671
+ gap: 0.75rem;
672
+ }
673
+
674
+ &__toolbar-row .search-text {
675
+ width: 13.125rem;
676
+ }
677
+
678
+ &__create-row {
679
+ display: flex;
680
+ justify-content: space-between;
681
+ align-items: center;
682
+ }
667
683
  }
668
684
 
669
685
  .popover-action-container:hover{
@@ -1101,4 +1117,47 @@
1101
1117
  .inapp-illustration-parent {
1102
1118
  height: "calc(100vh - 325px)";
1103
1119
  overflow: 'auto';
1120
+ }
1121
+
1122
+ /* Local SMS / slidebox: viewport-based .v2-pagination-container-half height leaves empty space below and clips cards */
1123
+ .creatives-templates-container--local-sms.library-mode {
1124
+ .creatives-templates-list.library-mode > .cap-row:first-of-type > div {
1125
+ display: flex;
1126
+ flex-direction: column;
1127
+ flex: 1 1 auto;
1128
+ min-height: 0;
1129
+ overflow: hidden;
1130
+ }
1131
+
1132
+ .creatives-templates-list.library-mode > .cap-row:first-of-type > div > div:first-child {
1133
+ display: flex;
1134
+ flex-direction: column;
1135
+ flex: 1 1 auto;
1136
+ min-height: 0;
1137
+ overflow: hidden;
1138
+ }
1139
+
1140
+ /* Block below search/filter: grid + skeletons — must grow to use space above footer */
1141
+ .creatives-templates-list.library-mode > .cap-row:first-of-type > div > div:first-child > div:nth-child(2) {
1142
+ flex: 1 1 auto;
1143
+ min-height: 0;
1144
+ display: flex;
1145
+ flex-direction: column;
1146
+ overflow: hidden;
1147
+ }
1148
+
1149
+ /*
1150
+ * Scroll needs a definite height. Pure flex + height:auto + max-height:100% often won’t bound (no % base), so no scrollbar.
1151
+ * Use a taller slice than global 100vh-20rem so the grid uses space under search; still overflow-y:auto for long lists.
1152
+ */
1153
+ .v2-pagination-container,
1154
+ .v2-pagination-container-half {
1155
+ flex: 0 1 auto;
1156
+ min-height: 0;
1157
+ height: calc(100vh - 12rem);
1158
+ max-height: calc(100vh - 12rem);
1159
+ overflow-y: auto;
1160
+ overflow-x: hidden;
1161
+ -webkit-overflow-scrolling: touch;
1162
+ }
1104
1163
  }
@@ -15,6 +15,17 @@ export function getAllTemplates(channel, queryParams, intlCopyOf = '') {
15
15
  };
16
16
  }
17
17
 
18
+
19
+ export function getLocalSmsTemplates(queryParams, intlCopyOf = '', onSuccess, onFailure) {
20
+ return {
21
+ type: types.GET_LOCAL_SMS_TEMPLATES_REQUEST,
22
+ queryParams,
23
+ intlCopyOf,
24
+ onSuccess,
25
+ onFailure,
26
+ };
27
+ }
28
+
18
29
  export function resetTemplate() {
19
30
  return {
20
31
  type: types.RESET_TEMPLATE,
@@ -10,6 +10,8 @@ export const GET_ALL_TEMPLATES_REQUEST = 'app/v2Containers/Templates/GET_ALL_TEM
10
10
  export const GET_ALL_TEMPLATES_SUCCESS = 'app/v2Containers/Templates/GET_ALL_TEMPLATES_SUCCESS';
11
11
  export const GET_ALL_TEMPLATES_FAILURE = 'app/v2Containers/Templates/GET_ALL_TEMPLATES_FAILURE';
12
12
 
13
+ export const GET_LOCAL_SMS_TEMPLATES_REQUEST = 'app/v2Containers/Templates/GET_LOCAL_SMS_TEMPLATES_REQUEST';
14
+
13
15
  export const DELETE_TEMPLATE_REQUEST = 'app/v2Containers/Templates/DELETE_TEMPLATE_REQUEST';
14
16
  export const DELETE_RCS_TEMPLATE_REQUEST = 'app/v2Containers/Templates/DELETE_RCS_TEMPLATE_REQUEST';
15
17
  export const DELETE_TEMPLATE_SUCCESS = 'app/v2Containers/Templates/DELETE_TEMPLATE_SUCCESS';
@@ -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(),