@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
@@ -44,10 +44,6 @@ class TemplateNameInputField extends React.Component {
44
44
  }
45
45
  }
46
46
  import PropTypes from 'prop-types';
47
- import {
48
- CAP_SPACE_16, CAP_SPACE_32, CAP_SPACE_56, CAP_SPACE_64,
49
- } from '@capillarytech/cap-ui-library/styled/variables';
50
-
51
47
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
52
48
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
53
49
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
@@ -57,12 +53,11 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
57
53
  import { injectIntl, FormattedMessage } from 'react-intl';
58
54
  import classnames from 'classnames';
59
55
  import {
60
- isEmpty, get, forEach, cloneDeep, debounce,
56
+ isEmpty, get, forEach, cloneDeep, debounce, pick,
61
57
  } from 'lodash';
62
58
  import { connect } from 'react-redux';
63
59
  import { createStructuredSelector } from 'reselect';
64
60
  import { bindActionCreators, compose } from 'redux';
65
- import styled from 'styled-components';
66
61
  import { GA } from '@capillarytech/cap-ui-utils';
67
62
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
68
63
 
@@ -76,6 +71,7 @@ import SlideBoxContent from './SlideBoxContent';
76
71
  import * as constants from './constants';
77
72
  import * as commonUtil from '../../utils/common';
78
73
  import { gtmPush } from '../../utils/gtmTrackers';
74
+ import { normalizeRcsMessageContentForApi } from '../../utils/rcsPayloadUtils';
79
75
  import './index.scss';
80
76
  import * as templateActions from '../Templates/actions';
81
77
  import * as globalActions from '../Cap/actions';
@@ -90,7 +86,10 @@ import {
90
86
  } from '../Line/Container/constants';
91
87
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
92
88
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
93
- import { RCS_STATUSES } from '../Rcs/constants';
89
+ import { RCS_STATUSES, contentType as RCS_CONTENT_TYPE, EMBEDDED } from '../Rcs/constants';
90
+ import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
91
+ import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
92
+ import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
94
93
  import { CREATIVE } from '../Facebook/constants';
95
94
  import { LOYALTY } from '../App/constants';
96
95
  import {
@@ -131,6 +130,11 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
131
130
  import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
132
131
  import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
133
132
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
133
+ import SlideBoxWrapper from './CreativesSlideBoxWrapper';
134
+ import {
135
+ computeLiquidFooterUpdateFromFormBuilder,
136
+ getSlideBoxWrapperMarginFromLiquidErrors,
137
+ } from './embeddedSlideboxUtils';
134
138
 
135
139
  import {
136
140
  transformChannelPayload,
@@ -139,51 +143,24 @@ import {
139
143
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
140
144
  import { BIG_HTML } from '../InApp/constants';
141
145
 
142
- /**
143
- * Returns true if value is "deep empty": no errors present.
144
- * - null/undefined: empty
145
- * - string: empty if length === 0
146
- * - array: empty if length === 0
147
- * - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
148
- */
149
- function isDeepEmpty(value) {
150
- if (value == null) return true;
151
- if (typeof value === 'string') return value.length === 0;
152
- if (Array.isArray(value)) return value.length === 0;
153
- if (typeof value === 'object') {
154
- return Object.values(value).every(isDeepEmpty);
155
- }
156
- return false;
157
- }
158
-
159
146
  const classPrefix = 'add-creatives-section';
160
147
  const CREATIVES_CONTAINER = 'creativesContainer';
161
148
 
162
- const SlideBoxWrapper = styled.div`
163
- .cap-slide-box-v2-container{
164
- .slidebox-header, .slidebox-content-container{
165
- margin-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
166
- padding: 0 rem;
167
- &.has-footer{
168
- overflow-x: hidden;
169
- }
170
- }
171
- .slidebox-footer{
172
- /* Only apply margin-bottom to footer when ErrorInfoNote is shown in footer (BEE editor) */
173
- /* For HTML Editor, errors are shown in ValidationErrorDisplay (inside content area), so no footer margin needed */
174
- margin-bottom: ${({ shouldApplyFooterMargin }) => (shouldApplyFooterMargin ? `${CAP_SPACE_16}` : '0')};
175
- padding: 0 rem;
176
- &.has-footer{
177
- overflow-x: hidden;
178
- }
179
- }
180
- }
181
- `;
182
149
  export class Creatives extends React.Component {
183
150
  constructor(props) {
184
151
  super(props);
185
152
 
186
- const initialSlidBoxContent = this.getSlideBoxContent({ mode: props.creativesMode, templateData: props.templateData, isFullMode: props.isFullMode });
153
+ const useLocalTemplates = get(
154
+ props,
155
+ 'localTemplatesConfig.useLocalTemplates',
156
+ get(props, 'useLocalTemplates', false),
157
+ );
158
+ const initialSlidBoxContent = this.getSlideBoxContent({
159
+ mode: props.creativesMode,
160
+ templateData: props.templateData,
161
+ isFullMode: props.isFullMode,
162
+ useLocalTemplates,
163
+ });
187
164
 
188
165
  this.state = {
189
166
  isLoadingContent: true,
@@ -230,7 +207,13 @@ export class Creatives extends React.Component {
230
207
  }
231
208
 
232
209
  componentWillUnmount() {
233
- if (get(this.props, 'location.query.type', '') === "embedded") {
210
+ const isEmbedded = get(this.props, 'location.query.type', '') === EMBEDDED;
211
+ const useLocalTemplates = get(
212
+ this.props,
213
+ 'localTemplatesConfig.useLocalTemplates',
214
+ get(this.props, 'useLocalTemplates', false),
215
+ );
216
+ if (isEmbedded && !useLocalTemplates) {
234
217
  this.props.templateActions.resetTemplateStoreData();
235
218
  }
236
219
  this.props.globalActions.clearMetaEntities();
@@ -840,15 +823,69 @@ export class Creatives extends React.Component {
840
823
  smsFallBackContent = {},
841
824
  creativeName = "",
842
825
  channel = constants.RCS,
826
+ rcsCardVarMapped,
843
827
  accountId = "",
844
828
  } = templateData || {};
845
- const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
829
+ const { isFullMode: isFullModeForRcsPayload } = this.props;
830
+ const isCarouselRcs = (rcsContent?.cardType || '').toString().toUpperCase() === RCS_CONTENT_TYPE.carousel;
831
+ const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
832
+ const {
833
+ cardDisplayTitle: _omitDispTitleIn,
834
+ cardDisplayDescription: _omitDispDescIn,
835
+ ...cardContent
836
+ } = firstCardIn;
846
837
  const Status = RCS_STATUSES.approved || '';
838
+ const mergedCardVarMapped = (() => {
839
+ const nestedCardVarMapped = cardContent?.cardVarMapped;
840
+ const rootMirrorCardVarMapped = rcsCardVarMapped;
841
+ const nestedRecord =
842
+ nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
843
+ ? nestedCardVarMapped
844
+ : {};
845
+ const rootRecord =
846
+ rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
847
+ ? rootMirrorCardVarMapped
848
+ : {};
849
+ const mergedFromRootAndNested = {
850
+ ...pickRcsCardVarMappedEntries(rootRecord),
851
+ ...pickRcsCardVarMappedEntries(nestedRecord),
852
+ };
853
+ return Object.keys(mergedFromRootAndNested).length > 0
854
+ ? mergedFromRootAndNested
855
+ : null;
856
+ })();
857
+ // Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
858
+ // slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
859
+ // Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
860
+ const includeRootRcsCardVarMapped =
861
+ mergedCardVarMapped && isFullModeForRcsPayload === true;
862
+
863
+ const builtCardContent = isCarouselRcs
864
+ ? (rcsContent.cardContent || []).map((card, idx) => {
865
+ const {
866
+ cardDisplayTitle: _dt,
867
+ cardDisplayDescription: _dd,
868
+ ...restCard
869
+ } = card || {};
870
+ return {
871
+ ...restCard,
872
+ ...(idx === 0 && mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
873
+ Status,
874
+ };
875
+ })
876
+ : [
877
+ {
878
+ ...cardContent,
879
+ ...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
880
+ Status,
881
+ },
882
+ ];
847
883
 
848
884
  creativesTemplateData = {
849
885
  type: channel,
850
886
  edit: true,
851
887
  name: creativeName,
888
+ ...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
852
889
  versions: {
853
890
  base: {
854
891
  content: {
@@ -856,12 +893,7 @@ export class Creatives extends React.Component {
856
893
  rcsContent: {
857
894
  ...rcsContent,
858
895
  ...(accountId && !isFullMode && { accountId }),
859
- cardContent: [
860
- {
861
- ...cardContent,
862
- Status,
863
- },
864
- ],
896
+ cardContent: builtCardContent,
865
897
  },
866
898
  smsFallBackContent,
867
899
  },
@@ -1009,7 +1041,10 @@ export class Creatives extends React.Component {
1009
1041
  return newExpandableDetails;
1010
1042
  }
1011
1043
 
1012
- getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
1044
+ getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
1045
+ const channel = String(
1046
+ channelParam || template?.type || get(template, 'value.type') || '',
1047
+ ).toUpperCase();
1013
1048
  let templateData = { channel };
1014
1049
  switch (channel) {
1015
1050
  case constants.SMS:
@@ -1356,28 +1391,104 @@ export class Creatives extends React.Component {
1356
1391
  break;
1357
1392
  case constants.RCS:
1358
1393
  if (template.value) {
1359
- const { name = "", versions = {} } = {
1360
- } = template.value || {};
1361
- const smsFallBackContent = get(versions, 'base.content.RCS.smsFallBackContent', {});
1394
+ const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
1395
+ const { name = "", versions = {} } = template.value || {};
1396
+ const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
1397
+ const fromRecords = {
1398
+ ...(templateRecords?.smsFallBackContent || {}),
1399
+ ...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
1400
+ };
1401
+ const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
1402
+ if (
1403
+ !smsFallbackPayload
1404
+ || typeof smsFallbackPayload !== 'object'
1405
+ || Object.keys(smsFallbackPayload).length === 0
1406
+ ) {
1407
+ return false;
1408
+ }
1409
+ const fallbackBodyText = String(
1410
+ smsFallbackPayload.smsContent
1411
+ ?? smsFallbackPayload.smsTemplateContent
1412
+ ?? smsFallbackPayload.message
1413
+ ?? smsFallbackPayload.content
1414
+ ?? '',
1415
+ ).trim();
1416
+ const fallbackTemplateName = String(
1417
+ smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
1418
+ ).trim();
1419
+ const rcsSmsFallbackVarMapped =
1420
+ smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
1421
+ const hasVarMappedEntries =
1422
+ rcsSmsFallbackVarMapped != null
1423
+ && typeof rcsSmsFallbackVarMapped === 'object'
1424
+ && Object.keys(rcsSmsFallbackVarMapped).length > 0;
1425
+ return (
1426
+ fallbackBodyText !== ''
1427
+ || fallbackTemplateName !== ''
1428
+ || hasVarMappedEntries
1429
+ );
1430
+ };
1431
+ // If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
1432
+ const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
1433
+ ? { ...fromRecords, ...fromSubmit }
1434
+ : { ...fromSubmit, ...fromRecords };
1362
1435
  const {
1363
- cardContent = [],
1436
+ cardContent: cardContentFromSubmit = [],
1364
1437
  contentType = "",
1365
1438
  cardType = "",
1366
1439
  cardSettings = {},
1367
1440
  accountId = "",
1368
1441
  } = get(versions, 'base.content.RCS.rcsContent', {});
1442
+ const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1443
+ const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1444
+ ? cardContentFromSubmit[0]
1445
+ : null;
1446
+ const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1447
+ cardContentFromSubmit,
1448
+ rootRcsCardVarMappedFromSubmit,
1449
+ isFullModeForRcsConsumerPayload,
1450
+ );
1369
1451
  const rcsContent = {
1370
1452
  contentType,
1371
1453
  cardType,
1372
1454
  cardSettings,
1373
1455
  cardContent,
1374
1456
  };
1457
+ const cardVarMappedFromFirstRcsCard =
1458
+ firstCardFromSubmit?.cardVarMapped != null
1459
+ && typeof firstCardFromSubmit.cardVarMapped === 'object'
1460
+ ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1461
+ : null;
1462
+ const includeRootRcsCardVarMappedOnConsumerPayload =
1463
+ cardVarMappedFromFirstRcsCard
1464
+ && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
1465
+ && isFullModeForRcsConsumerPayload === true;
1375
1466
  templateData = {
1376
1467
  channel,
1377
1468
  creativeName: name,
1378
1469
  rcsContent,
1379
1470
  accountId,
1471
+ ...(includeRootRcsCardVarMappedOnConsumerPayload
1472
+ ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1473
+ : {}),
1380
1474
  };
1475
+ // Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
1476
+ // so reopening the editor restores fallback text and tag mappings.
1477
+ // cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
1478
+ if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
1479
+ const smsText =
1480
+ smsFallBackContent.message
1481
+ ?? smsFallBackContent.smsContent
1482
+ ?? smsFallBackContent.smsTemplateContent
1483
+ ?? '';
1484
+ templateData.smsFallBackContent = {
1485
+ ...smsFallBackContent,
1486
+ ...(String(smsText).trim() !== ''
1487
+ ? { message: String(smsText).trim() }
1488
+ : {}),
1489
+ };
1490
+ }
1491
+ normalizeRcsMessageContentForApi(templateData);
1381
1492
  }
1382
1493
  break;
1383
1494
  case constants.ZALO:
@@ -1495,7 +1606,10 @@ export class Creatives extends React.Component {
1495
1606
  return templateData;
1496
1607
  };
1497
1608
 
1498
- getSlideBoxContent({ mode, templateData, isFullMode }) {
1609
+ getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
1610
+ if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
1611
+ return 'templates';
1612
+ }
1499
1613
  let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
1500
1614
  if (mode === 'create' && isFullMode) {
1501
1615
  creativesMode = 'createTemplate';
@@ -1583,24 +1697,110 @@ export class Creatives extends React.Component {
1583
1697
  getFormData = (template) => {
1584
1698
  // Always reset isGetFormData so the child does not re-send form data on every re-render
1585
1699
  // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1586
- this.setState({ isGetFormData: false });
1587
- if (template.validity) {
1588
- this.setState(
1589
- {},
1590
- () => {
1591
- const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1592
- const channel = templateData.type;
1593
- const creativesData = this.getCreativesData(channel, template, templateData);// convers data to consumer understandable format
1594
- creativesData.then((data) => {
1595
- this.logGTMEvent(channel, data);
1596
- this.processCentralCommsMetaId(channel, data);
1700
+ this.setState(
1701
+ (prevState) => {
1702
+ const next = { isGetFormData: false };
1703
+ if (!template.validity) {
1704
+ return next;
1705
+ }
1706
+ const baseTd = prevState.templateData || template;
1707
+ const channel = (
1708
+ baseTd?.type
1709
+ || template?.type
1710
+ || get(template, 'value.type')
1711
+ || ''
1712
+ ).toUpperCase();
1713
+ // Library mode: persist last submitted creatives shape so reopening still hydrates the editor
1714
+ // (parent may not merge getCreativesData back into templateData).
1715
+ if (this.props.isFullMode === false && template.value) {
1716
+ if (channel === constants.RCS) {
1717
+ const smsFallBackFromPayload = get(
1718
+ template.value,
1719
+ 'versions.base.content.RCS.smsFallBackContent',
1720
+ );
1721
+ const rcsCardVarMappedFromPayload = get(
1722
+ template.value,
1723
+ 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
1724
+ );
1725
+ next.templateData = {
1726
+ ...baseTd,
1727
+ type: constants.RCS,
1728
+ name: template?.value?.name,
1729
+ versions: template?.value?.versions,
1730
+ ...(smsFallBackFromPayload != null
1731
+ && typeof smsFallBackFromPayload === 'object'
1732
+ && Object.keys(smsFallBackFromPayload).length > 0
1733
+ ? { smsFallBackContent: smsFallBackFromPayload }
1734
+ : {}),
1735
+ ...(rcsCardVarMappedFromPayload != null
1736
+ && typeof rcsCardVarMappedFromPayload === 'object'
1737
+ ? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
1738
+ : {}),
1739
+ };
1740
+ if (template._id) {
1741
+ next.templateData._id = template._id;
1742
+ }
1743
+ } else if (channel === constants.SMS) {
1744
+ const submittedSmsTemplateValue = template?.value;
1745
+ const smsVersions =
1746
+ submittedSmsTemplateValue?.history != null
1747
+ ? submittedSmsTemplateValue
1748
+ : {
1749
+ base: submittedSmsTemplateValue?.base,
1750
+ history: submittedSmsTemplateValue?.base
1751
+ ? [submittedSmsTemplateValue.base]
1752
+ : [],
1753
+ };
1754
+ next.templateData = {
1755
+ ...baseTd,
1756
+ type: constants.SMS,
1757
+ name: baseTd?.name || constants.DEFAULT_SMS_TEMPLATE_NAME,
1758
+ versions: smsVersions,
1759
+ };
1760
+ if (template?._id) {
1761
+ next.templateData._id = template._id;
1762
+ }
1763
+ }
1764
+ }
1765
+ return next;
1766
+ },
1767
+ () => {
1768
+ if (!template.validity) {
1769
+ return;
1770
+ }
1771
+ const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1772
+ const channelForConsumer = String(
1773
+ templateData.type
1774
+ || template.type
1775
+ || get(template, 'value.type')
1776
+ || '',
1777
+ ).toUpperCase();
1778
+ const creativesData = this.getCreativesData(
1779
+ channelForConsumer,
1780
+ template,
1781
+ this.state.templateData || template,
1782
+ );// convers data to consumer understandable format
1783
+ creativesData.then((data) => {
1784
+ this.logGTMEvent(channelForConsumer, data);
1785
+ this.processCentralCommsMetaId(channelForConsumer, data, {
1786
+ closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
1597
1787
  });
1598
- },
1599
- );
1600
- }
1788
+ });
1789
+ },
1790
+ );
1601
1791
  }
1602
1792
 
1603
- processCentralCommsMetaId = (channel, creativesData) => {
1793
+ processCentralCommsMetaId = (channel, creativesData, options = {}) => {
1794
+ const { closeSlideBoxAfterSubmit = false } = options;
1795
+ const maybeCloseLibrarySlideBox = () => {
1796
+ if (
1797
+ closeSlideBoxAfterSubmit
1798
+ && this.props.isFullMode === false
1799
+ && typeof this.handleCloseSlideBox === 'function'
1800
+ ) {
1801
+ this.handleCloseSlideBox();
1802
+ }
1803
+ };
1604
1804
  // Create the payload for the centralcommnsmetaId API call
1605
1805
  const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1606
1806
  const { actionName, setMetaData = () => { } } = loyaltyMetaData;
@@ -1626,6 +1826,7 @@ export class Creatives extends React.Component {
1626
1826
  if (result?.status?.code === 200) {
1627
1827
  setMetaData(result);
1628
1828
  this.props.getCreativesData(creativesData);
1829
+ maybeCloseLibrarySlideBox();
1629
1830
  } else {
1630
1831
  CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
1631
1832
  }
@@ -1636,6 +1837,7 @@ export class Creatives extends React.Component {
1636
1837
  } else {
1637
1838
  // If not a loyalty module or different action, should work as usual
1638
1839
  this.props.getCreativesData(creativesData);
1840
+ maybeCloseLibrarySlideBox();
1639
1841
  }
1640
1842
  };
1641
1843
 
@@ -1668,7 +1870,9 @@ export class Creatives extends React.Component {
1668
1870
  }
1669
1871
  this.setState((prevState) => ({
1670
1872
  ...prevState,
1671
- templateData: undefined,
1873
+ // Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
1874
+ // Undefined isFullMode defaults to full-mode close behavior (clear templateData).
1875
+ ...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
1672
1876
  showSlideBox: false,
1673
1877
  liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1674
1878
  isLiquidValidationError: false,
@@ -1871,21 +2075,17 @@ export class Creatives extends React.Component {
1871
2075
  }
1872
2076
 
1873
2077
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1874
- const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1875
- const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1876
- const hasLiquid = !isDeepEmpty(liquidMsgs);
1877
- const hasStandard = !isDeepEmpty(standardMsgs);
1878
- const isLiquidValidationError = hasLiquid || hasStandard;
1879
- // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1880
- const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1881
- if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1882
- return;
1883
- }
1884
- this.setState({
1885
- isLiquidValidationError,
1886
- liquidErrorMessage: errorMessagesFromFormBuilder,
1887
- activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1888
- });
2078
+ const next = computeLiquidFooterUpdateFromFormBuilder(
2079
+ errorMessagesFromFormBuilder,
2080
+ this.state.liquidErrorMessage,
2081
+ currentFormBuilderTab,
2082
+ {
2083
+ previousIsLiquidValidationError: this.state.isLiquidValidationError,
2084
+ currentChannelUpper: this.state.currentChannel?.toUpperCase(),
2085
+ },
2086
+ );
2087
+ if (next == null) return;
2088
+ this.setState(next);
1889
2089
  }
1890
2090
 
1891
2091
  // Callback to update HTML Editor validation state (called from EmailWrapper)
@@ -2008,6 +2208,11 @@ export class Creatives extends React.Component {
2008
2208
  inAppEditorType,
2009
2209
  htmlEditorValidationState,
2010
2210
  } = this.state;
2211
+ const useLocalTemplates = get(
2212
+ this.props,
2213
+ 'localTemplatesConfig.useLocalTemplates',
2214
+ get(this.props, 'useLocalTemplates', false),
2215
+ );
2011
2216
  const {
2012
2217
  isFullMode,
2013
2218
  creativesMode,
@@ -2026,7 +2231,6 @@ export class Creatives extends React.Component {
2026
2231
  smsRegister,
2027
2232
  enableNewChannels,
2028
2233
  eventContextTags,
2029
- waitEventContextTags = {},
2030
2234
  isLoyaltyModule,
2031
2235
  loyaltyMetaData = {},
2032
2236
  } = this.props;
@@ -2060,14 +2264,7 @@ export class Creatives extends React.Component {
2060
2264
  // IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
2061
2265
  const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
2062
2266
 
2063
- // Calculate margin for header/content (always apply if there are errors, regardless of editor type)
2064
- const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
2065
- ? CAP_SPACE_64
2066
- : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
2067
- ? CAP_SPACE_56
2068
- : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
2069
- ? CAP_SPACE_32
2070
- : 0;
2267
+ const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
2071
2268
  /* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
2072
2269
 
2073
2270
  // Compute anonymous user type and channel restrictions
@@ -2096,7 +2293,10 @@ export class Creatives extends React.Component {
2096
2293
  <SlideBoxWrapper
2097
2294
  slideBoxWrapperMargin={slideBoxWrapperMargin}
2098
2295
  shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
2099
- className={classnames(`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`)}
2296
+ className={classnames(
2297
+ `${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
2298
+ useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
2299
+ )}
2100
2300
  >
2101
2301
  <CapSlideBox
2102
2302
  header={
@@ -2121,12 +2321,13 @@ export class Creatives extends React.Component {
2121
2321
  smsRegister={smsRegister}
2122
2322
  handleClose={this.handleCloseSlideBox}
2123
2323
  moduleType={this.props.messageDetails?.type}
2324
+ useLocalTemplates={useLocalTemplates}
2124
2325
  />
2125
2326
  )}
2126
2327
  content={(
2127
2328
  <SlideBoxContent
2128
2329
  key="creatives-container-slidebox-content"
2129
- onSelectTemplate={this.onSelectTemplate}
2330
+ onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
2130
2331
  onCreateComplete={getCreativesData}
2131
2332
  onPreviewTemplate={this.onPreviewTemplate}
2132
2333
  slidBoxContent={slidBoxContent}
@@ -2194,7 +2395,6 @@ export class Creatives extends React.Component {
2194
2395
  creativesMode={creativesMode} // An existing prop that we're using here. Required to ensure correct account details in Edit or Preview in case of Embedded mode.
2195
2396
  hostName={this.props?.hostName || ''}
2196
2397
  eventContextTags={eventContextTags}
2197
- waitEventContextTags={waitEventContextTags}
2198
2398
  isLoyaltyModule={isLoyaltyModule}
2199
2399
  loyaltyMetaData={loyaltyMetaData}
2200
2400
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
@@ -2203,7 +2403,8 @@ export class Creatives extends React.Component {
2203
2403
  isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
2204
2404
  onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
2205
2405
  onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
2206
- />
2406
+ localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
2407
+ />
2207
2408
  )}
2208
2409
  footer={this.shouldShowFooter() ? (
2209
2410
  <SlideBoxFooter
@@ -2288,13 +2489,31 @@ Creatives.propTypes = {
2288
2489
  orgUnitId: PropTypes.number,
2289
2490
  hostName: PropTypes.string,
2290
2491
  eventContextTags: PropTypes.array,
2291
- waitEventContextTags: PropTypes.object,
2292
2492
  loyaltyTagFetchingDependencies: PropTypes.object,
2293
2493
  customerType: PropTypes.string,
2294
2494
  intl: PropTypes.shape({
2295
2495
  formatMessage: PropTypes.func,
2296
2496
  }),
2297
2497
  stopValidation: PropTypes.func,
2498
+ // Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
2499
+ // All optional. Pass either localTemplatesConfig (object) or individual props below.
2500
+ localTemplatesConfig: PropTypes.shape({
2501
+ useLocalTemplates: PropTypes.bool,
2502
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2503
+ localTemplatesLoading: PropTypes.bool,
2504
+ localTemplatesFilterContent: PropTypes.node,
2505
+ localTemplatesSentinelContent: PropTypes.node,
2506
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2507
+ localTemplatesUseSkeleton: PropTypes.bool,
2508
+ }),
2509
+ useLocalTemplates: PropTypes.bool,
2510
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2511
+ localTemplatesLoading: PropTypes.bool,
2512
+ localTemplatesFilterContent: PropTypes.node,
2513
+ localTemplatesSentinelContent: PropTypes.node,
2514
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2515
+ localTemplatesUseSkeleton: PropTypes.bool,
2516
+ onSelectTemplate: PropTypes.func,
2298
2517
  };
2299
2518
  const mapStatesToProps = () => createStructuredSelector({
2300
2519
  isLoading: isLoadingSelector(),