@capillarytech/creatives-library 8.0.316 → 8.0.317-alpha.0

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 (172) 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/tagValidations.test.js +34 -0
  8. package/utils/tests/templateVarUtils.test.js +160 -0
  9. package/v2Components/CapTagList/index.js +25 -22
  10. package/v2Components/CapTagList/style.scss +48 -0
  11. package/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  12. package/v2Components/CapTagListWithInput/index.js +4 -0
  13. package/v2Components/CapWhatsappCTA/index.js +2 -0
  14. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  18. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  19. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  20. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  21. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  24. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  25. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  26. package/v2Components/CommonTestAndPreview/index.js +693 -155
  27. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  28. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  29. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  30. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  31. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  32. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  33. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  34. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  35. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  36. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  37. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  38. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  39. package/v2Components/FormBuilder/index.js +14 -1
  40. package/v2Components/HtmlEditor/HTMLEditor.js +6 -1
  41. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -0
  42. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +927 -2
  43. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +3 -0
  44. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  45. package/v2Components/SmsFallback/constants.js +73 -0
  46. package/v2Components/SmsFallback/index.js +956 -0
  47. package/v2Components/SmsFallback/index.scss +265 -0
  48. package/v2Components/SmsFallback/messages.js +78 -0
  49. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  50. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  51. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  52. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  53. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  54. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  55. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  56. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  57. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  58. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  59. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  60. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  61. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  62. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  63. package/v2Containers/BeeEditor/index.js +3 -0
  64. package/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  65. package/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  66. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  67. package/v2Containers/CommunicationFlow/constants.js +200 -0
  68. package/v2Containers/CommunicationFlow/index.js +102 -0
  69. package/v2Containers/CommunicationFlow/messages.js +346 -0
  70. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  71. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  72. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  73. package/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  74. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  75. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  76. package/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  77. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  78. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  79. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  80. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  81. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  82. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  83. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  84. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  85. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  86. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  87. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  88. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  89. package/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  90. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  91. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  92. package/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  93. package/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  94. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  95. package/v2Containers/CreativesContainer/SlideBoxContent.js +64 -5
  96. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  97. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  98. package/v2Containers/CreativesContainer/constants.js +12 -0
  99. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  100. package/v2Containers/CreativesContainer/index.js +289 -93
  101. package/v2Containers/CreativesContainer/index.scss +51 -1
  102. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  103. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +104 -0
  104. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  105. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  106. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  107. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  108. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  109. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  110. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  111. package/v2Containers/Email/index.js +1 -0
  112. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +7 -1
  113. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  114. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +20 -2
  115. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  116. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +3 -0
  117. package/v2Containers/EmailWrapper/index.js +4 -0
  118. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  119. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +9 -0
  120. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  121. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  122. package/v2Containers/InAppWrapper/index.js +3 -0
  123. package/v2Containers/MobilePush/Create/index.js +2 -0
  124. package/v2Containers/MobilePush/Edit/index.js +2 -0
  125. package/v2Containers/MobilepushWrapper/index.js +3 -1
  126. package/v2Containers/Rcs/constants.js +32 -1
  127. package/v2Containers/Rcs/index.js +951 -873
  128. package/v2Containers/Rcs/index.scss +85 -6
  129. package/v2Containers/Rcs/messages.js +10 -1
  130. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  131. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  132. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  133. package/v2Containers/Rcs/tests/index.test.js +41 -38
  134. package/v2Containers/Rcs/tests/mockData.js +38 -0
  135. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  136. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  137. package/v2Containers/Rcs/utils.js +358 -10
  138. package/v2Containers/Sms/Create/index.js +83 -36
  139. package/v2Containers/Sms/Edit/index.js +2 -0
  140. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  141. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  142. package/v2Containers/SmsTrai/Create/index.js +9 -4
  143. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  144. package/v2Containers/SmsTrai/Edit/index.js +611 -128
  145. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  146. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  147. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  148. package/v2Containers/SmsWrapper/index.js +39 -8
  149. package/v2Containers/TagList/index.js +47 -2
  150. package/v2Containers/TagList/messages.js +4 -0
  151. package/v2Containers/TagList/tests/TagList.test.js +122 -20
  152. package/v2Containers/TagList/tests/mockdata.js +17 -0
  153. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  154. package/v2Containers/Templates/_templates.scss +61 -2
  155. package/v2Containers/Templates/actions.js +11 -0
  156. package/v2Containers/Templates/constants.js +2 -0
  157. package/v2Containers/Templates/index.js +90 -40
  158. package/v2Containers/Templates/sagas.js +57 -12
  159. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  160. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  161. package/v2Containers/Templates/tests/sagas.test.js +193 -12
  162. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  163. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  164. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  165. package/v2Containers/TemplatesV2/index.js +86 -23
  166. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  167. package/v2Containers/Viber/index.js +5 -0
  168. package/v2Containers/WebPush/Create/hooks/useTagManagement.js +0 -2
  169. package/v2Containers/WebPush/Create/index.js +9 -1
  170. package/v2Containers/Whatsapp/index.js +8 -20
  171. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +598 -34
  172. package/v2Containers/Zalo/index.js +2 -0
@@ -1,9 +1,5 @@
1
1
  import React from 'react';
2
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
-
7
3
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
8
4
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
9
5
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
@@ -13,12 +9,11 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
13
9
  import { injectIntl, FormattedMessage } from 'react-intl';
14
10
  import classnames from 'classnames';
15
11
  import {
16
- isEmpty, get, forEach, cloneDeep, debounce,
12
+ isEmpty, get, forEach, cloneDeep, debounce, pick,
17
13
  } from 'lodash';
18
14
  import { connect } from 'react-redux';
19
15
  import { createStructuredSelector } from 'reselect';
20
16
  import { bindActionCreators, compose } from 'redux';
21
- import styled from 'styled-components';
22
17
  import { GA } from '@capillarytech/cap-ui-utils';
23
18
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
24
19
 
@@ -47,6 +42,8 @@ import {
47
42
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
48
43
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
49
44
  import { RCS_STATUSES } from '../Rcs/constants';
45
+ import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
46
+ import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
50
47
  import { CREATIVE } from '../Facebook/constants';
51
48
  import { LOYALTY } from '../App/constants';
52
49
  import {
@@ -61,6 +58,11 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
61
58
  import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
62
59
  import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
63
60
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
61
+ import SlideBoxWrapper from './CreativesSlideBoxWrapper';
62
+ import {
63
+ computeLiquidFooterUpdateFromFormBuilder,
64
+ getSlideBoxWrapperMarginFromLiquidErrors,
65
+ } from './embeddedSlideboxUtils';
64
66
 
65
67
  import {
66
68
  transformChannelPayload,
@@ -69,51 +71,24 @@ import {
69
71
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
70
72
  import { BIG_HTML } from '../InApp/constants';
71
73
 
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
74
  const classPrefix = 'add-creatives-section';
90
75
  const CREATIVES_CONTAINER = 'creativesContainer';
91
76
 
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
77
  export class Creatives extends React.Component {
113
78
  constructor(props) {
114
79
  super(props);
115
80
 
116
- const initialSlidBoxContent = this.getSlideBoxContent({ mode: props.creativesMode, templateData: props.templateData, isFullMode: props.isFullMode });
81
+ const useLocalTemplates = get(
82
+ props,
83
+ 'localTemplatesConfig.useLocalTemplates',
84
+ get(props, 'useLocalTemplates', false),
85
+ );
86
+ const initialSlidBoxContent = this.getSlideBoxContent({
87
+ mode: props.creativesMode,
88
+ templateData: props.templateData,
89
+ isFullMode: props.isFullMode,
90
+ useLocalTemplates,
91
+ });
117
92
 
118
93
  this.state = {
119
94
  isLoadingContent: true,
@@ -160,7 +135,13 @@ export class Creatives extends React.Component {
160
135
  }
161
136
 
162
137
  componentWillUnmount() {
163
- if (get(this.props, 'location.query.type', '') === "embedded") {
138
+ const isEmbedded = get(this.props, 'location.query.type', '') === "embedded";
139
+ const useLocalTemplates = get(
140
+ this.props,
141
+ 'localTemplatesConfig.useLocalTemplates',
142
+ get(this.props, 'useLocalTemplates', false),
143
+ );
144
+ if (isEmbedded && !useLocalTemplates) {
164
145
  this.props.templateActions.resetTemplateStoreData();
165
146
  }
166
147
  this.props.globalActions.clearMetaEntities();
@@ -762,14 +743,41 @@ export class Creatives extends React.Component {
762
743
  smsFallBackContent = {},
763
744
  creativeName = "",
764
745
  channel = constants.RCS,
746
+ rcsCardVarMapped,
765
747
  } = templateData || {};
766
- const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
748
+ const { isFullMode: isFullModeForRcsPayload } = this.props;
749
+ const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
750
+ const {
751
+ cardDisplayTitle: _omitDispTitleIn,
752
+ cardDisplayDescription: _omitDispDescIn,
753
+ ...cardContent
754
+ } = firstCardIn;
767
755
  const Status = RCS_STATUSES.approved || '';
756
+ const mergedCardVarMapped = (() => {
757
+ const nestedCardVarMapped = cardContent?.cardVarMapped;
758
+ const rootMirrorCardVarMapped = rcsCardVarMapped;
759
+ const nestedRecord =
760
+ nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
761
+ ? nestedCardVarMapped
762
+ : {};
763
+ const rootRecord =
764
+ rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
765
+ ? rootMirrorCardVarMapped
766
+ : {};
767
+ const mergedFromRootAndNested = { ...rootRecord, ...nestedRecord };
768
+ return Object.keys(mergedFromRootAndNested).length > 0 ? mergedFromRootAndNested : null;
769
+ })();
770
+ // Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
771
+ // slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
772
+ // Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
773
+ const includeRootRcsCardVarMapped =
774
+ mergedCardVarMapped && isFullModeForRcsPayload === true;
768
775
 
769
776
  creativesTemplateData = {
770
777
  type: channel,
771
778
  edit: true,
772
779
  name: creativeName,
780
+ ...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
773
781
  versions: {
774
782
  base: {
775
783
  content: {
@@ -779,6 +787,7 @@ export class Creatives extends React.Component {
779
787
  cardContent: [
780
788
  {
781
789
  ...cardContent,
790
+ ...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
782
791
  Status,
783
792
  },
784
793
  ],
@@ -929,7 +938,10 @@ export class Creatives extends React.Component {
929
938
  return newExpandableDetails;
930
939
  }
931
940
 
932
- getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
941
+ getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
942
+ const channel = String(
943
+ channelParam || template?.type || get(template, 'value.type') || '',
944
+ ).toUpperCase();
933
945
  let templateData = { channel };
934
946
  switch (channel) {
935
947
  case constants.SMS:
@@ -1271,26 +1283,101 @@ export class Creatives extends React.Component {
1271
1283
  break;
1272
1284
  case constants.RCS: {
1273
1285
  if (template.value) {
1274
- const { name = "", versions = {} } = {
1275
- } = template.value || {};
1276
- const smsFallBackContent = get(versions, 'base.content.RCS.smsFallBackContent', {});
1286
+ const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
1287
+ const { name = "", versions = {} } = template.value || {};
1288
+ const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
1289
+ const fromRecords = {
1290
+ ...(templateRecords?.smsFallBackContent || {}),
1291
+ ...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
1292
+ };
1293
+ const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
1294
+ if (
1295
+ !smsFallbackPayload
1296
+ || typeof smsFallbackPayload !== 'object'
1297
+ || Object.keys(smsFallbackPayload).length === 0
1298
+ ) {
1299
+ return false;
1300
+ }
1301
+ const fallbackBodyText = String(
1302
+ smsFallbackPayload.smsContent
1303
+ ?? smsFallbackPayload.smsTemplateContent
1304
+ ?? smsFallbackPayload.message
1305
+ ?? smsFallbackPayload.content
1306
+ ?? '',
1307
+ ).trim();
1308
+ const fallbackTemplateName = String(
1309
+ smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
1310
+ ).trim();
1311
+ const rcsSmsFallbackVarMapped =
1312
+ smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
1313
+ const hasVarMappedEntries =
1314
+ rcsSmsFallbackVarMapped != null
1315
+ && typeof rcsSmsFallbackVarMapped === 'object'
1316
+ && Object.keys(rcsSmsFallbackVarMapped).length > 0;
1317
+ return (
1318
+ fallbackBodyText !== ''
1319
+ || fallbackTemplateName !== ''
1320
+ || hasVarMappedEntries
1321
+ );
1322
+ };
1323
+ // If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
1324
+ const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
1325
+ ? { ...fromRecords, ...fromSubmit }
1326
+ : { ...fromSubmit, ...fromRecords };
1277
1327
  const {
1278
- cardContent = [],
1328
+ cardContent: cardContentFromSubmit = [],
1279
1329
  contentType = "",
1280
1330
  cardType = "",
1281
1331
  cardSettings = {},
1282
1332
  } = get(versions, 'base.content.RCS.rcsContent', {});
1333
+ const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1334
+ const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1335
+ ? cardContentFromSubmit[0]
1336
+ : null;
1337
+ const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1338
+ cardContentFromSubmit,
1339
+ rootRcsCardVarMappedFromSubmit,
1340
+ isFullModeForRcsConsumerPayload,
1341
+ );
1283
1342
  const rcsContent = {
1284
1343
  contentType,
1285
1344
  cardType,
1286
1345
  cardSettings,
1287
1346
  cardContent,
1288
1347
  };
1348
+ const cardVarMappedFromFirstRcsCard =
1349
+ firstCardFromSubmit?.cardVarMapped != null
1350
+ && typeof firstCardFromSubmit.cardVarMapped === 'object'
1351
+ ? firstCardFromSubmit.cardVarMapped
1352
+ : null;
1353
+ const includeRootRcsCardVarMappedOnConsumerPayload =
1354
+ cardVarMappedFromFirstRcsCard
1355
+ && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
1356
+ && isFullModeForRcsConsumerPayload === true;
1289
1357
  templateData = {
1290
1358
  channel,
1291
1359
  creativeName: name,
1292
1360
  rcsContent,
1361
+ ...(includeRootRcsCardVarMappedOnConsumerPayload
1362
+ ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1363
+ : {}),
1293
1364
  };
1365
+ // Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
1366
+ // so reopening the editor restores fallback text and tag mappings.
1367
+ // cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
1368
+ if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
1369
+ const smsText =
1370
+ smsFallBackContent.message
1371
+ ?? smsFallBackContent.smsContent
1372
+ ?? smsFallBackContent.smsTemplateContent
1373
+ ?? '';
1374
+ templateData.smsFallBackContent = {
1375
+ ...smsFallBackContent,
1376
+ ...(String(smsText).trim() !== ''
1377
+ ? { message: String(smsText).trim() }
1378
+ : {}),
1379
+ };
1380
+ }
1294
1381
  }
1295
1382
  }
1296
1383
  break;
@@ -1410,7 +1497,10 @@ export class Creatives extends React.Component {
1410
1497
  return templateData;
1411
1498
  };
1412
1499
 
1413
- getSlideBoxContent({ mode, templateData, isFullMode }) {
1500
+ getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
1501
+ if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
1502
+ return 'templates';
1503
+ }
1414
1504
  let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
1415
1505
  if (mode === 'create' && isFullMode) {
1416
1506
  creativesMode = 'createTemplate';
@@ -1498,24 +1588,110 @@ export class Creatives extends React.Component {
1498
1588
  getFormData = (template) => {
1499
1589
  // Always reset isGetFormData so the child does not re-send form data on every re-render
1500
1590
  // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1501
- this.setState({ isGetFormData: false });
1502
- if (template.validity) {
1503
- this.setState(
1504
- {},
1505
- () => {
1506
- const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1507
- const channel = templateData.type;
1508
- const creativesData = this.getCreativesData(channel, template, templateData);// convers data to consumer understandable format
1509
- creativesData.then((data) => {
1510
- this.logGTMEvent(channel, data);
1511
- this.processCentralCommsMetaId(channel, data);
1591
+ this.setState(
1592
+ (prevState) => {
1593
+ const next = { isGetFormData: false };
1594
+ if (!template.validity) {
1595
+ return next;
1596
+ }
1597
+ const baseTd = prevState.templateData || template;
1598
+ const channel = (
1599
+ baseTd?.type
1600
+ || template?.type
1601
+ || get(template, 'value.type')
1602
+ || ''
1603
+ ).toUpperCase();
1604
+ // Library mode: persist last submitted creatives shape so reopening still hydrates the editor
1605
+ // (parent may not merge getCreativesData back into templateData).
1606
+ if (this.props.isFullMode === false && template.value) {
1607
+ if (channel === constants.RCS) {
1608
+ const smsFallBackFromPayload = get(
1609
+ template.value,
1610
+ 'versions.base.content.RCS.smsFallBackContent',
1611
+ );
1612
+ const rcsCardVarMappedFromPayload = get(
1613
+ template.value,
1614
+ 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
1615
+ );
1616
+ next.templateData = {
1617
+ ...baseTd,
1618
+ type: constants.RCS,
1619
+ name: template?.value?.name,
1620
+ versions: template?.value?.versions,
1621
+ ...(smsFallBackFromPayload != null
1622
+ && typeof smsFallBackFromPayload === 'object'
1623
+ && Object.keys(smsFallBackFromPayload).length > 0
1624
+ ? { smsFallBackContent: smsFallBackFromPayload }
1625
+ : {}),
1626
+ ...(rcsCardVarMappedFromPayload != null
1627
+ && typeof rcsCardVarMappedFromPayload === 'object'
1628
+ ? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
1629
+ : {}),
1630
+ };
1631
+ if (template._id) {
1632
+ next.templateData._id = template._id;
1633
+ }
1634
+ } else if (channel === constants.SMS) {
1635
+ const submittedSmsTemplateValue = template?.value;
1636
+ const smsVersions =
1637
+ submittedSmsTemplateValue?.history != null
1638
+ ? submittedSmsTemplateValue
1639
+ : {
1640
+ base: submittedSmsTemplateValue?.base,
1641
+ history: submittedSmsTemplateValue?.base
1642
+ ? [submittedSmsTemplateValue.base]
1643
+ : [],
1644
+ };
1645
+ next.templateData = {
1646
+ ...baseTd,
1647
+ type: constants.SMS,
1648
+ name: baseTd?.name || 'Campaign message SMS content',
1649
+ versions: smsVersions,
1650
+ };
1651
+ if (template?._id) {
1652
+ next.templateData._id = template._id;
1653
+ }
1654
+ }
1655
+ }
1656
+ return next;
1657
+ },
1658
+ () => {
1659
+ if (!template.validity) {
1660
+ return;
1661
+ }
1662
+ const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1663
+ const channelForConsumer = String(
1664
+ templateData.type
1665
+ || template.type
1666
+ || get(template, 'value.type')
1667
+ || '',
1668
+ ).toUpperCase();
1669
+ const creativesData = this.getCreativesData(
1670
+ channelForConsumer,
1671
+ template,
1672
+ this.state.templateData || template,
1673
+ );// convers data to consumer understandable format
1674
+ creativesData.then((data) => {
1675
+ this.logGTMEvent(channelForConsumer, data);
1676
+ this.processCentralCommsMetaId(channelForConsumer, data, {
1677
+ closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
1512
1678
  });
1513
- },
1514
- );
1515
- }
1679
+ });
1680
+ },
1681
+ );
1516
1682
  }
1517
1683
 
1518
- processCentralCommsMetaId = (channel, creativesData) => {
1684
+ processCentralCommsMetaId = (channel, creativesData, options = {}) => {
1685
+ const { closeSlideBoxAfterSubmit = false } = options;
1686
+ const maybeCloseLibrarySlideBox = () => {
1687
+ if (
1688
+ closeSlideBoxAfterSubmit
1689
+ && this.props.isFullMode === false
1690
+ && typeof this.handleCloseSlideBox === 'function'
1691
+ ) {
1692
+ this.handleCloseSlideBox();
1693
+ }
1694
+ };
1519
1695
  // Create the payload for the centralcommnsmetaId API call
1520
1696
  const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1521
1697
  const { actionName, setMetaData = () => { } } = loyaltyMetaData;
@@ -1541,6 +1717,7 @@ export class Creatives extends React.Component {
1541
1717
  if (result?.status?.code === 200) {
1542
1718
  setMetaData(result);
1543
1719
  this.props.getCreativesData(creativesData);
1720
+ maybeCloseLibrarySlideBox();
1544
1721
  } else {
1545
1722
  CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
1546
1723
  }
@@ -1551,6 +1728,7 @@ export class Creatives extends React.Component {
1551
1728
  } else {
1552
1729
  // If not a loyalty module or different action, should work as usual
1553
1730
  this.props.getCreativesData(creativesData);
1731
+ maybeCloseLibrarySlideBox();
1554
1732
  }
1555
1733
  };
1556
1734
 
@@ -1583,7 +1761,9 @@ export class Creatives extends React.Component {
1583
1761
  }
1584
1762
  this.setState((prevState) => ({
1585
1763
  ...prevState,
1586
- templateData: undefined,
1764
+ // Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
1765
+ // Undefined isFullMode defaults to full-mode close behavior (clear templateData).
1766
+ ...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
1587
1767
  showSlideBox: false,
1588
1768
  liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1589
1769
  isLiquidValidationError: false,
@@ -1794,21 +1974,12 @@ export class Creatives extends React.Component {
1794
1974
  }
1795
1975
 
1796
1976
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1797
- const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1798
- const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1799
- const hasLiquid = !isDeepEmpty(liquidMsgs);
1800
- const hasStandard = !isDeepEmpty(standardMsgs);
1801
- const isLiquidValidationError = hasLiquid || hasStandard;
1802
- // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1803
- const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1804
- if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1805
- return;
1806
- }
1807
- this.setState({
1808
- isLiquidValidationError,
1809
- liquidErrorMessage: errorMessagesFromFormBuilder,
1810
- activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1977
+ const next = computeLiquidFooterUpdateFromFormBuilder(errorMessagesFromFormBuilder, currentFormBuilderTab, {
1978
+ previousIsLiquidValidationError: this.state.isLiquidValidationError,
1979
+ currentChannelUpper: this.state.currentChannel?.toUpperCase(),
1811
1980
  });
1981
+ if (next == null) return;
1982
+ this.setState(next);
1812
1983
  }
1813
1984
 
1814
1985
  // Callback to update HTML Editor validation state (called from EmailWrapper)
@@ -1931,6 +2102,11 @@ export class Creatives extends React.Component {
1931
2102
  inAppEditorType,
1932
2103
  htmlEditorValidationState,
1933
2104
  } = this.state;
2105
+ const useLocalTemplates = get(
2106
+ this.props,
2107
+ 'localTemplatesConfig.useLocalTemplates',
2108
+ get(this.props, 'useLocalTemplates', false),
2109
+ );
1934
2110
  const {
1935
2111
  isFullMode,
1936
2112
  creativesMode,
@@ -1949,6 +2125,7 @@ export class Creatives extends React.Component {
1949
2125
  smsRegister,
1950
2126
  enableNewChannels,
1951
2127
  eventContextTags,
2128
+ waitEventContextTags,
1952
2129
  isLoyaltyModule,
1953
2130
  loyaltyMetaData = {},
1954
2131
  } = this.props;
@@ -1982,14 +2159,7 @@ export class Creatives extends React.Component {
1982
2159
  // IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
1983
2160
  const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
1984
2161
 
1985
- // Calculate margin for header/content (always apply if there are errors, regardless of editor type)
1986
- const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
1987
- ? CAP_SPACE_64
1988
- : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
1989
- ? CAP_SPACE_56
1990
- : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
1991
- ? CAP_SPACE_32
1992
- : 0;
2162
+ const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
1993
2163
  /* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
1994
2164
 
1995
2165
  // Compute anonymous user type and channel restrictions
@@ -2018,7 +2188,10 @@ export class Creatives extends React.Component {
2018
2188
  <SlideBoxWrapper
2019
2189
  slideBoxWrapperMargin={slideBoxWrapperMargin}
2020
2190
  shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
2021
- className={classnames(`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`)}
2191
+ className={classnames(
2192
+ `${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
2193
+ useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
2194
+ )}
2022
2195
  >
2023
2196
  <CapSlideBox
2024
2197
  header={
@@ -2043,12 +2216,13 @@ export class Creatives extends React.Component {
2043
2216
  smsRegister={smsRegister}
2044
2217
  handleClose={this.handleCloseSlideBox}
2045
2218
  moduleType={this.props.messageDetails?.type}
2219
+ useLocalTemplates={useLocalTemplates}
2046
2220
  />
2047
2221
  )}
2048
2222
  content={(
2049
2223
  <SlideBoxContent
2050
2224
  key="creatives-container-slidebox-content"
2051
- onSelectTemplate={this.onSelectTemplate}
2225
+ onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
2052
2226
  onCreateComplete={getCreativesData}
2053
2227
  onPreviewTemplate={this.onPreviewTemplate}
2054
2228
  slidBoxContent={slidBoxContent}
@@ -2116,6 +2290,7 @@ export class Creatives extends React.Component {
2116
2290
  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.
2117
2291
  hostName={this.props?.hostName || ''}
2118
2292
  eventContextTags={eventContextTags}
2293
+ waitEventContextTags={waitEventContextTags}
2119
2294
  isLoyaltyModule={isLoyaltyModule}
2120
2295
  loyaltyMetaData={loyaltyMetaData}
2121
2296
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
@@ -2124,7 +2299,8 @@ export class Creatives extends React.Component {
2124
2299
  isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
2125
2300
  onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
2126
2301
  onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
2127
- />
2302
+ localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
2303
+ />
2128
2304
  )}
2129
2305
  footer={this.shouldShowFooter() ? (
2130
2306
  <SlideBoxFooter
@@ -2208,12 +2384,32 @@ Creatives.propTypes = {
2208
2384
  orgUnitId: PropTypes.number,
2209
2385
  hostName: PropTypes.string,
2210
2386
  eventContextTags: PropTypes.array,
2387
+ waitEventContextTags: PropTypes.object,
2211
2388
  loyaltyTagFetchingDependencies: PropTypes.object,
2212
2389
  customerType: PropTypes.string,
2213
2390
  intl: PropTypes.shape({
2214
2391
  formatMessage: PropTypes.func,
2215
2392
  }),
2216
2393
  stopValidation: PropTypes.func,
2394
+ // Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
2395
+ // All optional. Pass either localTemplatesConfig (object) or individual props below.
2396
+ localTemplatesConfig: PropTypes.shape({
2397
+ useLocalTemplates: PropTypes.bool,
2398
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2399
+ localTemplatesLoading: PropTypes.bool,
2400
+ localTemplatesFilterContent: PropTypes.node,
2401
+ localTemplatesSentinelContent: PropTypes.node,
2402
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2403
+ localTemplatesUseSkeleton: PropTypes.bool,
2404
+ }),
2405
+ useLocalTemplates: PropTypes.bool,
2406
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2407
+ localTemplatesLoading: PropTypes.bool,
2408
+ localTemplatesFilterContent: PropTypes.node,
2409
+ localTemplatesSentinelContent: PropTypes.node,
2410
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2411
+ localTemplatesUseSkeleton: PropTypes.bool,
2412
+ onSelectTemplate: PropTypes.func,
2217
2413
  };
2218
2414
  const mapStatesToProps = () => createStructuredSelector({
2219
2415
  isLoading: isLoadingSelector(),
@@ -2,6 +2,43 @@
2
2
 
3
3
  $classPrefix: add-creatives-section;
4
4
 
5
+ /* Local SMS template picker: fill slidebox height; global .v2-pagination-container uses 100vh-20rem and leaves a dead zone inside slideboxes */
6
+ .#{$classPrefix}.creatives-slidebox--local-sms-templates {
7
+ .cap-slide-box-v2-container {
8
+ display: flex;
9
+ flex-direction: column;
10
+ min-height: 0;
11
+ max-height: 100vh;
12
+ }
13
+
14
+ .slidebox-content-container {
15
+ flex: 1;
16
+ min-height: 0;
17
+ display: flex;
18
+ flex-direction: column;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .slidebox-content-container > div {
23
+ flex: 1;
24
+ min-height: 0;
25
+ display: flex;
26
+ flex-direction: column;
27
+ overflow: hidden;
28
+ }
29
+
30
+ /* TemplatesV2 root: fill slidebox so the template grid can flex instead of using 100vh-based pagination height */
31
+ .slidebox-content-container .creatives-templates-container--local-sms.library-mode {
32
+ flex: 1;
33
+ min-height: 0;
34
+ display: flex;
35
+ flex-direction: column;
36
+ overflow: hidden;
37
+ height: auto;
38
+ max-height: 100%;
39
+ }
40
+ }
41
+
5
42
  .#{$classPrefix} {
6
43
  &.creatives-library-mode{
7
44
  .sms-create-container, .sms-email-container{
@@ -80,5 +117,18 @@ $classPrefix: add-creatives-section;
80
117
  }
81
118
 
82
119
  .template-footer-width {
83
- width: 100%;;
120
+ width: 100%;
121
+ }
122
+
123
+ .slidebox-footer-actions {
124
+ display: flex;
125
+ flex-wrap: nowrap;
126
+ align-items: center;
127
+ gap: 0.75rem;
128
+ min-width: 0;
129
+
130
+ .ant-btn,
131
+ button {
132
+ flex-shrink: 0;
133
+ }
84
134
  }