@capillarytech/creatives-library 8.0.353-alpha.6 → 8.0.353

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 (124) hide show
  1. package/constants/unified.js +0 -29
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +20 -35
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +108 -166
  7. package/v2Components/CapActionButton/index.scss +6 -157
  8. package/v2Components/CapActionButton/messages.js +3 -19
  9. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  10. package/v2Components/CapTagList/index.js +0 -10
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -72
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -213
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -157
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -346
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  22. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  23. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  24. package/v2Components/CommonTestAndPreview/index.js +186 -691
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  31. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  32. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  33. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  34. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  35. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  36. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +26 -36
  37. package/v2Components/FormBuilder/index.js +168 -63
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -38
  39. package/v2Components/TemplatePreview/index.js +31 -143
  40. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  41. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  42. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  43. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  44. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  46. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  47. package/v2Containers/CreativesContainer/constants.js +0 -9
  48. package/v2Containers/CreativesContainer/index.js +163 -346
  49. package/v2Containers/CreativesContainer/index.scss +1 -51
  50. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  51. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  52. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  53. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  54. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  55. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  56. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  57. package/v2Containers/Rcs/constants.js +10 -119
  58. package/v2Containers/Rcs/index.js +818 -2450
  59. package/v2Containers/Rcs/index.scss +8 -280
  60. package/v2Containers/Rcs/messages.js +3 -34
  61. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  62. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  63. package/v2Containers/Rcs/tests/index.test.js +121 -152
  64. package/v2Containers/Rcs/tests/mockData.js +0 -38
  65. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  66. package/v2Containers/Rcs/utils.js +11 -478
  67. package/v2Containers/Sms/Create/index.js +40 -106
  68. package/v2Containers/SmsTrai/Create/index.js +4 -9
  69. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  70. package/v2Containers/SmsTrai/Edit/index.js +130 -640
  71. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  72. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  73. package/v2Containers/SmsWrapper/index.js +8 -37
  74. package/v2Containers/TagList/index.js +0 -6
  75. package/v2Containers/Templates/_templates.scss +9 -166
  76. package/v2Containers/Templates/actions.js +0 -11
  77. package/v2Containers/Templates/constants.js +0 -2
  78. package/v2Containers/Templates/index.js +52 -120
  79. package/v2Containers/Templates/sagas.js +12 -56
  80. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1017 -1062
  81. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  82. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  83. package/v2Containers/TemplatesV2/index.js +23 -86
  84. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  85. package/v2Containers/Whatsapp/index.js +20 -3
  86. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  87. package/utils/rcsPayloadUtils.js +0 -92
  88. package/utils/templateVarUtils.js +0 -201
  89. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  90. package/utils/tests/templateVarUtils.test.js +0 -204
  91. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  92. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  93. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  94. package/v2Components/SmsFallback/constants.js +0 -73
  95. package/v2Components/SmsFallback/index.js +0 -956
  96. package/v2Components/SmsFallback/index.scss +0 -265
  97. package/v2Components/SmsFallback/messages.js +0 -78
  98. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -119
  99. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  100. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  101. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  102. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -223
  103. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  104. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  105. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  106. package/v2Components/TemplatePreview/constants.js +0 -2
  107. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  108. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  109. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  110. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  111. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  112. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  113. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  114. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  115. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  116. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  117. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  118. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  119. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  120. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  121. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  122. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  123. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  124. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,5 +1,53 @@
1
1
  import React from 'react';
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
+ }
2
46
  import PropTypes from 'prop-types';
47
+ import {
48
+ CAP_SPACE_16, CAP_SPACE_32, CAP_SPACE_56, CAP_SPACE_64,
49
+ } from '@capillarytech/cap-ui-library/styled/variables';
50
+
3
51
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
4
52
  import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
5
53
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
@@ -9,11 +57,12 @@ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
9
57
  import { injectIntl, FormattedMessage } from 'react-intl';
10
58
  import classnames from 'classnames';
11
59
  import {
12
- isEmpty, get, forEach, cloneDeep, debounce, pick,
60
+ isEmpty, get, forEach, cloneDeep, debounce,
13
61
  } from 'lodash';
14
62
  import { connect } from 'react-redux';
15
63
  import { createStructuredSelector } from 'reselect';
16
64
  import { bindActionCreators, compose } from 'redux';
65
+ import styled from 'styled-components';
17
66
  import { GA } from '@capillarytech/cap-ui-utils';
18
67
  import { DAEMON } from '@capillarytech/vulcan-react-sdk/utils/sagaInjectorTypes';
19
68
 
@@ -27,7 +76,6 @@ import SlideBoxContent from './SlideBoxContent';
27
76
  import * as constants from './constants';
28
77
  import * as commonUtil from '../../utils/common';
29
78
  import { gtmPush } from '../../utils/gtmTrackers';
30
- import { normalizeRcsMessageContentForApi } from '../../utils/rcsPayloadUtils';
31
79
  import './index.scss';
32
80
  import * as templateActions from '../Templates/actions';
33
81
  import * as globalActions from '../Cap/actions';
@@ -43,9 +91,6 @@ import {
43
91
  import {EXTERNAL_URL, SITE_URL, WEBPUSH_MEDIA_TYPES} from '../WebPush/constants';
44
92
  import { IMAGE, VIDEO } from '../Facebook/Advertisement/constant';
45
93
  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';
49
94
  import { CREATIVE } from '../Facebook/constants';
50
95
  import { LOYALTY } from '../App/constants';
51
96
  import {
@@ -60,11 +105,6 @@ import { capSagaForFetchSchemaForEntity, capSagaLiquidEntity } from '../Cap/saga
60
105
  import { v2TemplateSagaWatchGetDefaultBeeTemplates } from '../Templates/sagas';
61
106
  import { DYNAMIC_URL } from '../../v2Components/CapWhatsappCTA/constants';
62
107
  import ErrorInfoNote from '../../v2Components/ErrorInfoNote';
63
- import SlideBoxWrapper from './CreativesSlideBoxWrapper';
64
- import {
65
- computeLiquidFooterUpdateFromFormBuilder,
66
- getSlideBoxWrapperMarginFromLiquidErrors,
67
- } from './embeddedSlideboxUtils';
68
108
 
69
109
  import {
70
110
  transformChannelPayload,
@@ -73,24 +113,51 @@ import {
73
113
  import { MANUAL_CAROUSEL } from '../MobilePushNew/constants';
74
114
  import { BIG_HTML } from '../InApp/constants';
75
115
 
116
+ /**
117
+ * Returns true if value is "deep empty": no errors present.
118
+ * - null/undefined: empty
119
+ * - string: empty if length === 0
120
+ * - array: empty if length === 0
121
+ * - plain object (e.g. { android: [], ios: [], generic: [] }): empty only if every value is deep-empty
122
+ */
123
+ function isDeepEmpty(value) {
124
+ if (value == null) return true;
125
+ if (typeof value === 'string') return value.length === 0;
126
+ if (Array.isArray(value)) return value.length === 0;
127
+ if (typeof value === 'object') {
128
+ return Object.values(value).every(isDeepEmpty);
129
+ }
130
+ return false;
131
+ }
132
+
76
133
  const classPrefix = 'add-creatives-section';
77
134
  const CREATIVES_CONTAINER = 'creativesContainer';
78
135
 
136
+ const SlideBoxWrapper = styled.div`
137
+ .cap-slide-box-v2-container{
138
+ .slidebox-header, .slidebox-content-container{
139
+ margin-bottom: ${({ slideBoxWrapperMargin }) => `${slideBoxWrapperMargin}`};
140
+ padding: 0 rem;
141
+ &.has-footer{
142
+ overflow-x: hidden;
143
+ }
144
+ }
145
+ .slidebox-footer{
146
+ /* Only apply margin-bottom to footer when ErrorInfoNote is shown in footer (BEE editor) */
147
+ /* For HTML Editor, errors are shown in ValidationErrorDisplay (inside content area), so no footer margin needed */
148
+ margin-bottom: ${({ shouldApplyFooterMargin }) => (shouldApplyFooterMargin ? `${CAP_SPACE_16}` : '0')};
149
+ padding: 0 rem;
150
+ &.has-footer{
151
+ overflow-x: hidden;
152
+ }
153
+ }
154
+ }
155
+ `;
79
156
  export class Creatives extends React.Component {
80
157
  constructor(props) {
81
158
  super(props);
82
159
 
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
- });
160
+ const initialSlidBoxContent = this.getSlideBoxContent({ mode: props.creativesMode, templateData: props.templateData, isFullMode: props.isFullMode });
94
161
 
95
162
  this.state = {
96
163
  isLoadingContent: true,
@@ -137,13 +204,7 @@ export class Creatives extends React.Component {
137
204
  }
138
205
 
139
206
  componentWillUnmount() {
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) {
207
+ if (get(this.props, 'location.query.type', '') === "embedded") {
147
208
  this.props.templateActions.resetTemplateStoreData();
148
209
  }
149
210
  this.props.globalActions.clearMetaEntities();
@@ -751,69 +812,15 @@ export class Creatives extends React.Component {
751
812
  smsFallBackContent = {},
752
813
  creativeName = "",
753
814
  channel = constants.RCS,
754
- rcsCardVarMapped,
755
815
  accountId = "",
756
816
  } = templateData || {};
757
- const { isFullMode: isFullModeForRcsPayload } = this.props;
758
- const isCarouselRcs = (rcsContent?.cardType || '').toString().toLowerCase() === 'carousel';
759
- const firstCardIn = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
760
- const {
761
- cardDisplayTitle: _omitDispTitleIn,
762
- cardDisplayDescription: _omitDispDescIn,
763
- ...cardContent
764
- } = firstCardIn;
817
+ const cardContent = (rcsContent.cardContent && rcsContent.cardContent[0]) || {};
765
818
  const Status = RCS_STATUSES.approved || '';
766
- const mergedCardVarMapped = (() => {
767
- const nestedCardVarMapped = cardContent?.cardVarMapped;
768
- const rootMirrorCardVarMapped = rcsCardVarMapped;
769
- const nestedRecord =
770
- nestedCardVarMapped != null && typeof nestedCardVarMapped === 'object'
771
- ? nestedCardVarMapped
772
- : {};
773
- const rootRecord =
774
- rootMirrorCardVarMapped != null && typeof rootMirrorCardVarMapped === 'object'
775
- ? rootMirrorCardVarMapped
776
- : {};
777
- const mergedFromRootAndNested = {
778
- ...pickRcsCardVarMappedEntries(rootRecord),
779
- ...pickRcsCardVarMappedEntries(nestedRecord),
780
- };
781
- return Object.keys(mergedFromRootAndNested).length > 0
782
- ? mergedFromRootAndNested
783
- : null;
784
- })();
785
- // Campaigns (embedded): do not duplicate `cardVarMapped` as root `rcsCardVarMapped` on send —
786
- // slot map stays on `versions…cardContent[0].cardVarMapped` only. Library full mode keeps root mirror.
787
- // Use `=== true` so omitted/undefined `isFullMode` does not behave like library (avoids duplicate on approval payload).
788
- const includeRootRcsCardVarMapped =
789
- mergedCardVarMapped && isFullModeForRcsPayload === true;
790
-
791
- const builtCardContent = isCarouselRcs
792
- ? (rcsContent.cardContent || []).map((card, idx) => {
793
- const {
794
- cardDisplayTitle: _dt,
795
- cardDisplayDescription: _dd,
796
- ...restCard
797
- } = card || {};
798
- return {
799
- ...restCard,
800
- ...(idx === 0 && mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
801
- Status,
802
- };
803
- })
804
- : [
805
- {
806
- ...cardContent,
807
- ...(mergedCardVarMapped ? { cardVarMapped: mergedCardVarMapped } : {}),
808
- Status,
809
- },
810
- ];
811
819
 
812
820
  creativesTemplateData = {
813
821
  type: channel,
814
822
  edit: true,
815
823
  name: creativeName,
816
- ...(includeRootRcsCardVarMapped ? { rcsCardVarMapped: mergedCardVarMapped } : {}),
817
824
  versions: {
818
825
  base: {
819
826
  content: {
@@ -821,7 +828,12 @@ export class Creatives extends React.Component {
821
828
  rcsContent: {
822
829
  ...rcsContent,
823
830
  ...(accountId && !isFullMode && { accountId }),
824
- cardContent: builtCardContent,
831
+ cardContent: [
832
+ {
833
+ ...cardContent,
834
+ Status,
835
+ },
836
+ ],
825
837
  },
826
838
  smsFallBackContent,
827
839
  },
@@ -969,10 +981,7 @@ export class Creatives extends React.Component {
969
981
  return newExpandableDetails;
970
982
  }
971
983
 
972
- getCreativesData = async (channelParam, template, templateRecords) => { //from creatives to consumers
973
- const channel = String(
974
- channelParam || template?.type || get(template, 'value.type') || '',
975
- ).toUpperCase();
984
+ getCreativesData = async (channel, template, templateRecords) => { //from creatives to consumers
976
985
  let templateData = { channel };
977
986
  switch (channel) {
978
987
  case constants.SMS:
@@ -1319,104 +1328,28 @@ export class Creatives extends React.Component {
1319
1328
  break;
1320
1329
  case constants.RCS:
1321
1330
  if (template.value) {
1322
- const { isFullMode: isFullModeForRcsConsumerPayload } = this.props;
1323
- const { name = "", versions = {} } = template.value || {};
1324
- const fromSubmit = get(versions, 'base.content.RCS.smsFallBackContent', {});
1325
- const fromRecords = {
1326
- ...(templateRecords?.smsFallBackContent || {}),
1327
- ...(get(templateRecords, 'versions.base.content.RCS.smsFallBackContent') || {}),
1328
- };
1329
- const hasMeaningfulRcsSmsFallback = (smsFallbackPayload) => {
1330
- if (
1331
- !smsFallbackPayload
1332
- || typeof smsFallbackPayload !== 'object'
1333
- || Object.keys(smsFallbackPayload).length === 0
1334
- ) {
1335
- return false;
1336
- }
1337
- const fallbackBodyText = String(
1338
- smsFallbackPayload.smsContent
1339
- ?? smsFallbackPayload.smsTemplateContent
1340
- ?? smsFallbackPayload.message
1341
- ?? smsFallbackPayload.content
1342
- ?? '',
1343
- ).trim();
1344
- const fallbackTemplateName = String(
1345
- smsFallbackPayload.smsTemplateName ?? smsFallbackPayload.templateName ?? '',
1346
- ).trim();
1347
- const rcsSmsFallbackVarMapped =
1348
- smsFallbackPayload?.[RCS_SMS_FALLBACK_VAR_MAPPED_PROP];
1349
- const hasVarMappedEntries =
1350
- rcsSmsFallbackVarMapped != null
1351
- && typeof rcsSmsFallbackVarMapped === 'object'
1352
- && Object.keys(rcsSmsFallbackVarMapped).length > 0;
1353
- return (
1354
- fallbackBodyText !== ''
1355
- || fallbackTemplateName !== ''
1356
- || hasVarMappedEntries
1357
- );
1358
- };
1359
- // If submit has only empty strings, do not let it wipe fallback mirrored on templateRecords (library round-trip).
1360
- const smsFallBackContent = hasMeaningfulRcsSmsFallback(fromSubmit)
1361
- ? { ...fromRecords, ...fromSubmit }
1362
- : { ...fromSubmit, ...fromRecords };
1331
+ const { name = "", versions = {} } = {
1332
+ } = template.value || {};
1333
+ const smsFallBackContent = get(versions, 'base.content.RCS.smsFallBackContent', {});
1363
1334
  const {
1364
- cardContent: cardContentFromSubmit = [],
1335
+ cardContent = [],
1365
1336
  contentType = "",
1366
1337
  cardType = "",
1367
1338
  cardSettings = {},
1368
1339
  accountId = "",
1369
1340
  } = get(versions, 'base.content.RCS.rcsContent', {});
1370
- const rootRcsCardVarMappedFromSubmit = get(template, 'value.rcsCardVarMapped');
1371
- const firstCardFromSubmit = Array.isArray(cardContentFromSubmit)
1372
- ? cardContentFromSubmit[0]
1373
- : null;
1374
- const cardContent = mapRcsCardContentForConsumerWithResolvedTags(
1375
- cardContentFromSubmit,
1376
- rootRcsCardVarMappedFromSubmit,
1377
- isFullModeForRcsConsumerPayload,
1378
- );
1379
1341
  const rcsContent = {
1380
1342
  contentType,
1381
1343
  cardType,
1382
1344
  cardSettings,
1383
1345
  cardContent,
1384
1346
  };
1385
- const cardVarMappedFromFirstRcsCard =
1386
- firstCardFromSubmit?.cardVarMapped != null
1387
- && typeof firstCardFromSubmit.cardVarMapped === 'object'
1388
- ? pickRcsCardVarMappedEntries(firstCardFromSubmit.cardVarMapped)
1389
- : null;
1390
- const includeRootRcsCardVarMappedOnConsumerPayload =
1391
- cardVarMappedFromFirstRcsCard
1392
- && Object.keys(cardVarMappedFromFirstRcsCard).length > 0
1393
- && isFullModeForRcsConsumerPayload === true;
1394
1347
  templateData = {
1395
1348
  channel,
1396
1349
  creativeName: name,
1397
1350
  rcsContent,
1398
1351
  accountId,
1399
- ...(includeRootRcsCardVarMappedOnConsumerPayload
1400
- ? { rcsCardVarMapped: cardVarMappedFromFirstRcsCard }
1401
- : {}),
1402
1352
  };
1403
- // Library / campaign consumers round-trip templateData via getTemplateData; include SMS fallback
1404
- // so reopening the editor restores fallback text and tag mappings.
1405
- // cap-campaigns-v2 API expects `smsFallBackContent.message` (see normalizeRcsMessageContentForApi).
1406
- if (hasMeaningfulRcsSmsFallback(smsFallBackContent)) {
1407
- const smsText =
1408
- smsFallBackContent.message
1409
- ?? smsFallBackContent.smsContent
1410
- ?? smsFallBackContent.smsTemplateContent
1411
- ?? '';
1412
- templateData.smsFallBackContent = {
1413
- ...smsFallBackContent,
1414
- ...(String(smsText).trim() !== ''
1415
- ? { message: String(smsText).trim() }
1416
- : {}),
1417
- };
1418
- }
1419
- normalizeRcsMessageContentForApi(templateData);
1420
1353
  }
1421
1354
  break;
1422
1355
  case constants.ZALO:
@@ -1534,10 +1467,7 @@ export class Creatives extends React.Component {
1534
1467
  return templateData;
1535
1468
  };
1536
1469
 
1537
- getSlideBoxContent({ mode, templateData, isFullMode, useLocalTemplates }) {
1538
- if (useLocalTemplates && mode === 'create' && isEmpty(templateData)) {
1539
- return 'templates';
1540
- }
1470
+ getSlideBoxContent({ mode, templateData, isFullMode }) {
1541
1471
  let creativesMode = isFullMode ? 'createTemplate' : 'templates';// for library mode templates page is initial mode and for full mode createTemplates
1542
1472
  if (mode === 'create' && isFullMode) {
1543
1473
  creativesMode = 'createTemplate';
@@ -1625,110 +1555,24 @@ export class Creatives extends React.Component {
1625
1555
  getFormData = (template) => {
1626
1556
  // Always reset isGetFormData so the child does not re-send form data on every re-render
1627
1557
  // (e.g. when user fixes validation error by typing, we must not auto-close the slidebox)
1628
- this.setState(
1629
- (prevState) => {
1630
- const next = { isGetFormData: false };
1631
- if (!template.validity) {
1632
- return next;
1633
- }
1634
- const baseTd = prevState.templateData || template;
1635
- const channel = (
1636
- baseTd?.type
1637
- || template?.type
1638
- || get(template, 'value.type')
1639
- || ''
1640
- ).toUpperCase();
1641
- // Library mode: persist last submitted creatives shape so reopening still hydrates the editor
1642
- // (parent may not merge getCreativesData back into templateData).
1643
- if (this.props.isFullMode === false && template.value) {
1644
- if (channel === constants.RCS) {
1645
- const smsFallBackFromPayload = get(
1646
- template.value,
1647
- 'versions.base.content.RCS.smsFallBackContent',
1648
- );
1649
- const rcsCardVarMappedFromPayload = get(
1650
- template.value,
1651
- 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped',
1652
- );
1653
- next.templateData = {
1654
- ...baseTd,
1655
- type: constants.RCS,
1656
- name: template?.value?.name,
1657
- versions: template?.value?.versions,
1658
- ...(smsFallBackFromPayload != null
1659
- && typeof smsFallBackFromPayload === 'object'
1660
- && Object.keys(smsFallBackFromPayload).length > 0
1661
- ? { smsFallBackContent: smsFallBackFromPayload }
1662
- : {}),
1663
- ...(rcsCardVarMappedFromPayload != null
1664
- && typeof rcsCardVarMappedFromPayload === 'object'
1665
- ? { rcsCardVarMapped: rcsCardVarMappedFromPayload }
1666
- : {}),
1667
- };
1668
- if (template._id) {
1669
- next.templateData._id = template._id;
1670
- }
1671
- } else if (channel === constants.SMS) {
1672
- const submittedSmsTemplateValue = template?.value;
1673
- const smsVersions =
1674
- submittedSmsTemplateValue?.history != null
1675
- ? submittedSmsTemplateValue
1676
- : {
1677
- base: submittedSmsTemplateValue?.base,
1678
- history: submittedSmsTemplateValue?.base
1679
- ? [submittedSmsTemplateValue.base]
1680
- : [],
1681
- };
1682
- next.templateData = {
1683
- ...baseTd,
1684
- type: constants.SMS,
1685
- name: baseTd?.name || 'Campaign message SMS content',
1686
- versions: smsVersions,
1687
- };
1688
- if (template?._id) {
1689
- next.templateData._id = template._id;
1690
- }
1691
- }
1692
- }
1693
- return next;
1694
- },
1695
- () => {
1696
- if (!template.validity) {
1697
- return;
1698
- }
1699
- const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1700
- const channelForConsumer = String(
1701
- templateData.type
1702
- || template.type
1703
- || get(template, 'value.type')
1704
- || '',
1705
- ).toUpperCase();
1706
- const creativesData = this.getCreativesData(
1707
- channelForConsumer,
1708
- template,
1709
- this.state.templateData || template,
1710
- );// convers data to consumer understandable format
1711
- creativesData.then((data) => {
1712
- this.logGTMEvent(channelForConsumer, data);
1713
- this.processCentralCommsMetaId(channelForConsumer, data, {
1714
- closeSlideBoxAfterSubmit: template.closeSlideBoxAfterSubmit,
1558
+ this.setState({ isGetFormData: false });
1559
+ if (template.validity) {
1560
+ this.setState(
1561
+ {},
1562
+ () => {
1563
+ const templateData = this.state.templateData ? this.state.templateData : template; //select existing or create new content
1564
+ const channel = templateData.type;
1565
+ const creativesData = this.getCreativesData(channel, template, templateData);// convers data to consumer understandable format
1566
+ creativesData.then((data) => {
1567
+ this.logGTMEvent(channel, data);
1568
+ this.processCentralCommsMetaId(channel, data);
1715
1569
  });
1716
- });
1717
- },
1718
- );
1570
+ },
1571
+ );
1572
+ }
1719
1573
  }
1720
1574
 
1721
- processCentralCommsMetaId = (channel, creativesData, options = {}) => {
1722
- const { closeSlideBoxAfterSubmit = false } = options;
1723
- const maybeCloseLibrarySlideBox = () => {
1724
- if (
1725
- closeSlideBoxAfterSubmit
1726
- && this.props.isFullMode === false
1727
- && typeof this.handleCloseSlideBox === 'function'
1728
- ) {
1729
- this.handleCloseSlideBox();
1730
- }
1731
- };
1575
+ processCentralCommsMetaId = (channel, creativesData) => {
1732
1576
  // Create the payload for the centralcommnsmetaId API call
1733
1577
  const { isLoyaltyModule = false, loyaltyMetaData = {} } = this.props;
1734
1578
  const { actionName, setMetaData = () => { } } = loyaltyMetaData;
@@ -1754,7 +1598,6 @@ export class Creatives extends React.Component {
1754
1598
  if (result?.status?.code === 200) {
1755
1599
  setMetaData(result);
1756
1600
  this.props.getCreativesData(creativesData);
1757
- maybeCloseLibrarySlideBox();
1758
1601
  } else {
1759
1602
  CapNotification.error({ message: <FormattedMessage {...messages.somethingWentWrong} /> });
1760
1603
  }
@@ -1765,7 +1608,6 @@ export class Creatives extends React.Component {
1765
1608
  } else {
1766
1609
  // If not a loyalty module or different action, should work as usual
1767
1610
  this.props.getCreativesData(creativesData);
1768
- maybeCloseLibrarySlideBox();
1769
1611
  }
1770
1612
  };
1771
1613
 
@@ -1798,9 +1640,7 @@ export class Creatives extends React.Component {
1798
1640
  }
1799
1641
  this.setState((prevState) => ({
1800
1642
  ...prevState,
1801
- // Library mode (isFullMode === false): retain last template so reopening still has RCS payload.
1802
- // Undefined isFullMode defaults to full-mode close behavior (clear templateData).
1803
- ...(this.props.isFullMode !== false ? { templateData: undefined } : {}),
1643
+ templateData: undefined,
1804
1644
  showSlideBox: false,
1805
1645
  liquidErrorMessage: { STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] },
1806
1646
  isLiquidValidationError: false,
@@ -1957,30 +1797,22 @@ export class Creatives extends React.Component {
1957
1797
  } />
1958
1798
  )
1959
1799
 
1960
- templateNameComponentInput = ({ formData, onFormDataChange, name }) => {
1961
- // Use local state for immediate UI feedback, fallback to prop value
1962
- const displayValue = this.state.localTemplateName !== '' ? this.state.localTemplateName : name;
1963
-
1964
- return (
1965
- <CapInput
1966
- value={displayValue}
1967
- suffix={<span />}
1968
- onBlur={() => {
1969
- this.setState({
1970
- isEditName: false,
1971
- localTemplateName: '', // Clear local state on blur
1972
- }, () => {
1973
- this.showTemplateName({ formData, onFormDataChange });
1974
- });
1975
- }}
1976
- onChange={(ev) => {
1977
- const { value } = ev.currentTarget;
1978
- // Use optimized update for better performance
1979
- this.updateTemplateNameImmediately(value, formData, onFormDataChange);
1980
- }}
1981
- />
1982
- );
1983
- }
1800
+ templateNameComponentInput = ({ formData, onFormDataChange, name }) => (
1801
+ <TemplateNameInputField
1802
+ initialValue={name}
1803
+ suffix={<span />}
1804
+ onBlur={(committedValue) => {
1805
+ this.performTemplateNameUpdate(committedValue, formData, onFormDataChange);
1806
+ this.setState({ isEditName: false });
1807
+ }}
1808
+ onChange={(value) => {
1809
+ const isEmptyTemplateName = !value.trim();
1810
+ if (this.state.isTemplateNameEmpty !== isEmptyTemplateName) {
1811
+ this.setState({ isTemplateNameEmpty: isEmptyTemplateName });
1812
+ }
1813
+ }}
1814
+ />
1815
+ )
1984
1816
 
1985
1817
  showTemplateName = ({ formData, onFormDataChange }) => { //gets called from email/index after template data is fetched
1986
1818
  const {
@@ -2011,17 +1843,21 @@ export class Creatives extends React.Component {
2011
1843
  }
2012
1844
 
2013
1845
  showLiquidErrorInFooter = (errorMessagesFromFormBuilder, currentFormBuilderTab) => {
2014
- const next = computeLiquidFooterUpdateFromFormBuilder(
2015
- errorMessagesFromFormBuilder,
2016
- this.state.liquidErrorMessage,
2017
- currentFormBuilderTab,
2018
- {
2019
- previousIsLiquidValidationError: this.state.isLiquidValidationError,
2020
- currentChannelUpper: this.state.currentChannel?.toUpperCase(),
2021
- },
2022
- );
2023
- if (next == null) return;
2024
- this.setState(next);
1846
+ const liquidMsgs = get(errorMessagesFromFormBuilder, constants.LIQUID_ERROR_MSG, []);
1847
+ const standardMsgs = get(errorMessagesFromFormBuilder, constants.STANDARD_ERROR_MSG, []);
1848
+ const hasLiquid = !isDeepEmpty(liquidMsgs);
1849
+ const hasStandard = !isDeepEmpty(standardMsgs);
1850
+ const isLiquidValidationError = hasLiquid || hasStandard;
1851
+ // Don't overwrite existing liquid error with empty only for Mobile Push OLD (FormBuilder/clear calls empty there); SMS/others clear on input change
1852
+ const isMobilePush = this.state.currentChannel?.toUpperCase() === constants.MOBILE_PUSH;
1853
+ if (!hasLiquid && !hasStandard && this.state.isLiquidValidationError && isMobilePush) {
1854
+ return;
1855
+ }
1856
+ this.setState({
1857
+ isLiquidValidationError,
1858
+ liquidErrorMessage: errorMessagesFromFormBuilder,
1859
+ activeFormBuilderTab: currentFormBuilderTab === 1 ? constants.ANDROID : (currentFormBuilderTab === 2 ? constants.IOS : null), // Update activeFormBuilderTab, default to 1 if undefined
1860
+ });
2025
1861
  }
2026
1862
 
2027
1863
  // Callback to update HTML Editor validation state (called from EmailWrapper)
@@ -2144,11 +1980,6 @@ export class Creatives extends React.Component {
2144
1980
  inAppEditorType,
2145
1981
  htmlEditorValidationState,
2146
1982
  } = this.state;
2147
- const useLocalTemplates = get(
2148
- this.props,
2149
- 'localTemplatesConfig.useLocalTemplates',
2150
- get(this.props, 'useLocalTemplates', false),
2151
- );
2152
1983
  const {
2153
1984
  isFullMode,
2154
1985
  creativesMode,
@@ -2167,6 +1998,7 @@ export class Creatives extends React.Component {
2167
1998
  smsRegister,
2168
1999
  enableNewChannels,
2169
2000
  eventContextTags,
2001
+ waitEventContextTags = {},
2170
2002
  isLoyaltyModule,
2171
2003
  loyaltyMetaData = {},
2172
2004
  } = this.props;
@@ -2200,7 +2032,14 @@ export class Creatives extends React.Component {
2200
2032
  // IMPORTANT: Never show ErrorInfoNote in footer when in HTML Editor mode, even if liquidErrorMessage exists
2201
2033
  const shouldShowErrorInfoNoteInFooter = isHTMLEditorMode ? false : hasBEEEditorErrors;
2202
2034
 
2203
- const slideBoxWrapperMargin = getSlideBoxWrapperMarginFromLiquidErrors(liquidErrorMessage);
2035
+ // Calculate margin for header/content (always apply if there are errors, regardless of editor type)
2036
+ const slideBoxWrapperMargin = (get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0 && get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0)
2037
+ ? CAP_SPACE_64
2038
+ : get(liquidErrorMessage, 'LIQUID_ERROR_MSG.length', 0) > 0
2039
+ ? CAP_SPACE_56
2040
+ : get(liquidErrorMessage, 'STANDARD_ERROR_MSG.length', 0) > 0
2041
+ ? CAP_SPACE_32
2042
+ : 0;
2204
2043
  /* TODO: Instead of passing down same props separately to each component down, write common function to these props and pass it accordingly */
2205
2044
 
2206
2045
  // Compute anonymous user type and channel restrictions
@@ -2229,10 +2068,7 @@ export class Creatives extends React.Component {
2229
2068
  <SlideBoxWrapper
2230
2069
  slideBoxWrapperMargin={slideBoxWrapperMargin}
2231
2070
  shouldApplyFooterMargin={shouldShowErrorInfoNoteInFooter}
2232
- className={classnames(
2233
- `${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`,
2234
- useLocalTemplates && slidBoxContent === 'templates' && 'creatives-slidebox--local-sms-templates',
2235
- )}
2071
+ className={classnames(`${classPrefix} ${isFullMode ? 'creatives-full-mode' : 'creatives-library-mode'} ${mapTemplateCreate ? 'map-template-create' : ''}`)}
2236
2072
  >
2237
2073
  <CapSlideBox
2238
2074
  header={
@@ -2257,13 +2093,12 @@ export class Creatives extends React.Component {
2257
2093
  smsRegister={smsRegister}
2258
2094
  handleClose={this.handleCloseSlideBox}
2259
2095
  moduleType={this.props.messageDetails?.type}
2260
- useLocalTemplates={useLocalTemplates}
2261
2096
  />
2262
2097
  )}
2263
2098
  content={(
2264
2099
  <SlideBoxContent
2265
2100
  key="creatives-container-slidebox-content"
2266
- onSelectTemplate={this.props.onSelectTemplate != null ? this.props.onSelectTemplate : this.onSelectTemplate}
2101
+ onSelectTemplate={this.onSelectTemplate}
2267
2102
  onCreateComplete={getCreativesData}
2268
2103
  onPreviewTemplate={this.onPreviewTemplate}
2269
2104
  slidBoxContent={slidBoxContent}
@@ -2331,6 +2166,7 @@ export class Creatives extends React.Component {
2331
2166
  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.
2332
2167
  hostName={this.props?.hostName || ''}
2333
2168
  eventContextTags={eventContextTags}
2169
+ waitEventContextTags={waitEventContextTags}
2334
2170
  isLoyaltyModule={isLoyaltyModule}
2335
2171
  loyaltyMetaData={loyaltyMetaData}
2336
2172
  showTestAndPreviewSlidebox={showTestAndPreviewSlidebox}
@@ -2339,8 +2175,7 @@ export class Creatives extends React.Component {
2339
2175
  isTestAndPreviewMode={(() => this.state.isTestAndPreviewMode)()}
2340
2176
  onHtmlEditorValidationStateChange={this.updateHtmlEditorValidationState}
2341
2177
  onPersonalizationTokensChange={this.handlePersonalizationTokensChange}
2342
- localTemplatesConfig={pick(this.props.localTemplatesConfig || this.props, constants.LOCAL_TEMPLATE_CONFIG_KEYS)}
2343
- />
2178
+ />
2344
2179
  )}
2345
2180
  footer={this.shouldShowFooter() ? (
2346
2181
  <SlideBoxFooter
@@ -2425,31 +2260,13 @@ Creatives.propTypes = {
2425
2260
  orgUnitId: PropTypes.number,
2426
2261
  hostName: PropTypes.string,
2427
2262
  eventContextTags: PropTypes.array,
2263
+ waitEventContextTags: PropTypes.object,
2428
2264
  loyaltyTagFetchingDependencies: PropTypes.object,
2429
2265
  customerType: PropTypes.string,
2430
2266
  intl: PropTypes.shape({
2431
2267
  formatMessage: PropTypes.func,
2432
2268
  }),
2433
2269
  stopValidation: PropTypes.func,
2434
- // Local template list (e.g. for SMS fallback): when set, TemplatesV2 uses these instead of Redux.
2435
- // All optional. Pass either localTemplatesConfig (object) or individual props below.
2436
- localTemplatesConfig: PropTypes.shape({
2437
- useLocalTemplates: PropTypes.bool,
2438
- localTemplates: PropTypes.arrayOf(PropTypes.object),
2439
- localTemplatesLoading: PropTypes.bool,
2440
- localTemplatesFilterContent: PropTypes.node,
2441
- localTemplatesSentinelContent: PropTypes.node,
2442
- localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2443
- localTemplatesUseSkeleton: PropTypes.bool,
2444
- }),
2445
- useLocalTemplates: PropTypes.bool,
2446
- localTemplates: PropTypes.arrayOf(PropTypes.object),
2447
- localTemplatesLoading: PropTypes.bool,
2448
- localTemplatesFilterContent: PropTypes.node,
2449
- localTemplatesSentinelContent: PropTypes.node,
2450
- localTemplatesScrollContainerRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]),
2451
- localTemplatesUseSkeleton: PropTypes.bool,
2452
- onSelectTemplate: PropTypes.func,
2453
2270
  };
2454
2271
  const mapStatesToProps = () => createStructuredSelector({
2455
2272
  isLoading: isLoadingSelector(),