@capillarytech/creatives-library 8.0.359-alpha.0 → 8.0.359-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 (147) hide show
  1. package/constants/unified.js +29 -0
  2. package/index.html +1 -0
  3. package/package.json +1 -1
  4. package/services/tests/api.test.js +35 -20
  5. package/utils/cdnTransformation.js +75 -3
  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/cdnTransformation.test.js +127 -0
  10. package/utils/tests/rcsPayloadUtils.test.js +226 -0
  11. package/utils/tests/templateVarUtils.test.js +204 -0
  12. package/v2Components/CapActionButton/constants.js +7 -0
  13. package/v2Components/CapActionButton/index.js +166 -108
  14. package/v2Components/CapActionButton/index.scss +157 -6
  15. package/v2Components/CapActionButton/messages.js +19 -3
  16. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  17. package/v2Components/CapImageUpload/index.js +2 -2
  18. package/v2Components/CapTagList/index.js +10 -0
  19. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +72 -49
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  21. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +214 -21
  22. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +83 -9
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  26. package/v2Components/CommonTestAndPreview/SendTestMessage.js +10 -5
  27. package/v2Components/CommonTestAndPreview/UnifiedPreview/PreviewHeader.js +16 -0
  28. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +157 -15
  29. package/v2Components/CommonTestAndPreview/UnifiedPreview/ViberPreviewContent.js +14 -132
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/WebPushPreviewContent.js +169 -0
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +400 -239
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +202 -10
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  34. package/v2Components/CommonTestAndPreview/constants.js +40 -2
  35. package/v2Components/CommonTestAndPreview/index.js +887 -453
  36. package/v2Components/CommonTestAndPreview/messages.js +45 -3
  37. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  38. package/v2Components/CommonTestAndPreview/sagas.js +25 -6
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/PreviewHeader.test.js +163 -0
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  47. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/ViberPreviewContent.test.js +0 -364
  48. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/WebPushPreviewContent.test.js +522 -0
  49. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +454 -1
  50. package/v2Components/CommonTestAndPreview/tests/constants.test.js +2 -1
  51. package/v2Components/CommonTestAndPreview/tests/index.test.js +327 -4
  52. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  53. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +31 -24
  54. package/v2Components/FormBuilder/index.js +167 -56
  55. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +91 -0
  56. package/v2Components/SmsFallback/constants.js +73 -0
  57. package/v2Components/SmsFallback/index.js +956 -0
  58. package/v2Components/SmsFallback/index.scss +265 -0
  59. package/v2Components/SmsFallback/messages.js +78 -0
  60. package/v2Components/SmsFallback/smsFallbackUtils.js +119 -0
  61. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  62. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  63. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  64. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +223 -0
  65. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +309 -0
  66. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  67. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  68. package/v2Components/TemplatePreview/_templatePreview.scss +37 -22
  69. package/v2Components/TemplatePreview/constants.js +2 -0
  70. package/v2Components/TemplatePreview/index.js +143 -31
  71. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  72. package/v2Components/TestAndPreviewSlidebox/index.js +15 -3
  73. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  74. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  75. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  76. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  77. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  78. package/v2Containers/App/constants.js +3 -0
  79. package/v2Containers/App/tests/constants.test.js +61 -0
  80. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +17 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +14 -5
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +36 -5
  84. package/v2Containers/CreativesContainer/constants.js +9 -0
  85. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +79 -0
  86. package/v2Containers/CreativesContainer/index.js +382 -127
  87. package/v2Containers/CreativesContainer/index.scss +83 -1
  88. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  89. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +79 -34
  90. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  91. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  92. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  93. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  94. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  95. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  96. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  97. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  98. package/v2Containers/Rcs/constants.js +120 -11
  99. package/v2Containers/Rcs/index.js +2577 -812
  100. package/v2Containers/Rcs/index.scss +281 -8
  101. package/v2Containers/Rcs/messages.js +34 -3
  102. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  103. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98036 -70145
  104. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  105. package/v2Containers/Rcs/tests/index.test.js +152 -121
  106. package/v2Containers/Rcs/tests/mockData.js +38 -0
  107. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  108. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  109. package/v2Containers/Rcs/utils.js +478 -11
  110. package/v2Containers/Sms/Create/index.js +106 -40
  111. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  112. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  113. package/v2Containers/SmsTrai/Create/index.js +9 -4
  114. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  115. package/v2Containers/SmsTrai/Edit/index.js +640 -130
  116. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  117. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  118. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  119. package/v2Containers/SmsWrapper/index.js +37 -8
  120. package/v2Containers/TagList/index.js +6 -0
  121. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  122. package/v2Containers/Templates/_templates.scss +166 -86
  123. package/v2Containers/Templates/actions.js +11 -0
  124. package/v2Containers/Templates/constants.js +2 -0
  125. package/v2Containers/Templates/index.js +203 -145
  126. package/v2Containers/Templates/sagas.js +62 -13
  127. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  128. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1062 -1017
  129. package/v2Containers/Templates/tests/sagas.test.js +222 -22
  130. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  131. package/v2Containers/Templates/tests/webpush.test.js +375 -0
  132. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  133. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  134. package/v2Containers/TemplatesV2/index.js +86 -23
  135. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  136. package/v2Containers/Viber/constants.js +0 -19
  137. package/v2Containers/Viber/index.js +47 -714
  138. package/v2Containers/Viber/index.scss +0 -148
  139. package/v2Containers/Viber/messages.js +0 -116
  140. package/v2Containers/Viber/tests/index.test.js +0 -80
  141. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  142. package/v2Containers/WebPush/Create/index.js +91 -8
  143. package/v2Containers/WebPush/Create/index.scss +7 -0
  144. package/v2Containers/WebPush/Create/tests/getTemplateContent.test.js +348 -0
  145. package/v2Containers/WebPush/Create/tests/testAndPreviewIntegration.test.js +325 -0
  146. package/v2Containers/Whatsapp/index.js +3 -20
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -1,9 +1,49 @@
1
1
  import React from 'react';
2
- import PropTypes from 'prop-types';
3
- import {
4
- CAP_SPACE_16, CAP_SPACE_32, CAP_SPACE_56, CAP_SPACE_64,
5
- } from '@capillarytech/cap-ui-library/styled/variables';
6
2
 
3
+ // Isolated input for the email template name field.
4
+ // Manages its own value in local state so keystrokes only re-render this
5
+ // small component, not the entire CreativesContainer → Email → FormBuilder tree.
6
+ class TemplateNameInputField extends React.Component {
7
+ constructor(props) {
8
+ super(props);
9
+ this.state = { localValue: props.initialValue || '' };
10
+ }
11
+
12
+ componentDidUpdate(prevProps) {
13
+ // Sync from props only when the external value changed AND the user hasn't
14
+ // diverged from the previous prop value. This handles async data-load in edit
15
+ // mode without overwriting what the user is actively typing.
16
+ if (
17
+ prevProps.initialValue !== this.props.initialValue &&
18
+ this.state.localValue === (prevProps.initialValue || '')
19
+ ) {
20
+ this.setState({ localValue: this.props.initialValue || '' });
21
+ }
22
+ }
23
+
24
+ handleChange = (ev) => {
25
+ const { value } = ev.currentTarget;
26
+ this.setState({ localValue: value });
27
+ if (this.props.onChange) this.props.onChange(value);
28
+ };
29
+
30
+ handleBlur = () => {
31
+ if (this.props.onBlur) this.props.onBlur(this.state.localValue);
32
+ };
33
+
34
+ render() {
35
+ const { onChange: _onChange, initialValue: _initialValue, onBlur: _ob, ...rest } = this.props;
36
+ return (
37
+ <CapInput
38
+ {...rest}
39
+ value={this.state.localValue}
40
+ onChange={this.handleChange}
41
+ onBlur={this.handleBlur}
42
+ />
43
+ );
44
+ }
45
+ }
46
+ import PropTypes from 'prop-types';
7
47
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
8
48
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
9
49
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
@@ -13,12 +53,11 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
13
53
  import { injectIntl, FormattedMessage } from 'react-intl';
14
54
  import classnames from 'classnames';
15
55
  import {
16
- isEmpty, get, forEach, cloneDeep, debounce,
56
+ isEmpty, get, forEach, cloneDeep, debounce, pick,
17
57
  } from 'lodash';
18
58
  import { connect } from 'react-redux';
19
59
  import { createStructuredSelector } from 'reselect';
20
60
  import { bindActionCreators, compose } from 'redux';
21
- import styled from 'styled-components';
22
61
  import { GA } from '@capillarytech/cap-ui-utils';
23
62
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
24
63
 
@@ -32,6 +71,7 @@ import SlideBoxContent from './SlideBoxContent';
32
71
  import * as constants from './constants';
33
72
  import * as commonUtil from '../../utils/common';
34
73
  import { gtmPush } from '../../utils/gtmTrackers';
74
+ import { normalizeRcsMessageContentForApi } from '../../utils/rcsPayloadUtils';
35
75
  import './index.scss';
36
76
  import * as templateActions from '../Templates/actions';
37
77
  import * as globalActions from '../Cap/actions';
@@ -47,6 +87,9 @@ import {
47
87
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
48
88
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
49
89
  import { RCS_STATUSES } 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';
50
93
  import { CREATIVE } from '../Facebook/constants';
51
94
  import { LOYALTY } from '../App/constants';
52
95
  import {
@@ -61,6 +104,11 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
61
104
  import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
62
105
  import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
63
106
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
107
+ import SlideBoxWrapper from './CreativesSlideBoxWrapper';
108
+ import {
109
+ computeLiquidFooterUpdateFromFormBuilder,
110
+ getSlideBoxWrapperMarginFromLiquidErrors,
111
+ } from './embeddedSlideboxUtils';
64
112
 
65
113
  import {
66
114
  transformChannelPayload,
@@ -69,51 +117,24 @@ import {
69
117
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
70
118
  import { BIG_HTML } from '../InApp/constants';
71
119
 
72
- /**
73
- * Returns true if value is "deep empty": no errors present.
74
- * - null/undefined: empty
75
- * - string: empty if length === 0
76
- * - array: empty if length === 0
77
- * - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
78
- */
79
- function isDeepEmpty(value) {
80
- if (value == null) return true;
81
- if (typeof value === 'string') return value.length === 0;
82
- if (Array.isArray(value)) return value.length === 0;
83
- if (typeof value === 'object') {
84
- return Object.values(value).every(isDeepEmpty);
85
- }
86
- return false;
87
- }
88
-
89
120
  const classPrefix = 'add-creatives-section';
90
121
  const CREATIVES_CONTAINER = 'creativesContainer';
91
122
 
92
- const SlideBoxWrapper = styled.div`
93
- .cap-slide-box-v2-container{
94
- .slidebox-header, .slidebox-content-container{
95
- margin-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
96
- padding: 0 rem;
97
- &.has-footer{
98
- overflow-x: hidden;
99
- }
100
- }
101
- .slidebox-footer{
102
- /* Only apply margin-bottom to footer when ErrorInfoNote is shown in footer (BEE editor) */
103
- /* For HTML Editor, errors are shown in ValidationErrorDisplay (inside content area), so no footer margin needed */
104
- margin-bottom: ${({ shouldApplyFooterMargin }) => (shouldApplyFooterMargin ? `${CAP_SPACE_16}` : '0')};
105
- padding: 0 rem;
106
- &.has-footer{
107
- overflow-x: hidden;
108
- }
109
- }
110
- }
111
- `;
112
123
  export class Creatives extends React.Component {
113
124
  constructor(props) {
114
125
  super(props);
115
126
 
116
- const initialSlidBoxContent = this.getSlideBoxContent({ mode: props.creativesMode, templateData: props.templateData, isFullMode: props.isFullMode });
127
+ const useLocalTemplates = get(
128
+ props,
129
+ 'localTemplatesConfig.useLocalTemplates',
130
+ get(props, 'useLocalTemplates', false),
131
+ );
132
+ const initialSlidBoxContent = this.getSlideBoxContent({
133
+ mode: props.creativesMode,
134
+ templateData: props.templateData,
135
+ isFullMode: props.isFullMode,
136
+ useLocalTemplates,
137
+ });
117
138
 
118
139
  this.state = {
119
140
  isLoadingContent: true,
@@ -160,7 +181,13 @@ export class Creatives extends React.Component {
160
181
  }
161
182
 
162
183
  componentWillUnmount() {
163
- if (get(this.props, 'location.query.type', '') === "embedded") {
184
+ const isEmbedded = get(this.props, 'location.query.type', '') === "embedded";
185
+ const useLocalTemplates = get(
186
+ this.props,
187
+ 'localTemplatesConfig.useLocalTemplates',
188
+ get(this.props, 'useLocalTemplates', false),
189
+ );
190
+ if (isEmbedded && !useLocalTemplates) {
164
191
  this.props.templateActions.resetTemplateStoreData();
165
192
  }
166
193
  this.props.globalActions.clearMetaEntities();
@@ -768,15 +795,69 @@ export class Creatives extends React.Component {
768
795
  smsFallBackContent = {},
769
796
  creativeName = "",
770
797
  channel = constants.RCS,
798
+ rcsCardVarMapped,
771
799
  accountId = "",
772
800
  } = templateData || {};
773
- const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
801
+ const { isFullMode: isFullModeForRcsPayload } = this.props;
802
+ const isCarouselRcs = (rcsContent?.cardType || '').toString().toLowerCase() === 'carousel';
803
+ const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
804
+ const {
805
+ cardDisplayTitle: _omitDispTitleIn,
806
+ cardDisplayDescription: _omitDispDescIn,
807
+ ...cardContent
808
+ } = firstCardIn;
774
809
  const Status = RCS_STATUSES.approved || '';
810
+ const mergedCardVarMapped = (() => {
811
+ const nestedCardVarMapped = cardContent?.cardVarMapped;
812
+ const rootMirrorCardVarMapped = rcsCardVarMapped;
813
+ const nestedRecord =
814
+ nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
815
+ ? nestedCardVarMapped
816
+ : {};
817
+ const rootRecord =
818
+ rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
819
+ ? rootMirrorCardVarMapped
820
+ : {};
821
+ const mergedFromRootAndNested = {
822
+ ...pickRcsCardVarMappedEntries(rootRecord),
823
+ ...pickRcsCardVarMappedEntries(nestedRecord),
824
+ };
825
+ return Object.keys(mergedFromRootAndNested).length > 0
826
+ ? mergedFromRootAndNested
827
+ : null;
828
+ })();
829
+ // Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
830
+ // slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
831
+ // Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
832
+ const includeRootRcsCardVarMapped =
833
+ mergedCardVarMapped && isFullModeForRcsPayload === true;
834
+
835
+ const builtCardContent = isCarouselRcs
836
+ ? (rcsContent.cardContent || []).map((card, idx) => {
837
+ const {
838
+ cardDisplayTitle: _dt,
839
+ cardDisplayDescription: _dd,
840
+ ...restCard
841
+ } = card || {};
842
+ return {
843
+ ...restCard,
844
+ ...(idx === 0 && mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
845
+ Status,
846
+ };
847
+ })
848
+ : [
849
+ {
850
+ ...cardContent,
851
+ ...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
852
+ Status,
853
+ },
854
+ ];
775
855
 
776
856
  creativesTemplateData = {
777
857
  type: channel,
778
858
  edit: true,
779
859
  name: creativeName,
860
+ ...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
780
861
  versions: {
781
862
  base: {
782
863
  content: {
@@ -784,12 +865,7 @@ export class Creatives extends React.Component {
784
865
  rcsContent: {
785
866
  ...rcsContent,
786
867
  ...(accountId && !isFullMode && { accountId }),
787
- cardContent: [
788
- {
789
- ...cardContent,
790
- Status,
791
- },
792
- ],
868
+ cardContent: builtCardContent,
793
869
  },
794
870
  smsFallBackContent,
795
871
  },
@@ -937,7 +1013,10 @@ export class Creatives extends React.Component {
937
1013
  return newExpandableDetails;
938
1014
  }
939
1015
 
940
- getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
1016
+ getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
1017
+ const channel = String(
1018
+ channelParam || template?.type || get(template, 'value.type') || '',
1019
+ ).toUpperCase();
941
1020
  let templateData = { channel };
942
1021
  switch (channel) {
943
1022
  case constants.SMS:
@@ -1284,28 +1363,104 @@ export class Creatives extends React.Component {
1284
1363
  break;
1285
1364
  case constants.RCS:
1286
1365
  if (template.value) {
1287
- const { name = "", versions = {} } = {
1288
- } = template.value || {};
1289
- const smsFallBackContent = get(versions, 'base.content.RCS.smsFallBackContent', {});
1366
+ const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
1367
+ const { name = "", versions = {} } = template.value || {};
1368
+ const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
1369
+ const fromRecords = {
1370
+ ...(templateRecords?.smsFallBackContent || {}),
1371
+ ...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
1372
+ };
1373
+ const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
1374
+ if (
1375
+ !smsFallbackPayload
1376
+ || typeof smsFallbackPayload !== 'object'
1377
+ || Object.keys(smsFallbackPayload).length === 0
1378
+ ) {
1379
+ return false;
1380
+ }
1381
+ const fallbackBodyText = String(
1382
+ smsFallbackPayload.smsContent
1383
+ ?? smsFallbackPayload.smsTemplateContent
1384
+ ?? smsFallbackPayload.message
1385
+ ?? smsFallbackPayload.content
1386
+ ?? '',
1387
+ ).trim();
1388
+ const fallbackTemplateName = String(
1389
+ smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
1390
+ ).trim();
1391
+ const rcsSmsFallbackVarMapped =
1392
+ smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
1393
+ const hasVarMappedEntries =
1394
+ rcsSmsFallbackVarMapped != null
1395
+ && typeof rcsSmsFallbackVarMapped === 'object'
1396
+ && Object.keys(rcsSmsFallbackVarMapped).length > 0;
1397
+ return (
1398
+ fallbackBodyText !== ''
1399
+ || fallbackTemplateName !== ''
1400
+ || hasVarMappedEntries
1401
+ );
1402
+ };
1403
+ // If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
1404
+ const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
1405
+ ? { ...fromRecords, ...fromSubmit }
1406
+ : { ...fromSubmit, ...fromRecords };
1290
1407
  const {
1291
- cardContent = [],
1408
+ cardContent: cardContentFromSubmit = [],
1292
1409
  contentType = "",
1293
1410
  cardType = "",
1294
1411
  cardSettings = {},
1295
1412
  accountId = "",
1296
1413
  } = get(versions, 'base.content.RCS.rcsContent', {});
1414
+ const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1415
+ const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1416
+ ? cardContentFromSubmit[0]
1417
+ : null;
1418
+ const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1419
+ cardContentFromSubmit,
1420
+ rootRcsCardVarMappedFromSubmit,
1421
+ isFullModeForRcsConsumerPayload,
1422
+ );
1297
1423
  const rcsContent = {
1298
1424
  contentType,
1299
1425
  cardType,
1300
1426
  cardSettings,
1301
1427
  cardContent,
1302
1428
  };
1429
+ const cardVarMappedFromFirstRcsCard =
1430
+ firstCardFromSubmit?.cardVarMapped != null
1431
+ && typeof firstCardFromSubmit.cardVarMapped === 'object'
1432
+ ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1433
+ : null;
1434
+ const includeRootRcsCardVarMappedOnConsumerPayload =
1435
+ cardVarMappedFromFirstRcsCard
1436
+ && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
1437
+ && isFullModeForRcsConsumerPayload === true;
1303
1438
  templateData = {
1304
1439
  channel,
1305
1440
  creativeName: name,
1306
1441
  rcsContent,
1307
1442
  accountId,
1443
+ ...(includeRootRcsCardVarMappedOnConsumerPayload
1444
+ ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1445
+ : {}),
1308
1446
  };
1447
+ // Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
1448
+ // so reopening the editor restores fallback text and tag mappings.
1449
+ // cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
1450
+ if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
1451
+ const smsText =
1452
+ smsFallBackContent.message
1453
+ ?? smsFallBackContent.smsContent
1454
+ ?? smsFallBackContent.smsTemplateContent
1455
+ ?? '';
1456
+ templateData.smsFallBackContent = {
1457
+ ...smsFallBackContent,
1458
+ ...(String(smsText).trim() !== ''
1459
+ ? { message: String(smsText).trim() }
1460
+ : {}),
1461
+ };
1462
+ }
1463
+ normalizeRcsMessageContentForApi(templateData);
1309
1464
  }
1310
1465
  break;
1311
1466
  case constants.ZALO:
@@ -1423,7 +1578,10 @@ export class Creatives extends React.Component {
1423
1578
  return templateData;
1424
1579
  };
1425
1580
 
1426
- getSlideBoxContent({ mode, templateData, isFullMode }) {
1581
+ getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
1582
+ if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
1583
+ return 'templates';
1584
+ }
1427
1585
  let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
1428
1586
  if (mode === 'create' && isFullMode) {
1429
1587
  creativesMode = 'createTemplate';
@@ -1511,24 +1669,110 @@ export class Creatives extends React.Component {
1511
1669
  getFormData = (template) => {
1512
1670
  // Always reset isGetFormData so the child does not re-send form data on every re-render
1513
1671
  // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1514
- this.setState({ isGetFormData: false });
1515
- if (template.validity) {
1516
- this.setState(
1517
- {},
1518
- () => {
1519
- const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1520
- const channel = templateData.type;
1521
- const creativesData = this.getCreativesData(channel, template, templateData);// convers data to consumer understandable format
1522
- creativesData.then((data) => {
1523
- this.logGTMEvent(channel, data);
1524
- this.processCentralCommsMetaId(channel, data);
1672
+ this.setState(
1673
+ (prevState) => {
1674
+ const next = { isGetFormData: false };
1675
+ if (!template.validity) {
1676
+ return next;
1677
+ }
1678
+ const baseTd = prevState.templateData || template;
1679
+ const channel = (
1680
+ baseTd?.type
1681
+ || template?.type
1682
+ || get(template, 'value.type')
1683
+ || ''
1684
+ ).toUpperCase();
1685
+ // Library mode: persist last submitted creatives shape so reopening still hydrates the editor
1686
+ // (parent may not merge getCreativesData back into templateData).
1687
+ if (this.props.isFullMode === false && template.value) {
1688
+ if (channel === constants.RCS) {
1689
+ const smsFallBackFromPayload = get(
1690
+ template.value,
1691
+ 'versions.base.content.RCS.smsFallBackContent',
1692
+ );
1693
+ const rcsCardVarMappedFromPayload = get(
1694
+ template.value,
1695
+ 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
1696
+ );
1697
+ next.templateData = {
1698
+ ...baseTd,
1699
+ type: constants.RCS,
1700
+ name: template?.value?.name,
1701
+ versions: template?.value?.versions,
1702
+ ...(smsFallBackFromPayload != null
1703
+ && typeof smsFallBackFromPayload === 'object'
1704
+ && Object.keys(smsFallBackFromPayload).length > 0
1705
+ ? { smsFallBackContent: smsFallBackFromPayload }
1706
+ : {}),
1707
+ ...(rcsCardVarMappedFromPayload != null
1708
+ && typeof rcsCardVarMappedFromPayload === 'object'
1709
+ ? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
1710
+ : {}),
1711
+ };
1712
+ if (template._id) {
1713
+ next.templateData._id = template._id;
1714
+ }
1715
+ } else if (channel === constants.SMS) {
1716
+ const submittedSmsTemplateValue = template?.value;
1717
+ const smsVersions =
1718
+ submittedSmsTemplateValue?.history != null
1719
+ ? submittedSmsTemplateValue
1720
+ : {
1721
+ base: submittedSmsTemplateValue?.base,
1722
+ history: submittedSmsTemplateValue?.base
1723
+ ? [submittedSmsTemplateValue.base]
1724
+ : [],
1725
+ };
1726
+ next.templateData = {
1727
+ ...baseTd,
1728
+ type: constants.SMS,
1729
+ name: baseTd?.name || 'Campaign message SMS content',
1730
+ versions: smsVersions,
1731
+ };
1732
+ if (template?._id) {
1733
+ next.templateData._id = template._id;
1734
+ }
1735
+ }
1736
+ }
1737
+ return next;
1738
+ },
1739
+ () => {
1740
+ if (!template.validity) {
1741
+ return;
1742
+ }
1743
+ const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1744
+ const channelForConsumer = String(
1745
+ templateData.type
1746
+ || template.type
1747
+ || get(template, 'value.type')
1748
+ || '',
1749
+ ).toUpperCase();
1750
+ const creativesData = this.getCreativesData(
1751
+ channelForConsumer,
1752
+ template,
1753
+ this.state.templateData || template,
1754
+ );// convers data to consumer understandable format
1755
+ creativesData.then((data) => {
1756
+ this.logGTMEvent(channelForConsumer, data);
1757
+ this.processCentralCommsMetaId(channelForConsumer, data, {
1758
+ closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
1525
1759
  });
1526
- },
1527
- );
1528
- }
1760
+ });
1761
+ },
1762
+ );
1529
1763
  }
1530
1764
 
1531
- processCentralCommsMetaId = (channel, creativesData) => {
1765
+ processCentralCommsMetaId = (channel, creativesData, options = {}) => {
1766
+ const { closeSlideBoxAfterSubmit = false } = options;
1767
+ const maybeCloseLibrarySlideBox = () => {
1768
+ if (
1769
+ closeSlideBoxAfterSubmit
1770
+ && this.props.isFullMode === false
1771
+ && typeof this.handleCloseSlideBox === 'function'
1772
+ ) {
1773
+ this.handleCloseSlideBox();
1774
+ }
1775
+ };
1532
1776
  // Create the payload for the centralcommnsmetaId API call
1533
1777
  const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1534
1778
  const { actionName, setMetaData = () => { } } = loyaltyMetaData;
@@ -1554,6 +1798,7 @@ export class Creatives extends React.Component {
1554
1798
  if (result?.status?.code === 200) {
1555
1799
  setMetaData(result);
1556
1800
  this.props.getCreativesData(creativesData);
1801
+ maybeCloseLibrarySlideBox();
1557
1802
  } else {
1558
1803
  CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
1559
1804
  }
@@ -1564,6 +1809,7 @@ export class Creatives extends React.Component {
1564
1809
  } else {
1565
1810
  // If not a loyalty module or different action, should work as usual
1566
1811
  this.props.getCreativesData(creativesData);
1812
+ maybeCloseLibrarySlideBox();
1567
1813
  }
1568
1814
  };
1569
1815
 
@@ -1596,7 +1842,9 @@ export class Creatives extends React.Component {
1596
1842
  }
1597
1843
  this.setState((prevState) => ({
1598
1844
  ...prevState,
1599
- templateData: undefined,
1845
+ // Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
1846
+ // Undefined isFullMode defaults to full-mode close behavior (clear templateData).
1847
+ ...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
1600
1848
  showSlideBox: false,
1601
1849
  liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1602
1850
  isLiquidValidationError: false,
@@ -1753,30 +2001,22 @@ export class Creatives extends React.Component {
1753
2001
  } />
1754
2002
  )
1755
2003
 
1756
- templateNameComponentInput = ({ formData, onFormDataChange, name }) => {
1757
- // Use local state for immediate UI feedback, fallback to prop value
1758
- const displayValue = this.state.localTemplateName !== '' ? this.state.localTemplateName : name;
1759
-
1760
- return (
1761
- <CapInput
1762
- value={displayValue}
1763
- suffix={<span />}
1764
- onBlur={() => {
1765
- this.setState({
1766
- isEditName: false,
1767
- localTemplateName: '', // Clear local state on blur
1768
- }, () => {
1769
- this.showTemplateName({ formData, onFormDataChange });
1770
- });
1771
- }}
1772
- onChange={(ev) => {
1773
- const { value } = ev.currentTarget;
1774
- // Use optimized update for better performance
1775
- this.updateTemplateNameImmediately(value, formData, onFormDataChange);
1776
- }}
1777
- />
1778
- );
1779
- }
2004
+ templateNameComponentInput = ({ formData, onFormDataChange, name }) => (
2005
+ <TemplateNameInputField
2006
+ initialValue={name}
2007
+ suffix={<span />}
2008
+ onBlur={(committedValue) => {
2009
+ this.performTemplateNameUpdate(committedValue, formData, onFormDataChange);
2010
+ this.setState({ isEditName: false });
2011
+ }}
2012
+ onChange={(value) => {
2013
+ const isEmptyTemplateName = !value.trim();
2014
+ if (this.state.isTemplateNameEmpty !== isEmptyTemplateName) {
2015
+ this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
2016
+ }
2017
+ }}
2018
+ />
2019
+ )
1780
2020
 
1781
2021
  showTemplateName = ({ formData, onFormDataChange }) => { //gets called from email/index after template data is fetched
1782
2022
  const {
@@ -1807,21 +2047,17 @@ export class Creatives extends React.Component {
1807
2047
  }
1808
2048
 
1809
2049
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1810
- const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1811
- const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1812
- const hasLiquid = !isDeepEmpty(liquidMsgs);
1813
- const hasStandard = !isDeepEmpty(standardMsgs);
1814
- const isLiquidValidationError = hasLiquid || hasStandard;
1815
- // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1816
- const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1817
- if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1818
- return;
1819
- }
1820
- this.setState({
1821
- isLiquidValidationError,
1822
- liquidErrorMessage: errorMessagesFromFormBuilder,
1823
- activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1824
- });
2050
+ const next = computeLiquidFooterUpdateFromFormBuilder(
2051
+ errorMessagesFromFormBuilder,
2052
+ this.state.liquidErrorMessage,
2053
+ currentFormBuilderTab,
2054
+ {
2055
+ previousIsLiquidValidationError: this.state.isLiquidValidationError,
2056
+ currentChannelUpper: this.state.currentChannel?.toUpperCase(),
2057
+ },
2058
+ );
2059
+ if (next == null) return;
2060
+ this.setState(next);
1825
2061
  }
1826
2062
 
1827
2063
  // Callback to update HTML Editor validation state (called from EmailWrapper)
@@ -1944,6 +2180,11 @@ export class Creatives extends React.Component {
1944
2180
  inAppEditorType,
1945
2181
  htmlEditorValidationState,
1946
2182
  } = this.state;
2183
+ const useLocalTemplates = get(
2184
+ this.props,
2185
+ 'localTemplatesConfig.useLocalTemplates',
2186
+ get(this.props, 'useLocalTemplates', false),
2187
+ );
1947
2188
  const {
1948
2189
  isFullMode,
1949
2190
  creativesMode,
@@ -1962,7 +2203,6 @@ export class Creatives extends React.Component {
1962
2203
  smsRegister,
1963
2204
  enableNewChannels,
1964
2205
  eventContextTags,
1965
- waitEventContextTags = {},
1966
2206
  isLoyaltyModule,
1967
2207
  loyaltyMetaData = {},
1968
2208
  } = this.props;
@@ -1996,14 +2236,7 @@ export class Creatives extends React.Component {
1996
2236
  // IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
1997
2237
  const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
1998
2238
 
1999
- // Calculate margin for header/content (always apply if there are errors, regardless of editor type)
2000
- const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
2001
- ? CAP_SPACE_64
2002
- : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
2003
- ? CAP_SPACE_56
2004
- : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
2005
- ? CAP_SPACE_32
2006
- : 0;
2239
+ const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
2007
2240
  /* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
2008
2241
 
2009
2242
  // Compute anonymous user type and channel restrictions
@@ -2032,7 +2265,10 @@ export class Creatives extends React.Component {
2032
2265
  <SlideBoxWrapper
2033
2266
  slideBoxWrapperMargin={slideBoxWrapperMargin}
2034
2267
  shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
2035
- className={classnames(`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`)}
2268
+ className={classnames(
2269
+ `${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
2270
+ useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
2271
+ )}
2036
2272
  >
2037
2273
  <CapSlideBox
2038
2274
  header={
@@ -2057,12 +2293,13 @@ export class Creatives extends React.Component {
2057
2293
  smsRegister={smsRegister}
2058
2294
  handleClose={this.handleCloseSlideBox}
2059
2295
  moduleType={this.props.messageDetails?.type}
2296
+ useLocalTemplates={useLocalTemplates}
2060
2297
  />
2061
2298
  )}
2062
2299
  content={(
2063
2300
  <SlideBoxContent
2064
2301
  key="creatives-container-slidebox-content"
2065
- onSelectTemplate={this.onSelectTemplate}
2302
+ onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
2066
2303
  onCreateComplete={getCreativesData}
2067
2304
  onPreviewTemplate={this.onPreviewTemplate}
2068
2305
  slidBoxContent={slidBoxContent}
@@ -2130,7 +2367,6 @@ export class Creatives extends React.Component {
2130
2367
  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.
2131
2368
  hostName={this.props?.hostName || ''}
2132
2369
  eventContextTags={eventContextTags}
2133
- waitEventContextTags={waitEventContextTags}
2134
2370
  isLoyaltyModule={isLoyaltyModule}
2135
2371
  loyaltyMetaData={loyaltyMetaData}
2136
2372
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
@@ -2139,7 +2375,8 @@ export class Creatives extends React.Component {
2139
2375
  isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
2140
2376
  onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
2141
2377
  onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
2142
- />
2378
+ localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
2379
+ />
2143
2380
  )}
2144
2381
  footer={this.shouldShowFooter() ? (
2145
2382
  <SlideBoxFooter
@@ -2224,13 +2461,31 @@ Creatives.propTypes = {
2224
2461
  orgUnitId: PropTypes.number,
2225
2462
  hostName: PropTypes.string,
2226
2463
  eventContextTags: PropTypes.array,
2227
- waitEventContextTags: PropTypes.object,
2228
2464
  loyaltyTagFetchingDependencies: PropTypes.object,
2229
2465
  customerType: PropTypes.string,
2230
2466
  intl: PropTypes.shape({
2231
2467
  formatMessage: PropTypes.func,
2232
2468
  }),
2233
2469
  stopValidation: PropTypes.func,
2470
+ // Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
2471
+ // All optional. Pass either localTemplatesConfig (object) or individual props below.
2472
+ localTemplatesConfig: PropTypes.shape({
2473
+ useLocalTemplates: PropTypes.bool,
2474
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2475
+ localTemplatesLoading: PropTypes.bool,
2476
+ localTemplatesFilterContent: PropTypes.node,
2477
+ localTemplatesSentinelContent: PropTypes.node,
2478
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2479
+ localTemplatesUseSkeleton: PropTypes.bool,
2480
+ }),
2481
+ useLocalTemplates: PropTypes.bool,
2482
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2483
+ localTemplatesLoading: PropTypes.bool,
2484
+ localTemplatesFilterContent: PropTypes.node,
2485
+ localTemplatesSentinelContent: PropTypes.node,
2486
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2487
+ localTemplatesUseSkeleton: PropTypes.bool,
2488
+ onSelectTemplate: PropTypes.func,
2234
2489
  };
2235
2490
  const mapStatesToProps = () => createStructuredSelector({
2236
2491
  isLoading: isLoadingSelector(),