@capillarytech/creatives-library 8.0.345-alpha.14 → 8.0.345-alpha.15

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 (129) hide show
  1. package/constants/unified.js +29 -0
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +13 -0
  4. package/utils/commonUtils.js +19 -1
  5. package/utils/rcsPayloadUtils.js +92 -0
  6. package/utils/templateVarUtils.js +201 -0
  7. package/utils/tests/templateVarUtils.test.js +204 -0
  8. package/v2Components/CapActionButton/constants.js +7 -0
  9. package/v2Components/CapActionButton/index.js +167 -109
  10. package/v2Components/CapActionButton/index.scss +157 -6
  11. package/v2Components/CapActionButton/messages.js +19 -3
  12. package/v2Components/CapActionButton/tests/index.test.js +41 -17
  13. package/v2Components/CapTagList/index.js +10 -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 +10 -5
  22. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +160 -15
  23. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +18 -0
  24. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +341 -76
  25. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  26. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +11 -0
  27. package/v2Components/CommonTestAndPreview/constants.js +38 -2
  28. package/v2Components/CommonTestAndPreview/index.js +676 -186
  29. package/v2Components/CommonTestAndPreview/messages.js +49 -3
  30. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  31. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  32. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +308 -284
  33. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +231 -65
  34. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  35. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +341 -0
  36. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +8 -1
  37. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +34 -13
  38. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +281 -283
  39. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  40. package/v2Components/CommonTestAndPreview/tests/index.test.js +132 -4
  41. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  42. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  43. package/v2Components/FormBuilder/index.js +8 -10
  44. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  45. package/v2Components/SmsFallback/constants.js +73 -0
  46. package/v2Components/SmsFallback/index.js +955 -0
  47. package/v2Components/SmsFallback/index.scss +265 -0
  48. package/v2Components/SmsFallback/messages.js +78 -0
  49. package/v2Components/SmsFallback/smsFallbackUtils.js +118 -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 +277 -0
  55. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  56. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  57. package/v2Components/TemplatePreview/_templatePreview.scss +33 -23
  58. package/v2Components/TemplatePreview/constants.js +2 -0
  59. package/v2Components/TemplatePreview/index.js +143 -28
  60. package/v2Components/TemplatePreview/tests/index.test.js +142 -0
  61. package/v2Components/TestAndPreviewSlidebox/index.js +13 -1
  62. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  63. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  64. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  65. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  66. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  67. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  68. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  69. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  70. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  71. package/v2Containers/CreativesContainer/constants.js +9 -0
  72. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  73. package/v2Containers/CreativesContainer/index.js +300 -103
  74. package/v2Containers/CreativesContainer/index.scss +51 -1
  75. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  76. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +78 -34
  77. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +79 -16
  78. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +357 -98
  80. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -15
  81. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  82. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  83. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  84. package/v2Containers/Email/reducer.js +3 -11
  85. package/v2Containers/Email/sagas.js +5 -9
  86. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +0 -4
  87. package/v2Containers/Email/tests/sagas.test.js +3 -21
  88. package/v2Containers/Rcs/constants.js +119 -8
  89. package/v2Containers/Rcs/index.js +2379 -807
  90. package/v2Containers/Rcs/index.js.rej +1336 -0
  91. package/v2Containers/Rcs/index.scss +276 -6
  92. package/v2Containers/Rcs/index.scss.rej +74 -0
  93. package/v2Containers/Rcs/messages.js +38 -3
  94. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +225 -0
  95. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +98018 -70073
  96. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  97. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +128 -0
  98. package/v2Containers/Rcs/tests/index.test.js +152 -121
  99. package/v2Containers/Rcs/tests/mockData.js +38 -0
  100. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +318 -0
  101. package/v2Containers/Rcs/tests/utils.test.js +646 -30
  102. package/v2Containers/Rcs/utils.js +478 -11
  103. package/v2Containers/Sms/Create/index.js +100 -40
  104. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  105. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  106. package/v2Containers/SmsTrai/Create/index.js +9 -4
  107. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  108. package/v2Containers/SmsTrai/Edit/index.js +636 -130
  109. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  110. package/v2Containers/SmsTrai/Edit/messages.js +14 -4
  111. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4328 -2375
  112. package/v2Containers/SmsWrapper/index.js +37 -8
  113. package/v2Containers/TagList/index.js +6 -0
  114. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  115. package/v2Containers/Templates/_templates.scss +163 -2
  116. package/v2Containers/Templates/actions.js +11 -0
  117. package/v2Containers/Templates/constants.js +2 -0
  118. package/v2Containers/Templates/index.js +119 -54
  119. package/v2Containers/Templates/sagas.js +57 -12
  120. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  121. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  122. package/v2Containers/Templates/tests/sagas.test.js +193 -123
  123. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  124. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  125. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  126. package/v2Containers/TemplatesV2/index.js +86 -23
  127. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  128. package/v2Containers/Whatsapp/index.js +3 -20
  129. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
@@ -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
 
@@ -32,6 +27,7 @@ import SlideBoxContent from './SlideBoxContent';
32
27
  import * as constants from './constants';
33
28
  import * as commonUtil from '../../utils/common';
34
29
  import { gtmPush } from '../../utils/gtmTrackers';
30
+ import { normalizeRcsMessageContentForApi } from '../../utils/rcsPayloadUtils';
35
31
  import './index.scss';
36
32
  import * as templateActions from '../Templates/actions';
37
33
  import * as globalActions from '../Cap/actions';
@@ -47,6 +43,9 @@ import {
47
43
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
48
44
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
49
45
  import { RCS_STATUSES } from '../Rcs/constants';
46
+ import { mapRcsCardContentForConsumerWithResolvedTags } from '../Rcs/utils';
47
+ import { pickRcsCardVarMappedEntries } from '../Rcs/rcsLibraryHydrationUtils';
48
+ import { RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
50
49
  import { CREATIVE } from '../Facebook/constants';
51
50
  import { LOYALTY } from '../App/constants';
52
51
  import {
@@ -61,6 +60,11 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
61
60
  import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
62
61
  import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
63
62
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
63
+ import SlideBoxWrapper from './CreativesSlideBoxWrapper';
64
+ import {
65
+ computeLiquidFooterUpdateFromFormBuilder,
66
+ getSlideBoxWrapperMarginFromLiquidErrors,
67
+ } from './embeddedSlideboxUtils';
64
68
 
65
69
  import {
66
70
  transformChannelPayload,
@@ -69,51 +73,24 @@ import {
69
73
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
70
74
  import { BIG_HTML } from '../InApp/constants';
71
75
 
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
76
  const classPrefix = 'add-creatives-section';
90
77
  const CREATIVES_CONTAINER = 'creativesContainer';
91
78
 
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
79
  export class Creatives extends React.Component {
113
80
  constructor(props) {
114
81
  super(props);
115
82
 
116
- const initialSlidBoxContent = this.getSlideBoxContent({ mode: props.creativesMode, templateData: props.templateData, isFullMode: props.isFullMode });
83
+ const useLocalTemplates = get(
84
+ props,
85
+ 'localTemplatesConfig.useLocalTemplates',
86
+ get(props, 'useLocalTemplates', false),
87
+ );
88
+ const initialSlidBoxContent = this.getSlideBoxContent({
89
+ mode: props.creativesMode,
90
+ templateData: props.templateData,
91
+ isFullMode: props.isFullMode,
92
+ useLocalTemplates,
93
+ });
117
94
 
118
95
  this.state = {
119
96
  isLoadingContent: true,
@@ -160,7 +137,13 @@ export class Creatives extends React.Component {
160
137
  }
161
138
 
162
139
  componentWillUnmount() {
163
- if (get(this.props, 'location.query.type', '') === "embedded") {
140
+ const isEmbedded = get(this.props, 'location.query.type', '') === "embedded";
141
+ const useLocalTemplates = get(
142
+ this.props,
143
+ 'localTemplatesConfig.useLocalTemplates',
144
+ get(this.props, 'useLocalTemplates', false),
145
+ );
146
+ if (isEmbedded && !useLocalTemplates) {
164
147
  this.props.templateActions.resetTemplateStoreData();
165
148
  }
166
149
  this.props.globalActions.clearMetaEntities();
@@ -762,25 +745,56 @@ export class Creatives extends React.Component {
762
745
  smsFallBackContent = {},
763
746
  creativeName = "",
764
747
  channel = constants.RCS,
765
- accountId = "",
748
+ rcsCardVarMapped,
766
749
  } = templateData || {};
767
- const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
750
+ const { isFullMode: isFullModeForRcsPayload } = this.props;
751
+ const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
752
+ const {
753
+ cardDisplayTitle: _omitDispTitleIn,
754
+ cardDisplayDescription: _omitDispDescIn,
755
+ ...cardContent
756
+ } = firstCardIn;
768
757
  const Status = RCS_STATUSES.approved || '';
758
+ const mergedCardVarMapped = (() => {
759
+ const nestedCardVarMapped = cardContent?.cardVarMapped;
760
+ const rootMirrorCardVarMapped = rcsCardVarMapped;
761
+ const nestedRecord =
762
+ nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
763
+ ? nestedCardVarMapped
764
+ : {};
765
+ const rootRecord =
766
+ rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
767
+ ? rootMirrorCardVarMapped
768
+ : {};
769
+ const mergedFromRootAndNested = {
770
+ ...pickRcsCardVarMappedEntries(rootRecord),
771
+ ...pickRcsCardVarMappedEntries(nestedRecord),
772
+ };
773
+ return Object.keys(mergedFromRootAndNested).length > 0
774
+ ? mergedFromRootAndNested
775
+ : null;
776
+ })();
777
+ // Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
778
+ // slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
779
+ // Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
780
+ const includeRootRcsCardVarMapped =
781
+ mergedCardVarMapped && isFullModeForRcsPayload === true;
769
782
 
770
783
  creativesTemplateData = {
771
784
  type: channel,
772
785
  edit: true,
773
786
  name: creativeName,
787
+ ...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
774
788
  versions: {
775
789
  base: {
776
790
  content: {
777
791
  RCS: {
778
792
  rcsContent: {
779
793
  ...rcsContent,
780
- ...(accountId && !isFullMode && { accountId }),
781
794
  cardContent: [
782
795
  {
783
796
  ...cardContent,
797
+ ...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
784
798
  Status,
785
799
  },
786
800
  ],
@@ -931,7 +945,10 @@ export class Creatives extends React.Component {
931
945
  return newExpandableDetails;
932
946
  }
933
947
 
934
- getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
948
+ getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
949
+ const channel = String(
950
+ channelParam || template?.type || get(template, 'value.type') || '',
951
+ ).toUpperCase();
935
952
  let templateData = { channel };
936
953
  switch (channel) {
937
954
  case constants.SMS:
@@ -1225,7 +1242,7 @@ export class Creatives extends React.Component {
1225
1242
  };
1226
1243
  }
1227
1244
  break;
1228
- case constants.FACEBOOK:
1245
+ case constants.FACEBOOK: {
1229
1246
  if (template.value) {
1230
1247
  const FacebookAd = template?.value?.versions?.base?.content?.FacebookAd;
1231
1248
  const { type } = FacebookAd[0];
@@ -1269,34 +1286,110 @@ export class Creatives extends React.Component {
1269
1286
  selectedMarketingObjective: template.value.selectedMarketingObjective,
1270
1287
  };
1271
1288
  }
1289
+ }
1272
1290
  break;
1273
- case constants.RCS:
1291
+ case constants.RCS: {
1274
1292
  if (template.value) {
1275
- const { name = "", versions = {} } = {
1276
- } = template.value || {};
1277
- const smsFallBackContent = get(versions, 'base.content.RCS.smsFallBackContent', {});
1293
+ const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
1294
+ const { name = "", versions = {} } = template.value || {};
1295
+ const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
1296
+ const fromRecords = {
1297
+ ...(templateRecords?.smsFallBackContent || {}),
1298
+ ...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
1299
+ };
1300
+ const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
1301
+ if (
1302
+ !smsFallbackPayload
1303
+ || typeof smsFallbackPayload !== 'object'
1304
+ || Object.keys(smsFallbackPayload).length === 0
1305
+ ) {
1306
+ return false;
1307
+ }
1308
+ const fallbackBodyText = String(
1309
+ smsFallbackPayload.smsContent
1310
+ ?? smsFallbackPayload.smsTemplateContent
1311
+ ?? smsFallbackPayload.message
1312
+ ?? smsFallbackPayload.content
1313
+ ?? '',
1314
+ ).trim();
1315
+ const fallbackTemplateName = String(
1316
+ smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
1317
+ ).trim();
1318
+ const rcsSmsFallbackVarMapped =
1319
+ smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
1320
+ const hasVarMappedEntries =
1321
+ rcsSmsFallbackVarMapped != null
1322
+ && typeof rcsSmsFallbackVarMapped === 'object'
1323
+ && Object.keys(rcsSmsFallbackVarMapped).length > 0;
1324
+ return (
1325
+ fallbackBodyText !== ''
1326
+ || fallbackTemplateName !== ''
1327
+ || hasVarMappedEntries
1328
+ );
1329
+ };
1330
+ // If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
1331
+ const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
1332
+ ? { ...fromRecords, ...fromSubmit }
1333
+ : { ...fromSubmit, ...fromRecords };
1278
1334
  const {
1279
- cardContent = [],
1335
+ cardContent: cardContentFromSubmit = [],
1280
1336
  contentType = "",
1281
1337
  cardType = "",
1282
1338
  cardSettings = {},
1283
- accountId = "",
1284
1339
  } = get(versions, 'base.content.RCS.rcsContent', {});
1340
+ const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1341
+ const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1342
+ ? cardContentFromSubmit[0]
1343
+ : null;
1344
+ const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1345
+ cardContentFromSubmit,
1346
+ rootRcsCardVarMappedFromSubmit,
1347
+ isFullModeForRcsConsumerPayload,
1348
+ );
1285
1349
  const rcsContent = {
1286
1350
  contentType,
1287
1351
  cardType,
1288
1352
  cardSettings,
1289
1353
  cardContent,
1290
1354
  };
1355
+ const cardVarMappedFromFirstRcsCard =
1356
+ firstCardFromSubmit?.cardVarMapped != null
1357
+ && typeof firstCardFromSubmit.cardVarMapped === 'object'
1358
+ ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1359
+ : null;
1360
+ const includeRootRcsCardVarMappedOnConsumerPayload =
1361
+ cardVarMappedFromFirstRcsCard
1362
+ && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
1363
+ && isFullModeForRcsConsumerPayload === true;
1291
1364
  templateData = {
1292
1365
  channel,
1293
1366
  creativeName: name,
1294
1367
  rcsContent,
1295
- accountId,
1368
+ ...(includeRootRcsCardVarMappedOnConsumerPayload
1369
+ ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1370
+ : {}),
1296
1371
  };
1372
+ // Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
1373
+ // so reopening the editor restores fallback text and tag mappings.
1374
+ // cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
1375
+ if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
1376
+ const smsText =
1377
+ smsFallBackContent.message
1378
+ ?? smsFallBackContent.smsContent
1379
+ ?? smsFallBackContent.smsTemplateContent
1380
+ ?? '';
1381
+ templateData.smsFallBackContent = {
1382
+ ...smsFallBackContent,
1383
+ ...(String(smsText).trim() !== ''
1384
+ ? { message: String(smsText).trim() }
1385
+ : {}),
1386
+ };
1387
+ }
1388
+ normalizeRcsMessageContentForApi(templateData);
1297
1389
  }
1390
+ }
1298
1391
  break;
1299
- case constants.ZALO:
1392
+ case constants.ZALO: {
1300
1393
  if (template.value) {
1301
1394
  templateData = {
1302
1395
  ...template.value,
@@ -1305,6 +1398,7 @@ export class Creatives extends React.Component {
1305
1398
  delete templateData.type;
1306
1399
  }
1307
1400
  }
1401
+ }
1308
1402
  break;
1309
1403
  case constants.WEBPUSH: {
1310
1404
  if (template.value) {
@@ -1411,7 +1505,10 @@ export class Creatives extends React.Component {
1411
1505
  return templateData;
1412
1506
  };
1413
1507
 
1414
- getSlideBoxContent({ mode, templateData, isFullMode }) {
1508
+ getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
1509
+ if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
1510
+ return 'templates';
1511
+ }
1415
1512
  let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
1416
1513
  if (mode === 'create' && isFullMode) {
1417
1514
  creativesMode = 'createTemplate';
@@ -1499,24 +1596,110 @@ export class Creatives extends React.Component {
1499
1596
  getFormData = (template) => {
1500
1597
  // Always reset isGetFormData so the child does not re-send form data on every re-render
1501
1598
  // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1502
- this.setState({ isGetFormData: false });
1503
- if (template.validity) {
1504
- this.setState(
1505
- {},
1506
- () => {
1507
- const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1508
- const channel = templateData.type;
1509
- const creativesData = this.getCreativesData(channel, template, templateData);// convers data to consumer understandable format
1510
- creativesData.then((data) => {
1511
- this.logGTMEvent(channel, data);
1512
- this.processCentralCommsMetaId(channel, data);
1599
+ this.setState(
1600
+ (prevState) => {
1601
+ const next = { isGetFormData: false };
1602
+ if (!template.validity) {
1603
+ return next;
1604
+ }
1605
+ const baseTd = prevState.templateData || template;
1606
+ const channel = (
1607
+ baseTd?.type
1608
+ || template?.type
1609
+ || get(template, 'value.type')
1610
+ || ''
1611
+ ).toUpperCase();
1612
+ // Library mode: persist last submitted creatives shape so reopening still hydrates the editor
1613
+ // (parent may not merge getCreativesData back into templateData).
1614
+ if (this.props.isFullMode === false && template.value) {
1615
+ if (channel === constants.RCS) {
1616
+ const smsFallBackFromPayload = get(
1617
+ template.value,
1618
+ 'versions.base.content.RCS.smsFallBackContent',
1619
+ );
1620
+ const rcsCardVarMappedFromPayload = get(
1621
+ template.value,
1622
+ 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
1623
+ );
1624
+ next.templateData = {
1625
+ ...baseTd,
1626
+ type: constants.RCS,
1627
+ name: template?.value?.name,
1628
+ versions: template?.value?.versions,
1629
+ ...(smsFallBackFromPayload != null
1630
+ && typeof smsFallBackFromPayload === 'object'
1631
+ && Object.keys(smsFallBackFromPayload).length > 0
1632
+ ? { smsFallBackContent: smsFallBackFromPayload }
1633
+ : {}),
1634
+ ...(rcsCardVarMappedFromPayload != null
1635
+ && typeof rcsCardVarMappedFromPayload === 'object'
1636
+ ? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
1637
+ : {}),
1638
+ };
1639
+ if (template._id) {
1640
+ next.templateData._id = template._id;
1641
+ }
1642
+ } else if (channel === constants.SMS) {
1643
+ const submittedSmsTemplateValue = template?.value;
1644
+ const smsVersions =
1645
+ submittedSmsTemplateValue?.history != null
1646
+ ? submittedSmsTemplateValue
1647
+ : {
1648
+ base: submittedSmsTemplateValue?.base,
1649
+ history: submittedSmsTemplateValue?.base
1650
+ ? [submittedSmsTemplateValue.base]
1651
+ : [],
1652
+ };
1653
+ next.templateData = {
1654
+ ...baseTd,
1655
+ type: constants.SMS,
1656
+ name: baseTd?.name || 'Campaign message SMS content',
1657
+ versions: smsVersions,
1658
+ };
1659
+ if (template?._id) {
1660
+ next.templateData._id = template._id;
1661
+ }
1662
+ }
1663
+ }
1664
+ return next;
1665
+ },
1666
+ () => {
1667
+ if (!template.validity) {
1668
+ return;
1669
+ }
1670
+ const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1671
+ const channelForConsumer = String(
1672
+ templateData.type
1673
+ || template.type
1674
+ || get(template, 'value.type')
1675
+ || '',
1676
+ ).toUpperCase();
1677
+ const creativesData = this.getCreativesData(
1678
+ channelForConsumer,
1679
+ template,
1680
+ this.state.templateData || template,
1681
+ );// convers data to consumer understandable format
1682
+ creativesData.then((data) => {
1683
+ this.logGTMEvent(channelForConsumer, data);
1684
+ this.processCentralCommsMetaId(channelForConsumer, data, {
1685
+ closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
1513
1686
  });
1514
- },
1515
- );
1516
- }
1687
+ });
1688
+ },
1689
+ );
1517
1690
  }
1518
1691
 
1519
- processCentralCommsMetaId = (channel, creativesData) => {
1692
+ processCentralCommsMetaId = (channel, creativesData, options = {}) => {
1693
+ const { closeSlideBoxAfterSubmit = false } = options;
1694
+ const maybeCloseLibrarySlideBox = () => {
1695
+ if (
1696
+ closeSlideBoxAfterSubmit
1697
+ && this.props.isFullMode === false
1698
+ && typeof this.handleCloseSlideBox === 'function'
1699
+ ) {
1700
+ this.handleCloseSlideBox();
1701
+ }
1702
+ };
1520
1703
  // Create the payload for the centralcommnsmetaId API call
1521
1704
  const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1522
1705
  const { actionName, setMetaData = () => { } } = loyaltyMetaData;
@@ -1542,6 +1725,7 @@ export class Creatives extends React.Component {
1542
1725
  if (result?.status?.code === 200) {
1543
1726
  setMetaData(result);
1544
1727
  this.props.getCreativesData(creativesData);
1728
+ maybeCloseLibrarySlideBox();
1545
1729
  } else {
1546
1730
  CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
1547
1731
  }
@@ -1552,6 +1736,7 @@ export class Creatives extends React.Component {
1552
1736
  } else {
1553
1737
  // If not a loyalty module or different action, should work as usual
1554
1738
  this.props.getCreativesData(creativesData);
1739
+ maybeCloseLibrarySlideBox();
1555
1740
  }
1556
1741
  };
1557
1742
 
@@ -1584,7 +1769,9 @@ export class Creatives extends React.Component {
1584
1769
  }
1585
1770
  this.setState((prevState) => ({
1586
1771
  ...prevState,
1587
- templateData: undefined,
1772
+ // Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
1773
+ // Undefined isFullMode defaults to full-mode close behavior (clear templateData).
1774
+ ...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
1588
1775
  showSlideBox: false,
1589
1776
  liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1590
1777
  isLiquidValidationError: false,
@@ -1795,21 +1982,12 @@ export class Creatives extends React.Component {
1795
1982
  }
1796
1983
 
1797
1984
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
1798
- const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1799
- const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1800
- const hasLiquid = !isDeepEmpty(liquidMsgs);
1801
- const hasStandard = !isDeepEmpty(standardMsgs);
1802
- const isLiquidValidationError = hasLiquid || hasStandard;
1803
- // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1804
- const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1805
- if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1806
- return;
1807
- }
1808
- this.setState({
1809
- isLiquidValidationError,
1810
- liquidErrorMessage: errorMessagesFromFormBuilder,
1811
- activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1985
+ const next = computeLiquidFooterUpdateFromFormBuilder(errorMessagesFromFormBuilder, currentFormBuilderTab, {
1986
+ previousIsLiquidValidationError: this.state.isLiquidValidationError,
1987
+ currentChannelUpper: this.state.currentChannel?.toUpperCase(),
1812
1988
  });
1989
+ if (next == null) return;
1990
+ this.setState(next);
1813
1991
  }
1814
1992
 
1815
1993
  // Callback to update HTML Editor validation state (called from EmailWrapper)
@@ -1932,6 +2110,11 @@ export class Creatives extends React.Component {
1932
2110
  inAppEditorType,
1933
2111
  htmlEditorValidationState,
1934
2112
  } = this.state;
2113
+ const useLocalTemplates = get(
2114
+ this.props,
2115
+ 'localTemplatesConfig.useLocalTemplates',
2116
+ get(this.props, 'useLocalTemplates', false),
2117
+ );
1935
2118
  const {
1936
2119
  isFullMode,
1937
2120
  creativesMode,
@@ -1950,7 +2133,6 @@ export class Creatives extends React.Component {
1950
2133
  smsRegister,
1951
2134
  enableNewChannels,
1952
2135
  eventContextTags,
1953
- waitEventContextTags = {},
1954
2136
  isLoyaltyModule,
1955
2137
  loyaltyMetaData = {},
1956
2138
  } = this.props;
@@ -1984,14 +2166,7 @@ export class Creatives extends React.Component {
1984
2166
  // IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
1985
2167
  const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
1986
2168
 
1987
- // Calculate margin for header/content (always apply if there are errors, regardless of editor type)
1988
- const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
1989
- ? CAP_SPACE_64
1990
- : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
1991
- ? CAP_SPACE_56
1992
- : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
1993
- ? CAP_SPACE_32
1994
- : 0;
2169
+ const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
1995
2170
  /* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
1996
2171
 
1997
2172
  // Compute anonymous user type and channel restrictions
@@ -2020,7 +2195,10 @@ export class Creatives extends React.Component {
2020
2195
  <SlideBoxWrapper
2021
2196
  slideBoxWrapperMargin={slideBoxWrapperMargin}
2022
2197
  shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
2023
- className={classnames(`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`)}
2198
+ className={classnames(
2199
+ `${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
2200
+ useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
2201
+ )}
2024
2202
  >
2025
2203
  <CapSlideBox
2026
2204
  header={
@@ -2045,12 +2223,13 @@ export class Creatives extends React.Component {
2045
2223
  smsRegister={smsRegister}
2046
2224
  handleClose={this.handleCloseSlideBox}
2047
2225
  moduleType={this.props.messageDetails?.type}
2226
+ useLocalTemplates={useLocalTemplates}
2048
2227
  />
2049
2228
  )}
2050
2229
  content={(
2051
2230
  <SlideBoxContent
2052
2231
  key="creatives-container-slidebox-content"
2053
- onSelectTemplate={this.onSelectTemplate}
2232
+ onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
2054
2233
  onCreateComplete={getCreativesData}
2055
2234
  onPreviewTemplate={this.onPreviewTemplate}
2056
2235
  slidBoxContent={slidBoxContent}
@@ -2118,7 +2297,6 @@ export class Creatives extends React.Component {
2118
2297
  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.
2119
2298
  hostName={this.props?.hostName || ''}
2120
2299
  eventContextTags={eventContextTags}
2121
- waitEventContextTags={waitEventContextTags}
2122
2300
  isLoyaltyModule={isLoyaltyModule}
2123
2301
  loyaltyMetaData={loyaltyMetaData}
2124
2302
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
@@ -2127,7 +2305,8 @@ export class Creatives extends React.Component {
2127
2305
  isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
2128
2306
  onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
2129
2307
  onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
2130
- />
2308
+ localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
2309
+ />
2131
2310
  )}
2132
2311
  footer={this.shouldShowFooter() ? (
2133
2312
  <SlideBoxFooter
@@ -2211,13 +2390,31 @@ Creatives.propTypes = {
2211
2390
  orgUnitId: PropTypes.number,
2212
2391
  hostName: PropTypes.string,
2213
2392
  eventContextTags: PropTypes.array,
2214
- waitEventContextTags: PropTypes.object,
2215
2393
  loyaltyTagFetchingDependencies: PropTypes.object,
2216
2394
  customerType: PropTypes.string,
2217
2395
  intl: PropTypes.shape({
2218
2396
  formatMessage: PropTypes.func,
2219
2397
  }),
2220
2398
  stopValidation: PropTypes.func,
2399
+ // Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
2400
+ // All optional. Pass either localTemplatesConfig (object) or individual props below.
2401
+ localTemplatesConfig: PropTypes.shape({
2402
+ useLocalTemplates: PropTypes.bool,
2403
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2404
+ localTemplatesLoading: PropTypes.bool,
2405
+ localTemplatesFilterContent: PropTypes.node,
2406
+ localTemplatesSentinelContent: PropTypes.node,
2407
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2408
+ localTemplatesUseSkeleton: PropTypes.bool,
2409
+ }),
2410
+ useLocalTemplates: PropTypes.bool,
2411
+ localTemplates: PropTypes.arrayOf(PropTypes.object),
2412
+ localTemplatesLoading: PropTypes.bool,
2413
+ localTemplatesFilterContent: PropTypes.node,
2414
+ localTemplatesSentinelContent: PropTypes.node,
2415
+ localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2416
+ localTemplatesUseSkeleton: PropTypes.bool,
2417
+ onSelectTemplate: PropTypes.func,
2221
2418
  };
2222
2419
  const mapStatesToProps = () => createStructuredSelector({
2223
2420
  isLoading: isLoadingSelector(),