@capillarytech/creatives-library 9.0.15-alpha.3 → 9.0.16-beta.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 (146) hide show
  1. package/AppRoot.js +80 -0
  2. package/app.js +5 -3
  3. package/constants/unified.js +0 -29
  4. package/entry.js +67 -1
  5. package/global-styles.js +1 -1
  6. package/mfe-exposed-components.js +2 -4
  7. package/package.json +2 -2
  8. package/services/api.js +1 -1
  9. package/services/tests/api.test.js +20 -35
  10. package/styles/containers/layout/_layoutPage.scss +8 -6
  11. package/utils/commonUtils.js +1 -19
  12. package/utils/getDataLayer.js +15 -0
  13. package/utils/gtmTrackers/gtmEvents/creativeDetails.js +2 -1
  14. package/utils/mfeDetect.js +1 -0
  15. package/v2Components/CapActionButton/constants.js +1 -8
  16. package/v2Components/CapActionButton/index.js +144 -202
  17. package/v2Components/CapActionButton/index.scss +14 -232
  18. package/v2Components/CapActionButton/messages.js +3 -19
  19. package/v2Components/CapActionButton/tests/index.test.js +18 -42
  20. package/v2Components/CapImageUpload/index.js +2 -2
  21. package/v2Components/CapTagList/index.js +0 -10
  22. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +48 -70
  23. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  24. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -214
  25. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  26. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +9 -83
  27. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  28. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -60
  29. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  30. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +22 -202
  31. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +80 -381
  32. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +8 -155
  33. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  34. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  35. package/v2Components/CommonTestAndPreview/index.js +222 -761
  36. package/v2Components/CommonTestAndPreview/messages.js +3 -45
  37. package/v2Components/CommonTestAndPreview/sagas.js +6 -25
  38. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -1
  39. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +267 -284
  40. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  41. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  42. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  43. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  44. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  45. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  46. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  47. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -133
  48. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +24 -31
  49. package/v2Components/NavigationBar/index.js +9 -7
  50. package/v2Components/NavigationBar/mfeModuleHeader.config.js +16 -0
  51. package/v2Components/Pagination/_pagination.scss +2 -0
  52. package/v2Components/TemplatePreview/_templatePreview.scss +25 -45
  53. package/v2Components/TemplatePreview/index.js +32 -147
  54. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  55. package/v2Components/TestAndPreviewSlidebox/CustomValuesEditor.js +6 -6
  56. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  57. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  58. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  59. package/v2Containers/Cap/constants.js +1 -0
  60. package/v2Containers/Cap/index.js +47 -21
  61. package/v2Containers/Cap/sagas.js +19 -0
  62. package/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +1 -4
  63. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +0 -4
  64. package/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +20 -20
  65. package/v2Containers/CreativesContainer/SlideBoxContent.js +7 -37
  66. package/v2Containers/CreativesContainer/SlideBoxFooter.js +5 -14
  67. package/v2Containers/CreativesContainer/SlideBoxHeader.js +5 -36
  68. package/v2Containers/CreativesContainer/constants.js +0 -11
  69. package/v2Containers/CreativesContainer/index.js +104 -323
  70. package/v2Containers/CreativesContainer/index.scss +1 -83
  71. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -79
  72. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  73. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  74. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +90 -333
  75. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  76. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  77. package/v2Containers/MobilePush/Create/test/saga.test.js +2 -2
  78. package/v2Containers/Rcs/constants.js +11 -131
  79. package/v2Containers/Rcs/index.js +829 -2682
  80. package/v2Containers/Rcs/index.scss +8 -443
  81. package/v2Containers/Rcs/messages.js +6 -45
  82. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +39409 -74176
  83. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +74 -10
  84. package/v2Containers/Rcs/tests/index.test.js +40 -151
  85. package/v2Containers/Rcs/tests/mockData.js +0 -38
  86. package/v2Containers/Rcs/tests/utils.test.js +30 -643
  87. package/v2Containers/Rcs/utils.js +12 -466
  88. package/v2Containers/Sms/Create/index.js +48 -115
  89. package/v2Containers/SmsTrai/Create/index.js +4 -9
  90. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  91. package/v2Containers/SmsTrai/Edit/index.js +169 -678
  92. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  93. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2854 -5455
  94. package/v2Containers/SmsWrapper/index.js +8 -37
  95. package/v2Containers/TagList/index.js +0 -6
  96. package/v2Containers/Templates/_templates.scss +11 -196
  97. package/v2Containers/Templates/actions.js +0 -11
  98. package/v2Containers/Templates/constants.js +0 -2
  99. package/v2Containers/Templates/index.js +60 -131
  100. package/v2Containers/Templates/sagas.js +13 -57
  101. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1015 -1060
  102. package/v2Containers/Templates/tests/sagas.test.js +16 -199
  103. package/v2Containers/TemplatesV2/TemplatesV2.style.js +10 -75
  104. package/v2Containers/TemplatesV2/index.js +43 -86
  105. package/v2Containers/WeChat/MapTemplates/test/saga.test.js +9 -9
  106. package/v2Containers/Whatsapp/index.js +25 -4
  107. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  108. package/utils/rcsPayloadUtils.js +0 -92
  109. package/utils/templateVarUtils.js +0 -201
  110. package/utils/tests/rcsPayloadUtils.test.js +0 -226
  111. package/utils/tests/templateVarUtils.test.js +0 -204
  112. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  113. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  114. package/v2Components/CommonTestAndPreview/utils.js +0 -84
  115. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -91
  116. package/v2Components/SmsFallback/constants.js +0 -94
  117. package/v2Components/SmsFallback/index.js +0 -958
  118. package/v2Components/SmsFallback/index.scss +0 -266
  119. package/v2Components/SmsFallback/messages.js +0 -78
  120. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -120
  121. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  122. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  123. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  124. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -208
  125. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -309
  126. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  127. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  128. package/v2Components/TemplatePreview/constants.js +0 -2
  129. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  130. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  131. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  132. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -17
  133. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -79
  134. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  135. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  136. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  137. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -227
  138. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  139. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  140. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  141. package/v2Containers/SmsTrai/Edit/index.scss +0 -126
  142. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  143. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  144. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  145. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  146. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-unused-expressions */
2
- import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
2
+ import React, { useState, useEffect, useCallback } from 'react';
3
3
  import { bindActionCreators } from 'redux';
4
4
  import { createStructuredSelector } from 'reselect';
5
- import { FormattedMessage } from 'react-intl';
5
+ import { injectIntl, FormattedMessage } from 'react-intl';
6
6
  import get from 'lodash/get';
7
7
  import isEmpty from 'lodash/isEmpty';
8
8
  import cloneDeep from 'lodash/cloneDeep';
9
9
  import isNil from 'lodash/isNil';
10
+ import styled from 'styled-components';
10
11
  import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
11
12
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
12
13
  import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
@@ -34,14 +35,6 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
34
35
  import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
35
36
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
36
37
  import CapLink from '@capillarytech/cap-ui-library/CapLink';
37
- import CapTab from '@capillarytech/cap-ui-library/CapTab';
38
- import { flushSync } from 'react-dom';
39
- import { isUrl, isValidText } from '../Line/Container/Wrapper/utils';
40
- import {
41
- invalidVarRegex,
42
- RCS_CTA_URL_TYPE,
43
- URL_MAX_LENGTH,
44
- } from '../../v2Components/CapActionButton/constants';
45
38
 
46
39
  import {
47
40
  CAP_G01,
@@ -57,30 +50,17 @@ import {
57
50
  import CapVideoUpload from '../../v2Components/CapVideoUpload';
58
51
  import * as globalActions from '../Cap/actions';
59
52
  import CapActionButton from '../../v2Components/CapActionButton';
60
- import TemplatePreview from '../../v2Components/TemplatePreview';
61
53
  import { makeSelectRcs, makeSelectAccount } from './selectors';
62
54
  import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
63
55
  import {
64
56
  isLoadingMetaEntities,
65
57
  makeSelectMetaEntities,
66
58
  setInjectedTags,
67
- selectCurrentOrgDetails,
68
59
  } from '../Cap/selectors';
69
60
  import * as RcsActions from './actions';
70
61
  import { isAiContentBotDisabled } from '../../utils/common';
71
62
  import * as TemplatesActions from '../Templates/actions';
72
63
  import './index.scss';
73
- import {
74
- normalizeLibraryLoadedTitleDesc,
75
- mergeRcsSmsFallBackContentFromDetails,
76
- mergeRcsSmsFallbackVarMapLayers,
77
- extractRegisteredSenderIdsFromSmsFallbackRecord,
78
- pickFirstSmsFallbackTemplateString,
79
- syncCardVarMappedSemanticsFromSlots,
80
- hasMeaningfulSmsFallbackShape,
81
- getLibrarySmsFallbackApiBaselineFromTemplateData,
82
- pickRcsCardVarMappedEntries,
83
- } from './rcsLibraryHydrationUtils';
84
64
  import {
85
65
  RCS,
86
66
  SMS,
@@ -97,7 +77,6 @@ import {
97
77
  RCS_IMG_SIZE,
98
78
  RCS_DLT_MODE,
99
79
  CTA,
100
- AI_CONTENT_BOT_DISABLED,
101
80
  RCS_STATUSES,
102
81
  TITLE_TEXT,
103
82
  MESSAGE_TEXT,
@@ -112,15 +91,9 @@ import {
112
91
  rcsVarTestRegex,
113
92
  RCS_IMAGE_DIMENSIONS,
114
93
  RCS_TEXT_MESSAGE_MAX_LENGTH,
115
- RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
116
94
  RCS_RICH_CARD_MAX_LENGTH,
117
95
  RCS_VIDEO_THUMBNAIL_DIMENSIONS,
118
- RCS_CAROUSEL_IMAGE_DIMENSIONS,
119
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
120
- RCS_CAROUSEL_IMG_SIZE,
121
- RCS_CAROUSEL_VIDEO_SIZE,
122
96
  MAX_BUTTONS,
123
- INITIAL_SUGGESTIONS,
124
97
  INITIAL_SUGGESTIONS_DATA_STOP,
125
98
  RCS_BUTTON_TYPES,
126
99
  titletype,
@@ -130,50 +103,25 @@ import {
130
103
  SMALL,
131
104
  MEDIUM,
132
105
  RICHCARD,
133
- HOST_INFOBIP,
134
- HOST_ICS,
135
- CAROUSEL_HEIGHT_OPTIONS,
136
- CAROUSEL_WIDTH_OPTIONS,
137
- STOP,
138
- RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
139
- RCS_NUMERIC_VAR_NAME_REGEX,
140
- RCS_NUMERIC_VAR_TOKEN_REGEX,
141
- RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
142
- RCS_TAG_AREA_FIELD_TITLE,
143
- RCS_TAG_AREA_FIELD_DESC,
144
- REGEX_SPECIAL_CHARS_ESCAPE_PATTERN,
145
- JS_TYPES,
146
106
  } from './constants';
147
107
  import globalMessages from '../Cap/messages';
148
108
  import messages from './messages';
149
109
  import creativesMessages from '../CreativesContainer/messages';
150
110
  import withCreatives from '../../hoc/withCreatives';
151
111
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
152
- import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
153
- import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
112
+ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
154
113
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
155
- import { splitTemplateVarString } from '../../utils/templateVarUtils';
156
114
  import CapImageUpload from '../../v2Components/CapImageUpload';
115
+ import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
157
116
  import Templates from '../Templates';
158
117
  import SmsTraiEdit from '../SmsTrai/Edit';
159
- import SmsFallback from '../../v2Components/SmsFallback';
160
- import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
161
118
  import TagList from '../TagList';
162
119
  import { validateTags } from '../../utils/tagValidations';
163
- import { isTraiDLTEnable } from '../../utils/common';
120
+ import { getCdnUrl } from '../../utils/cdnTransformation';
164
121
  import { isTagIncluded } from '../../utils/commonUtils';
165
122
  import injectReducer from '../../utils/injectReducer';
166
123
  import v2RcsReducer from './reducer';
167
- import {
168
- areAllRcsSmsFallbackVarSlotsFilled,
169
- buildRcsNumericMustachePlaceholderRegex,
170
- getTemplateStatusType,
171
- normalizeCardVarMapped,
172
- coalesceCardVarMappedToTemplate,
173
- getRcsSemanticVarNamesSpanningTitleAndDesc,
174
- resolveCardVarMappedSlotValue,
175
- sanitizeCardVarMappedValue,
176
- } from './utils';
124
+ import { getTemplateStatusType } from './utils';
177
125
 
178
126
 
179
127
  const { Group: CapCheckboxGroup } = CapCheckbox;
@@ -190,46 +138,28 @@ export const Rcs = (props) => {
190
138
  templatesActions,
191
139
  globalActions,
192
140
  location,
141
+ handleClose,
193
142
  getDefaultTags,
194
143
  supportedTags,
195
144
  metaEntities,
196
145
  injectedTags,
197
146
  loadingTags,
198
147
  getFormData,
148
+ isDltEnabled,
199
149
  smsRegister,
200
- orgUnitId,
201
150
  selectedOfferDetails,
202
151
  eventContextTags,
152
+ waitEventContextTags,
203
153
  accountData = {},
204
- currentOrgDetails,
205
154
  // TestAndPreviewSlidebox props
206
155
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
207
156
  handleTestAndPreview: propsHandleTestAndPreview,
208
157
  handleCloseTestAndPreview: propsHandleCloseTestAndPreview,
209
- handleClose,
210
158
  } = props || {};
211
159
  const { formatMessage } = intl;
212
160
  const { TextArea } = CapInput;
213
161
  const { CapCustomCardList } = CapCustomCard;
214
-
215
- // Defensive: React cannot render plain objects as children (crashes with
216
- // "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
217
- // can accidentally set an error state to an object (e.g. `{}`).
218
- const normalizeErrorMessage = (err) => {
219
- if (!err) return '';
220
- if (React.isValidElement(err)) return err;
221
- if (typeof err === JS_TYPES.STRING) return err;
222
- if (typeof err === JS_TYPES.NUMBER) return String(err);
223
- if (typeof err === JS_TYPES.OBJECT && typeof err.message === JS_TYPES.STRING) return err.message;
224
- try {
225
- return JSON.stringify(err);
226
- } catch (e) {
227
- return String(err);
228
- }
229
- };
230
-
231
162
  const [isEditFlow, setEditFlow] = useState(false);
232
- const isEditLike = isEditFlow || !isFullMode;
233
163
  const [tags, updateTags] = useState([]);
234
164
  const [spin, setSpin] = useState(false);
235
165
  //template
@@ -238,21 +168,33 @@ export const Rcs = (props) => {
238
168
  const [templateMediaType, setTemplateMediaType] = useState(
239
169
  RCS_MEDIA_TYPES.NONE,
240
170
  );
171
+ const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
241
172
  const [templateTitle, setTemplateTitle] = useState('');
242
173
  const [templateDesc, setTemplateDesc] = useState('');
243
174
  const [templateDescError, setTemplateDescError] = useState(false);
244
175
  const [templateStatus, setTemplateStatus] = useState('');
176
+ const [templateDate, setTemplateDate] = useState('');
177
+ //fallback
178
+ const [fallbackMessage, setFallbackMessage] = useState('');
179
+ const [fallbackMessageError, setFallbackMessageError] = useState(false);
245
180
  //fallback dlt
246
181
  const [showDltContainer, setShowDltContainer] = useState(false);
247
182
  const [dltMode, setDltMode] = useState('');
248
183
  const [dltEditData, setDltEditData] = useState({});
249
- /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
250
- const [smsFallbackData, setSmsFallbackData] = useState(undefined);
184
+ const [showDltCard, setShowDltCard] = useState(false);
185
+ const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
186
+ const [dltPreviewData, setDltPreviewData] = useState('');
251
187
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
252
- const buttonType = RCS_BUTTON_TYPES.NONE;
188
+ const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
253
189
  const [suggestionError, setSuggestionError] = useState(true);
254
- const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
255
190
  const [templateType, setTemplateType] = useState('text_message');
191
+ const [templateHeader, setTemplateHeader] = useState('');
192
+ const [templateMessage, setTemplateMessage] = useState('');
193
+ const [templateHeaderError, setTemplateHeaderError] = useState('');
194
+ const [templateMessageError, setTemplateMessageError] = useState('');
195
+ const validVarRegex = /\{\{(\d+)\}\}/g;
196
+ const [updatedTitleData, setUpdatedTitleData] = useState([]);
197
+ const [updatedDescData, setUpdatedDescData] = useState([]);
256
198
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
257
199
  const [descVarMappedData, setDescVarMappedData] = useState({});
258
200
  const [titleTextAreaId, setTitleTextAreaId] = useState();
@@ -263,58 +205,75 @@ export const Rcs = (props) => {
263
205
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
264
206
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
265
207
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
266
- // Carousel (UI-only) state
267
- const [selectedCarousel, setSelectedCarousel] = useState('');
268
- const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
269
- const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
270
- const [carouselData, setCarouselData] = useState([]);
271
- const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
272
- const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
273
- const [carouselResetNonce, setCarouselResetNonce] = useState(0);
274
- const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
275
- const [imageError, setImageError] = useState(null);
208
+ const [imageError, setImageError] = useState(null);
276
209
  const [templateTitleError, setTemplateTitleError] = useState(false);
277
210
  const [cardVarMapped, setCardVarMapped] = useState({});
278
- /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
279
- const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
280
- const lastHydratedRcsCardVarSignatureRef = useRef(null);
281
-
282
- /**
283
- * Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
284
- * without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
285
- * fallback card / content stopped appearing until re-selected.
286
- */
287
- const rcsHydrationDetails = useMemo(
288
- () => (isFullMode ? rcsData?.templateDetails : templateData),
289
- [isFullMode, rcsData?.templateDetails, templateData],
290
- );
291
-
292
- /** Skip duplicate /meta/TAG fetches: same query is triggered from (1) useEffect below, (2) title TagList mount, (3) description TagList mount — each calls getTagsforContext('Outbound'). */
293
- const lastTagSchemaQueryKeyRef = useRef(null);
294
- /**
295
- * Library: parent often passes a new `templateData` object reference every render. Re-applying the same
296
- * SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
297
- */
298
- const lastSmsFallbackHydrationKeyRef = useRef(null);
299
-
300
- const fetchTagSchemaIfNewQuery = useCallback(
301
- (query) => {
302
- const key = JSON.stringify(query);
303
- if (lastTagSchemaQueryKeyRef.current === key) {
304
- return;
305
- }
306
- lastTagSchemaQueryKeyRef.current = key;
307
- globalActions.fetchSchemaForEntity(query);
308
- },
309
- [globalActions],
310
- );
311
211
 
312
212
  // TestAndPreviewSlidebox state
313
213
  const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
214
+ const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
215
+
216
+ const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
217
+
218
+ // Get template content for TestAndPreviewSlidebox
219
+ // Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
220
+ // getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
221
+ // renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
222
+ // Note: templateHeader and templateMessage are defined but NOT used in the component
223
+ const getTemplateContent = useCallback(() => {
224
+ const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
225
+ const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
226
+ const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
227
+
228
+ // Build media preview object (same pattern as getRcsPreview)
229
+ const mediaPreview = {};
230
+ if (isMediaTypeImage && rcsImageSrc) {
231
+ mediaPreview.rcsImageSrc = rcsImageSrc;
232
+ }
233
+ if (isMediaTypeVideo && !isMediaTypeText) {
234
+ // For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
235
+ if (rcsThumbnailSrc) {
236
+ mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
237
+ } else if (rcsVideoSrc?.videoSrc) {
238
+ mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
239
+ }
240
+ // Also include thumbnailSrc separately if available
241
+ if (rcsThumbnailSrc) {
242
+ mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
243
+ }
244
+ }
245
+
246
+ // Build content object
247
+ // Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
248
+ // templateTitle is used for rich_card/carousel title, empty for text_message
249
+ // templateDesc is used for ALL types (text message body or rich card description)
250
+ // For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
251
+ const contentObj = {
252
+ // Map templateTitle to templateHeader and templateDesc to templateMessage
253
+ templateHeader: templateTitle,
254
+ templateMessage: templateDesc,
255
+ ...mediaPreview,
256
+ ...(suggestions.length > 0 && {
257
+ suggestions: suggestions,
258
+ }),
259
+ };
260
+
261
+ return contentObj;
262
+ }, [
263
+ templateMediaType,
264
+ templateTitle,
265
+ templateDesc,
266
+ rcsImageSrc,
267
+ rcsVideoSrc,
268
+ rcsThumbnailSrc,
269
+ suggestions,
270
+ selectedDimension,
271
+ ]);
314
272
 
315
273
  // Handle Test and Preview button click
316
274
  const handleTestAndPreview = useCallback(() => {
317
275
  setShowTestAndPreviewSlidebox(true);
276
+ setIsTestAndPreviewMode(true);
318
277
  if (propsHandleTestAndPreview) {
319
278
  propsHandleTestAndPreview();
320
279
  }
@@ -338,758 +297,31 @@ export const Rcs = (props) => {
338
297
  // For video
339
298
  return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
340
299
  };
341
- /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
342
- const handleSmsFallbackEditorStateChange = useCallback((patch) => {
343
- setSmsFallbackData((prev) => {
344
- if (!patch || typeof patch !== 'object') return prev;
345
- // Bail out when no template has been selected yet — SmsTraiEdit fires
346
- // onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
347
- // which would create a non-null smsFallbackData from nothing and cause
348
- // the card to appear as "Untitled creative" before any save.
349
- if (!prev) return prev;
350
- return { ...prev, ...patch };
351
- });
352
- }, []);
353
300
 
354
- /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
355
301
  const [accountId, setAccountId] = useState('');
356
- /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
357
- const [wecrmAccountId, setWecrmAccountId] = useState('');
358
302
  const [accessToken, setAccessToken] = useState('');
359
303
  const [hostName, setHostName] = useState('');
360
304
  const [accountName, setAccountName] = useState('');
361
- const isHostInfoBip = hostName === HOST_INFOBIP;
362
- const isHostIcs = hostName === HOST_ICS;
363
-
364
- useEffect(() => {
365
- setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
366
- }, [isHostIcs]);
367
305
  const [rcsAccount, setRcsAccount] = useState('');
368
306
  useEffect(() => {
369
307
  const accountObj = accountData.selectedRcsAccount || {};
370
308
  if (!isEmpty(accountObj)) {
371
309
  const {
372
- id: wecrmId,
373
310
  sourceAccountIdentifier = '',
374
311
  configs = {},
375
312
  } = accountObj;
313
+
376
314
  setAccountId(sourceAccountIdentifier);
377
- setWecrmAccountId(
378
- wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
379
- );
380
315
  setAccessToken(configs.accessToken || '');
381
316
  setHostName(accountObj.hostName || '');
382
317
  setAccountName(accountObj.name || '');
383
318
  setRcsAccount(accountObj.id || '');
384
- } else {
385
- setAccountId('');
386
- setWecrmAccountId('');
387
- setAccessToken('');
388
- setHostName('');
389
- setAccountName('');
390
319
  }
391
320
  }, [accountData.selectedRcsAccount]);
392
321
 
393
322
  const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
394
323
  const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
395
324
  const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
396
- const isCarouselType = templateType === contentType.carousel;
397
-
398
- const MAX_RCS_CAROUSEL_ALLOWED = 10;
399
- // Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
400
- // Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
401
- // and show the same media/thumbnail.
402
- const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
403
- const getCarouselImageAssetIndex = (cardIndex) =>
404
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
405
- const getCarouselVideoAssetIndex = (cardIndex) =>
406
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
407
- const getCarouselThumbnailAssetIndex = (cardIndex) =>
408
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
409
- const isThumbnailAssetIndex = (index) => {
410
- if (index === 1) return true; // standalone thumbnail
411
- if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
412
- return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
413
- }
414
- return false;
415
- };
416
-
417
- // Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
418
- const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
419
-
420
- const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
421
- setCarouselData((prev = []) => {
422
- const updated = cloneDeep(prev);
423
- if (!updated?.[cardIndex]) return updated;
424
- if (clearImage) updated[cardIndex].imageSrc = '';
425
- if (clearVideo) updated[cardIndex].videoAsset = {};
426
- if (clearThumb) updated[cardIndex].thumbnailSrc = '';
427
- return updated;
428
- });
429
-
430
- if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
431
- if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
432
- if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
433
- };
434
-
435
- const resetCarouselMediaForAllCards = () => {
436
- setCarouselData((prev = []) => {
437
- const updated = cloneDeep(prev);
438
- updated.forEach((card) => {
439
- if (!card) return;
440
- card.imageSrc = '';
441
- card.videoAsset = {};
442
- card.thumbnailSrc = '';
443
- });
444
- return updated;
445
- });
446
- (carouselData || []).forEach((_, idx) => {
447
- actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
448
- actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
449
- actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
450
- });
451
- // Force tab panes to remount after global reset (some tab implementations cache inactive panes)
452
- setCarouselResetNonce((n) => n + 1);
453
- };
454
-
455
- const RCS_CAROUSEL_INITIAL_CARD = {
456
- title: '',
457
- description: '',
458
- mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
459
- imageSrc: '',
460
- videoAsset: {}, // CapVideoUpload object shape
461
- thumbnailSrc: '',
462
- suggestions: [],
463
- };
464
-
465
- const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
466
- ...RCS_CAROUSEL_INITIAL_CARD,
467
- suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
468
- };
469
-
470
- const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
471
- const next = cloneDeep(cards || []);
472
- if (next.length === 0) return next;
473
- const s = next[0].suggestions;
474
- if (!Array.isArray(s) || s.length === 0) {
475
- next[0] = {
476
- ...next[0],
477
- suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
478
- };
479
- }
480
- return next;
481
- };
482
-
483
- // Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
484
- // updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
485
- // suggestions typed since the last commit (stale closure).
486
- const handleCarouselValueChange = (carouselIndex, fields) => {
487
- setCarouselData((prev = []) => {
488
- const updated = cloneDeep(prev);
489
- if (!updated[carouselIndex]) return prev;
490
- fields.forEach(({ fieldName, value }) => {
491
- updated[carouselIndex][fieldName] = value;
492
- });
493
- return updated;
494
- });
495
- };
496
-
497
- const updateCarouselErrors = (carouselIndex, patch) => {
498
- setCarouselErrors((prev = []) => {
499
- const next = Array.isArray(prev) ? [...prev] : [];
500
- next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
501
- return next;
502
- });
503
- };
504
-
505
- const deleteCarouselCard = (index) => {
506
- let nextIdx = 0;
507
- flushSync(() => {
508
- setCarouselData((prev = []) => {
509
- const updated = cloneDeep(prev);
510
- if (index < 0 || index >= updated.length) return updated;
511
- updated.splice(index, 1);
512
- nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
513
- return ensureFirstCardDefaultPhoneSuggestions(updated);
514
- });
515
- });
516
- setCarouselErrors((prev = []) => {
517
- const next = Array.isArray(prev) ? [...prev] : [];
518
- next.splice(index, 1);
519
- return next;
520
- });
521
- setActiveCarouselIndex(`${nextIdx}`);
522
- };
523
-
524
- const carouselButtonTextHasForbiddenChars = (value) => {
525
- if (!value) return false;
526
- if (value.includes('[') || value.includes(']')) return true;
527
- const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
528
- if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
529
- return false;
530
- };
531
-
532
- const isCompleteSavedCarouselSuggestion = (s) => {
533
- if (!s || !s.isSaved) return false;
534
- const text = (s.text || '').trim();
535
- if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
536
- if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
537
- return String(s.phoneNumber || '').length >= 5;
538
- }
539
- if (s.type === RCS_BUTTON_TYPES.CTA) {
540
- const url = String(s.url || '').trim();
541
- if (!url || url.length > URL_MAX_LENGTH) return false;
542
- const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
543
- if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
544
- return true;
545
- }
546
- if (!isUrl(url)) return false;
547
- const varMatches = url.match(invalidVarRegex);
548
- return !(varMatches && varMatches.length > 0);
549
- }
550
- if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
551
- return true;
552
- }
553
- return false;
554
- };
555
-
556
- const isCarouselCardValid = (card, cardIndex) => {
557
- if (!card) return false;
558
- if (!card.title || !card.title.trim()) return false;
559
- if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
560
- if (!card.description || !card.description.trim()) return false;
561
- if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
562
- let mediaOk = false;
563
- if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
564
- mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
565
- } else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
566
- const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
567
- const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
568
- mediaOk = hasVideo && hasThumb;
569
- } else {
570
- return false;
571
- }
572
- if (!mediaOk) return false;
573
- if (cardIndex === 0) {
574
- const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
575
- if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
576
- }
577
- return true;
578
- };
579
-
580
- const checkDisableAddCarouselButton = () => {
581
- const idx = parseInt(activeCarouselIndex, 10);
582
- const activeCard = carouselData?.[idx];
583
- return !isCarouselCardValid(activeCard, idx);
584
- };
585
-
586
- const addCarouselCard = () => {
587
- let newIndex = 0;
588
- flushSync(() => {
589
- setCarouselData((prev = []) => {
590
- const updated = cloneDeep(prev);
591
- updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
592
- newIndex = updated.length - 1;
593
- return updated;
594
- });
595
- });
596
- setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
597
- setActiveCarouselIndex(`${newIndex}`);
598
- };
599
-
600
- // Initialize carousel data when switching to carousel type
601
- useEffect(() => {
602
- if (!isCarouselType) return;
603
- if (!carouselData || carouselData.length === 0) {
604
- setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
605
- setCarouselErrors([{}]);
606
- setActiveCarouselIndex('0');
607
- }
608
- }, [isCarouselType]);
609
-
610
- // keep derived carousel key in sync
611
- useEffect(() => {
612
- if (!isCarouselType) return;
613
- if (!selectedCarouselHeight || !selectedCarouselWidth) return;
614
- // Required format: HEIGHT_WIDTH
615
- setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
616
- }, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
617
-
618
- const renderCarouselDimensionSelection = () => {
619
- if (!isCarouselType) return null;
620
- return (
621
- <CapRow className="rcs-carousel-dimension-section">
622
- <CapRow gutter={16} className="rcs-carousel-dimension-row">
623
- <CapColumn span={12}>
624
- <CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
625
- {isEditFlow ? (
626
- <CapHeading type="h5" className="rcs-creative-name-value">
627
- {CAROUSEL_HEIGHT_OPTIONS.find((option) => option.value === selectedCarouselHeight)?.label || '-'}
628
- </CapHeading>
629
- ) : (
630
- <CapSelect
631
- id="rcs-carousel-height-select"
632
- value={selectedCarouselHeight}
633
- onChange={(val) => {
634
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
635
- resetCarouselMediaForAllCards();
636
- setSelectedCarouselHeight(val);
637
- }}
638
- options={CAROUSEL_HEIGHT_OPTIONS}
639
- disabled={!isFullMode}
640
- />
641
- )}
642
- </CapColumn>
643
- <CapColumn span={12}>
644
- <CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
645
- {isEditFlow ? (
646
- <CapHeading type="h5" className="rcs-creative-name-value">
647
- {CAROUSEL_WIDTH_OPTIONS.find((option) => option.value === selectedCarouselWidth)?.label || '-'}
648
- </CapHeading>
649
- ) : (
650
- <CapSelect
651
- id="rcs-carousel-width-select"
652
- value={selectedCarouselWidth}
653
- onChange={(val) => {
654
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
655
- resetCarouselMediaForAllCards();
656
- setSelectedCarouselWidth(val);
657
- }}
658
- options={CAROUSEL_WIDTH_OPTIONS}
659
- disabled={!isFullMode}
660
- />
661
- )}
662
- </CapColumn>
663
- </CapRow>
664
- {!!selectedCarousel && (
665
- <CapLabel type="label3" className="rcs-carousel-selected-dimension">
666
- Selected: {selectedCarousel}
667
- </CapLabel>
668
- )}
669
- </CapRow>
670
- );
671
- };
672
-
673
- // Reuse rich-card buttons UI per carousel card
674
- const renderButtonComponentForCarouselCard = (cardIndex) => {
675
- const card = carouselData?.[cardIndex] || {};
676
- const suggestionsForCard = card.suggestions || [];
677
- return (
678
- <>
679
- <CapHeader
680
- className="rcs-button-cta"
681
- title={(
682
- <CapRow type="flex">
683
- <CapHeading type="h4">
684
- {formatMessage(messages.btnLabel)}
685
- </CapHeading>
686
- </CapRow>
687
- )}
688
- description={!isEditFlow && (
689
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
690
- )}
691
- />
692
- <CapActionButton
693
- buttonType={RCS_BUTTON_TYPES.NONE}
694
- updateButtonChange={(data, btnIndex) => {
695
- // Match existing behavior: allow CapActionButton to manage save gating.
696
- const updated = cloneDeep(suggestionsForCard);
697
- if (btnIndex === MAX_BUTTONS) {
698
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
699
- return;
700
- }
701
- updated[btnIndex] = data;
702
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
703
- }}
704
- deleteButtonHandler={(btnIndex) => {
705
- if (cardIndex === 0 && btnIndex === 0) {
706
- return;
707
- }
708
- const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
709
- const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
710
- if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
711
- return;
712
- }
713
- const updated = cloneDeep(suggestionsForCard)
714
- .filter((i) => i.index !== btnIndex)
715
- .map((i, idx) => ({ ...i, index: idx }));
716
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
717
- }}
718
- suggestions={suggestionsForCard}
719
- isEditFlow={isEditFlow}
720
- isFullMode={isFullMode}
721
- maxButtons={MAX_BUTTONS}
722
- host={hostName}
723
- minSavedSuggestions={cardIndex === 0 ? 1 : 0}
724
- hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
725
- />
726
- </>
727
- );
728
- };
729
-
730
- const renderCarouselCardMedia = (cardIndex) => {
731
- const card = carouselData?.[cardIndex] || {};
732
- const dimKey = getCarouselDimensionKey();
733
-
734
- if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
735
- return (
736
- <>
737
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
738
- <CapVideoUpload
739
- index={getCarouselVideoAssetIndex(cardIndex)}
740
- allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
741
- videoSize={RCS_CAROUSEL_VIDEO_SIZE}
742
- isFullMode={isFullMode}
743
- uploadAsset={uploadRcsVideo}
744
- uploadedAssetList={card.videoAsset || {}}
745
- onVideoUploadUpdateAssestList={(_, val) => {
746
- handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
747
- }}
748
- videoData={rcsData}
749
- className="cap-custom-video-upload"
750
- formClassName={"rcs-video-upload"}
751
- channel={RCS}
752
- errorMessage={formatMessage(messages.videoErrorMessage)}
753
- showVideoNameAndDuration={false}
754
- showReUploadButton={!isEditFlow && isFullMode}
755
- channelSpecificStyle={!isFullMode}
756
- />
757
-
758
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
759
- <CapImageUpload
760
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
761
- imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
762
- imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
763
- imgSize={RCS_THUMBNAIL_MAX_SIZE}
764
- uploadAsset={uploadRcsImage}
765
- isFullMode={isFullMode}
766
- imageSrc={card.thumbnailSrc}
767
- updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
768
- updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
769
- minImgSize={RCS_THUMBNAIL_MIN_SIZE}
770
- index={getCarouselThumbnailAssetIndex(cardIndex)}
771
- className="cap-custom-image-upload"
772
- key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
773
- imageData={rcsData}
774
- channel={RCS}
775
- channelSpecificStyle={!isFullMode}
776
- skipDimensionValidation={true}
777
- showReUploadButton={!isEditFlow && isFullMode}
778
- disabled={isEditFlow || !isFullMode}
779
- />
780
- </>
781
- );
782
- }
783
-
784
- // Default: IMAGE
785
- return (
786
- <>
787
- <CapHeading type="h4" className="rcs-image-dimensions-label">{formatMessage(messages.mediaImage)}</CapHeading>
788
- <CapImageUpload
789
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
790
- imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
791
- imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
792
- imgSize={RCS_CAROUSEL_IMG_SIZE}
793
- uploadAsset={uploadRcsImage}
794
- isFullMode={isFullMode}
795
- imageSrc={card.imageSrc}
796
- updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
797
- updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
798
- index={getCarouselImageAssetIndex(cardIndex)}
799
- className="cap-custom-image-upload"
800
- key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
801
- imageData={rcsData}
802
- channel={RCS}
803
- channelSpecificStyle={!isFullMode}
804
- skipDimensionValidation={true}
805
- showReUploadButton={!isEditFlow && isFullMode}
806
- disabled={isEditFlow || !isFullMode}
807
- />
808
- </>
809
- );
810
- };
811
-
812
- const renderCarouselCardButtons = (cardIndex) => {
813
- return renderButtonComponentForCarouselCard(cardIndex);
814
- };
815
-
816
- const getCarouselTabPanes = () => {
817
- return (carouselData || []).map((card, index) => {
818
- return {
819
- key: String(index),
820
- tab: index + 1,
821
- content: (
822
- <CapCard
823
- title={`Card ${index + 1}`}
824
- extra={
825
- !isEditFlow &&
826
- (carouselData.length === 1 ? (
827
- <CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
828
- <span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
829
- <CapButton
830
- className="rcs-carousel-card-delete"
831
- type="flat"
832
- onClick={() => deleteCarouselCard(index)}
833
- disabled
834
- aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
835
- >
836
- <CapIcon type="delete" />
837
- </CapButton>
838
- </span>
839
- </CapTooltip>
840
- ) : (
841
- <CapButton
842
- className="rcs-carousel-card-delete"
843
- type="flat"
844
- onClick={() => deleteCarouselCard(index)}
845
- aria-label={formatMessage(globalMessages.delete)}
846
- >
847
- <CapIcon type="delete" />
848
- </CapButton>
849
- ))
850
- }
851
- className="rcs-carousel-card"
852
- >
853
- {/* Media selection should be at top of card */}
854
- {isEditFlow ? (
855
- <div className="rcs-creative-name-readonly">
856
- <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
857
- <CapHeading type="h5" className="rcs-creative-name-value">
858
- {mediaRadioOptions.find((option) => option.value === card.mediaType)?.label || '-'}
859
- </CapHeading>
860
- </div>
861
- ) : (
862
- <CapRow className="rcs-carousel-media-selection">
863
- <CapColumn className="rcs-carousel-media-selection-heading">
864
- <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
865
- </CapColumn>
866
- <CapColumn>
867
- <CapRadioGroup
868
- id={`rcs-carousel-media-radio-${index}`}
869
- options={mediaRadioOptions}
870
- value={card.mediaType}
871
- onChange={({ target: { value } }) => {
872
- // Reset media fields when switching type
873
- if (value === RCS_MEDIA_TYPES.IMAGE) {
874
- // Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
875
- clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
876
- handleCarouselValueChange(index, [
877
- { fieldName: 'mediaType', value },
878
- { fieldName: 'videoAsset', value: {} },
879
- { fieldName: 'thumbnailSrc', value: '' },
880
- ]);
881
- } else {
882
- // Switching to VIDEO: clear image upload so it doesn't auto-restore.
883
- clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
884
- handleCarouselValueChange(index, [
885
- { fieldName: 'mediaType', value },
886
- { fieldName: 'imageSrc', value: '' },
887
- ]);
888
- }
889
- }}
890
- disabled={!isFullMode}
891
- className="rcs-radio"
892
- />
893
- </CapColumn>
894
- </CapRow>
895
- )}
896
-
897
- <CapRow className={`rcs-carousel-media-upload ${isEditFlow ? 'rcs-carousel-media-upload--readonly-above' : ''}`}>
898
- {renderCarouselCardMedia(index)}
899
- </CapRow>
900
-
901
- {/* Title after media */}
902
- <CapRow className="rcs-carousel-card-row">
903
- <CapHeader
904
- className="rcs-template-title-label"
905
- title={<CapHeading type="h4">Card title</CapHeading>}
906
- suffix={
907
- (isEditFlow || !isFullMode) ? (
908
- <TagList
909
- label={formatMessage(globalMessages.addLabels)}
910
- onTagSelect={onCarouselTagSelect}
911
- location={location}
912
- tags={getRcsTags()}
913
- onContextChange={handleOnTagsContextChange}
914
- injectedTags={injectedTags || {}}
915
- selectedOfferDetails={selectedOfferDetails}
916
- />
917
- ) : (
918
- <CapButton
919
- data-testid={`rcs-carousel-title-add-var-${index}`}
920
- type="flat"
921
- isAddBtn
922
- onClick={() => appendVarToCarouselField(index, 'title')}
923
- disabled={!!carouselErrors?.[index]?.title}
924
- >
925
- {formatMessage(messages.addVar)}
926
- </CapButton>
927
- )
928
- }
929
- />
930
- <CapRow className="rcs_text_area_wrapper">
931
- {(isEditFlow || !isFullMode) ? (
932
- renderCarouselEditMessage(card.title || '')
933
- ) : (
934
- <CapInput
935
- value={card.title || ''}
936
- placeholder={formatMessage(messages.templateTitlePlaceholder)}
937
- onChange={({ target: { value } }) => {
938
- let error = false;
939
- if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
940
- error = formatMessage(messages.templateHeaderLengthError);
941
- } else {
942
- error = variableErrorHandling(value);
943
- }
944
- updateCarouselErrors(index, { title: error });
945
- handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
946
- }}
947
- disabled={isEditFlow || !isFullMode}
948
- errorMessage={carouselErrors?.[index]?.title}
949
- />
950
- )}
951
- </CapRow>
952
- </CapRow>
953
- {!isEditFlow && (
954
- <CapRow className="rcs-carousel-character-count-row">
955
- {renderCarouselCharacterCount(
956
- getCarouselTitleCharacterCount(index),
957
- getTitleMaxLength(),
958
- )}
959
- </CapRow>
960
- )}
961
-
962
- {/* Description after title */}
963
- <CapRow className="rcs-carousel-card-row">
964
- <CapHeader
965
- title={<CapHeading type="h4">Card body text</CapHeading>}
966
- suffix={
967
- (isEditFlow || !isFullMode) ? (
968
- <TagList
969
- label={formatMessage(globalMessages.addLabels)}
970
- onTagSelect={onCarouselTagSelect}
971
- location={location}
972
- tags={getRcsTags()}
973
- onContextChange={handleOnTagsContextChange}
974
- injectedTags={injectedTags || {}}
975
- selectedOfferDetails={selectedOfferDetails}
976
- />
977
- ) : (
978
- <CapButton
979
- data-testid={`rcs-carousel-desc-add-var-${index}`}
980
- type="flat"
981
- isAddBtn
982
- onClick={() => appendVarToCarouselField(index, 'description')}
983
- disabled={!!carouselErrors?.[index]?.description}
984
- >
985
- {formatMessage(messages.addVar)}
986
- </CapButton>
987
- )
988
- }
989
- />
990
- <CapRow className="rcs_text_area_wrapper">
991
- {(isEditFlow || !isFullMode) ? (
992
- renderCarouselEditMessage(card.description || '')
993
- ) : (
994
- <TextArea
995
- autosize={{ minRows: 3, maxRows: 5 }}
996
- value={card.description || ''}
997
- placeholder={formatMessage(messages.templateDescPlaceholder)}
998
- onChange={({ target: { value } }) => {
999
- let error = false;
1000
- if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
1001
- error = formatMessage(messages.templateMessageLengthError);
1002
- } else {
1003
- error = variableErrorHandling(value);
1004
- }
1005
- updateCarouselErrors(index, { description: error });
1006
- handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
1007
- }}
1008
- disabled={isEditFlow || !isFullMode}
1009
- errorMessage={
1010
- carouselErrors?.[index]?.description && (
1011
- <CapError className="rcs-template-message-error">
1012
- {carouselErrors[index].description}
1013
- </CapError>
1014
- )
1015
- }
1016
- />
1017
- )}
1018
- </CapRow>
1019
- </CapRow>
1020
- {!isEditFlow && (
1021
- <CapRow className="rcs-carousel-character-count-row">
1022
- {renderCarouselCharacterCount(
1023
- getCarouselDescriptionCharacterCount(index),
1024
- getDescriptionMaxLength(),
1025
- )}
1026
- </CapRow>
1027
- )}
1028
-
1029
- <CapDivider className="rcs-carousel-card-divider" />
1030
- {renderCarouselCardButtons(index)}
1031
- </CapCard>
1032
- ),
1033
- };
1034
- });
1035
- };
1036
-
1037
- const renderCarouselSection = () => {
1038
- if (!isCarouselType) return null;
1039
-
1040
- const isMaxCarouselCardsReached = MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0);
1041
- const addCardTooltipTitle = isMaxCarouselCardsReached
1042
- ? formatMessage(messages.rcsCarouselMaxCardTooltip, { max: MAX_RCS_CAROUSEL_ALLOWED })
1043
- // "Set up the first card" only makes sense while creating a new template —
1044
- // in edit flow the cards already exist, so don't show it there.
1045
- : (!isEditFlow && checkDisableAddCarouselButton() ? formatMessage(messages.rcsCarouselAddCardDisabledTooltip) : '');
1046
-
1047
- // No "+" in edit flow (cards can't be added there) — and with no "+", the
1048
- // divider before it has nothing to separate, so skip both together instead
1049
- // of rendering a dangling divider.
1050
- const operations = isEditFlow ? null : (
1051
- <>
1052
- <CapDivider type="vertical" />
1053
- <CapTooltip title={addCardTooltipTitle}>
1054
- <span className="button-disabled-tooltip-wrapper">
1055
- <CapButton
1056
- onClick={addCarouselCard}
1057
- type="flat"
1058
- className="add-carousel-content-button"
1059
- disabled={
1060
- isEditFlow ||
1061
- !isFullMode ||
1062
- isMaxCarouselCardsReached ||
1063
- checkDisableAddCarouselButton()
1064
- }
1065
- >
1066
- <CapIcon type="plus" />
1067
- </CapButton>
1068
- </span>
1069
- </CapTooltip>
1070
- </>
1071
- );
1072
-
1073
- return (
1074
- <CapRow className={`rcs-carousel-section ${isEditFlow ? 'rcs-carousel-section--readonly-above' : ''}`}>
1075
- {renderCarouselDimensionSelection()}
1076
- <CapRow className="rcs-carousel-tab">
1077
- <CapTab
1078
- key={`rcs-carousel-tab-${carouselResetNonce}`}
1079
- defaultActiveKey="0"
1080
- activeKey={activeCarouselIndex}
1081
- tabBarExtraContent={operations}
1082
- onChange={(key) => {
1083
- setActiveCarouselIndex(`${key}`);
1084
- // reset focused var when switching cards (as per requirement)
1085
- setCarouselFocusedVarId('');
1086
- }}
1087
- panes={getCarouselTabPanes()}
1088
- />
1089
- </CapRow>
1090
- </CapRow>
1091
- );
1092
- };
1093
325
 
1094
326
  const mediaRadioOptions = [
1095
327
  {
@@ -1098,16 +330,11 @@ export const Rcs = (props) => {
1098
330
  },
1099
331
  {
1100
332
  value: RCS_MEDIA_TYPES.VIDEO,
1101
- label: formatMessage(
1102
- templateType === contentType.carousel
1103
- ? messages.carouselMediaVideoOption
1104
- : messages.mediaVideo,
1105
- ),
333
+ label: formatMessage(messages.mediaVideo),
1106
334
  },
1107
335
  ];
1108
336
  const aiContentBotDisabled = isAiContentBotDisabled();
1109
337
 
1110
-
1111
338
  const updateButtonChange = (data, index) => {
1112
339
  if (data && data.text) {
1113
340
  const forbiddenError = forbiddenCharactersValidation(data.text);
@@ -1144,9 +371,7 @@ export const Rcs = (props) => {
1144
371
  if (isFullMode) return;
1145
372
  if (loadingTags || !tags || tags.length === 0) return;
1146
373
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1147
- const slotOffset =
1148
- type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
1149
- const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
374
+ const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
1150
375
  if (!resolved) {
1151
376
  if (type === TITLE_TEXT) setTemplateTitleError(false);
1152
377
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -1156,7 +381,7 @@ export const Rcs = (props) => {
1156
381
  let contentForValidation = resolved;
1157
382
  const placeholderTokens = templateStr.match(rcsVarRegex) || [];
1158
383
  placeholderTokens.forEach((t) => {
1159
- const escaped = t.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
384
+ const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1160
385
  contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
1161
386
  });
1162
387
  if (!contentForValidation.trim()) {
@@ -1173,16 +398,10 @@ export const Rcs = (props) => {
1173
398
  tagModule: getDefaultTags,
1174
399
  isFullMode,
1175
400
  }) || {};
1176
- const unsupportedTagsLengthCheck =
1177
- validationResponse?.unsupportedTags?.length > 0;
1178
- const errorMsg =
1179
- (unsupportedTagsLengthCheck &&
1180
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1181
- unsupportedTags: validationResponse.unsupportedTags,
1182
- })) ||
1183
- (validationResponse.isBraceError &&
1184
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1185
- false;
401
+ const errorMsg =
402
+ (validationResponse?.isBraceError &&
403
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
404
+ false;
1186
405
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
1187
406
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
1188
407
  };
@@ -1195,98 +414,15 @@ export const Rcs = (props) => {
1195
414
  validateResolvedTagsForType(MESSAGE_TEXT);
1196
415
  }, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
1197
416
 
1198
- useEffect(() => {
1199
- if (isFullMode || !isCarouselType) return;
1200
- if (loadingTags || !tags || tags.length === 0) return;
1201
- (carouselData || []).forEach((card, idx) => {
1202
- ['title', 'description'].forEach((field) => {
1203
- const templateStr = card?.[field] || '';
1204
- if (!templateStr) return;
1205
- const resolved = resolveTemplateWithMap(templateStr);
1206
- if (!resolved) {
1207
- updateCarouselErrors(idx, { [field]: false });
1208
- return;
1209
- }
1210
- let contentForValidation = resolved;
1211
- const placeholderTokens = templateStr.match(rcsVarRegex) || [];
1212
- placeholderTokens.forEach((t) => {
1213
- const escaped = t.replace(REGEX_SPECIAL_CHARS_ESCAPE_PATTERN, '\\$&');
1214
- contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
1215
- });
1216
- if (!contentForValidation.trim()) {
1217
- updateCarouselErrors(idx, { [field]: false });
1218
- return;
1219
- }
1220
- const validationResponse = validateTags({
1221
- content: contentForValidation,
1222
- tagsParam: tags,
1223
- injectedTagsParams: injectedTags,
1224
- location,
1225
- tagModule: getDefaultTags,
1226
- eventContextTags,
1227
- isFullMode,
1228
- }) || {};
1229
- const errorMsg =
1230
- (validationResponse?.unsupportedTags?.length > 0 &&
1231
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1232
- unsupportedTags: validationResponse.unsupportedTags,
1233
- })) ||
1234
- (validationResponse.isBraceError &&
1235
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1236
- false;
1237
- updateCarouselErrors(idx, { [field]: errorMsg });
1238
- });
1239
- });
1240
- }, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
1241
-
1242
417
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
1243
418
 
1244
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
1245
-
1246
- /** Same `{{tag}}` in both title and description must not share one semantic map entry. */
1247
- const rcsSpanningSemanticVarNames = useMemo(
1248
- () => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
1249
- [templateTitle, templateDesc],
1250
- );
1251
-
1252
- /** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
1253
- const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
1254
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
1255
- const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
1256
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
1257
- let varOrdinal = 0;
1258
- for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
1259
- const segmentToken = templateSegments[segmentIndexInField];
1260
- if (rcsVarTestRegex.test(segmentToken)) {
1261
- if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
1262
- return offset + varOrdinal;
1263
- }
1264
- varOrdinal += 1;
1265
- }
1266
- }
1267
- return null;
1268
- };
1269
-
1270
- /**
1271
- * Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
1272
- * multi-field variable resolution.
1273
- */
1274
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
419
+ const resolveTemplateWithMap = (str = '') => {
1275
420
  if (!str) return '';
1276
- const arr = splitTemplateVarStringRcs(str);
1277
- let varOrdinal = 0;
421
+ const arr = splitTemplateVarString(str);
1278
422
  return arr.map((elem) => {
1279
423
  if (rcsVarTestRegex.test(elem)) {
1280
424
  const key = getVarNameFromToken(elem);
1281
- const globalSlot = slotOffset + varOrdinal;
1282
- varOrdinal += 1;
1283
- const v = resolveCardVarMappedSlotValue(
1284
- cardVarMapped,
1285
- key,
1286
- globalSlot,
1287
- isEditLike,
1288
- rcsSpanningSemanticVarNames.has(key),
1289
- );
425
+ const v = cardVarMapped?.[key];
1290
426
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
1291
427
  return String(v);
1292
428
  }
@@ -1294,145 +430,39 @@ export const Rcs = (props) => {
1294
430
  }).join('');
1295
431
  };
1296
432
 
1297
- const buildCarouselCardsForPreview = (cards = []) => {
1298
- // Track cumulative variable slot index across all cards so resolveTemplateWithMap
1299
- // uses the correct globalSlot → slotKey for each token (e.g. card 1's {{3}} needs
1300
- // slotOffset=2 so slotKey becomes "3", not "1").
1301
- let slotOffset = 0;
1302
- return (cards || []).map((card = {}) => {
1303
- const rawTitle = card.title || '';
1304
- const rawDesc = card.description || '';
1305
- const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
1306
- const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
1307
- const resolvedTitle = !isFullMode
1308
- ? resolveTemplateWithMap(rawTitle, slotOffset)
1309
- : rawTitle;
1310
- const resolvedDesc = !isFullMode
1311
- ? resolveTemplateWithMap(rawDesc, slotOffset + titleVarCount)
1312
- : rawDesc;
1313
- if (!isFullMode) {
1314
- slotOffset += titleVarCount + descVarCount;
1315
- }
1316
- const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
1317
- const videoSrc = card.videoAsset?.videoSrc || '';
1318
- return {
1319
- mediaType: (card.mediaType || '').toLowerCase(),
1320
- imageSrc: card.imageSrc || '',
1321
- videoSrc,
1322
- videoPreviewImg: videoThumb,
1323
- title: resolvedTitle,
1324
- bodyText: resolvedDesc,
1325
- suggestions: card.suggestions || [],
1326
- };
1327
- });
1328
- };
1329
-
1330
- /**
1331
- * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
1332
- * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
1333
- * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
1334
- */
1335
- const getTemplateContent = useCallback(() => {
1336
- if (templateType === contentType.carousel) {
1337
- const carouselDimKey = getCarouselDimensionKey();
1338
- const carouselImgDims =
1339
- RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
1340
- const carouselVidDims =
1341
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
1342
- || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
1343
- return {
1344
- carouselData: buildCarouselCardsForPreview(carouselData),
1345
- carouselPreviewDimensions: {
1346
- imageWidth: carouselImgDims.width,
1347
- imageHeight: carouselImgDims.height,
1348
- videoThumbWidth: carouselVidDims.width,
1349
- videoThumbHeight: carouselVidDims.height,
1350
- },
1351
- };
1352
- }
1353
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
1354
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
1355
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
1356
-
1357
- const isSlotMappingMode = isEditFlow || !isFullMode;
1358
- const titleVarCountForResolve = isMediaTypeText
1359
- ? 0
1360
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
1361
- const resolvedTitle = isMediaTypeText
1362
- ? ''
1363
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
1364
- const resolvedDesc = isSlotMappingMode
1365
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
1366
- : templateDesc;
1367
-
1368
- const mediaPreview = {};
1369
- if (isMediaTypeImage && rcsImageSrc) {
1370
- mediaPreview.rcsImageSrc = rcsImageSrc;
1371
- }
1372
- if (isMediaTypeVideo && !isMediaTypeText) {
1373
- if (rcsThumbnailSrc) {
1374
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
1375
- } else if (rcsVideoSrc?.videoSrc) {
1376
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
1377
- }
1378
- if (rcsThumbnailSrc) {
1379
- mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
1380
- }
1381
- }
1382
-
1383
- const contentObj = {
1384
- templateHeader: resolvedTitle,
1385
- templateMessage: resolvedDesc,
1386
- ...mediaPreview,
1387
- ...(suggestions.length > 0 && {
1388
- suggestions: suggestions,
1389
- }),
1390
- };
1391
433
 
1392
- return contentObj;
1393
- }, [
1394
- templateMediaType,
1395
- templateTitle,
1396
- templateDesc,
1397
- rcsImageSrc,
1398
- rcsVideoSrc,
1399
- rcsThumbnailSrc,
1400
- suggestions,
1401
- selectedDimension,
1402
- isFullMode,
1403
- isEditFlow,
1404
- cardVarMapped,
1405
- rcsSpanningSemanticVarNames,
1406
- carouselData,
1407
- selectedCarouselHeight,
1408
- selectedCarouselWidth,
1409
- ]);
434
+ useEffect(() => {
435
+ if (isFullMode || isEditFlow) return;
436
+ const tokens = [
437
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
438
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
439
+ ];
440
+ if (!tokens.length) return;
441
+ setCardVarMapped((prev) => {
442
+ const next = { ...(prev || {}) };
443
+ let changed = false;
444
+ tokens.forEach((t) => {
445
+ const name = getVarNameFromToken(t);
446
+ if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
447
+ next[name] = '';
448
+ changed = true;
449
+ }
450
+ });
451
+ return changed ? next : prev;
452
+ });
453
+ }, [isFullMode, templateTitle, templateDesc]);
1410
454
 
1411
- const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
1412
455
 
456
+ const RcsLabel = styled.div`
457
+ display: flex;
458
+ margin-top: 20px;
459
+ `;
1413
460
  const paramObj = params || {};
1414
461
  useEffect(() => {
1415
462
  const { id } = paramObj;
1416
463
  if (id && isFullMode) {
1417
464
  setSpin(true);
1418
465
  actions.getTemplateDetails(id, setSpin);
1419
- } else if (!id && isFullMode) {
1420
- // Create New: clear standalone media and ALL possible carousel card Redux slots.
1421
- // Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
1422
- updateRcsImageSrc('');
1423
- setRcsVideoSrc({});
1424
- setRcsThumbnailSrc('');
1425
- setAssetList({});
1426
- setEditFlow(false);
1427
- actions.clearRcsMediaAsset(0);
1428
- actions.clearRcsMediaAsset(1);
1429
- for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
1430
- actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
1431
- actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
1432
- actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
1433
- }
1434
- setCarouselData([]);
1435
- setCarouselErrors([]);
1436
466
  }
1437
467
  return () => {
1438
468
  actions.clearEditResponse();
@@ -1440,68 +470,59 @@ export const Rcs = (props) => {
1440
470
  }, [paramObj.id]);
1441
471
 
1442
472
  useEffect(() => {
1443
- if (!(isEditFlow || !isFullMode)) return;
1444
-
1445
- const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
473
+ if (!(isEditFlow || !isFullMode)) return;
1446
474
 
1447
- const initField = (targetString, setVarMap, slotOffset) => {
1448
- const arr = splitTemplateVarStringRcs(targetString);
1449
- if (!arr?.length) {
1450
- setVarMap({});
1451
- return;
1452
- }
1453
- const nextVarMap = {};
1454
- let varOrdinal = 0;
1455
- arr.forEach((elem, idx) => {
1456
- // Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
1457
- if (rcsVarTestRegex.test(elem)) {
1458
- const id = `${elem}_${idx}`;
1459
- const varName = getVarNameFromToken(elem);
1460
- const globalSlot = slotOffset + varOrdinal;
1461
- varOrdinal += 1;
1462
- const mappedValue = resolveCardVarMappedSlotValue(
1463
- cardVarMapped,
1464
- varName,
1465
- globalSlot,
1466
- isEditLike,
1467
- rcsSpanningSemanticVarNames.has(varName),
1468
- );
1469
- nextVarMap[id] = mappedValue;
475
+ const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
476
+ const arr = splitTemplateVarString(targetString);
477
+ if (!arr?.length) {
478
+ setVarMap({});
479
+ setUpdated([]);
480
+ return;
1470
481
  }
1471
- });
1472
- setVarMap(nextVarMap);
1473
- };
1474
-
1475
- initField(templateTitle, setTitleVarMappedData, 0);
1476
- initField(templateDesc, setDescVarMappedData, titleTokenCount);
1477
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
482
+ const nextVarMap = {};
483
+ const nextUpdated = [...arr];
484
+ arr.forEach((elem, idx) => {
485
+ // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
486
+ if (rcsVarTestRegex.test(elem)) {
487
+ const id = `${elem}_${idx}`;
488
+ const varName = getVarNameFromToken(elem);
489
+ const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
490
+ nextVarMap[id] = mappedValue;
491
+ if (mappedValue !== '') {
492
+ nextUpdated[idx] = mappedValue;
493
+ } else {
494
+ nextUpdated[idx] = elem;
495
+ }
496
+ }
497
+ });
498
+ setVarMap(nextVarMap);
499
+ setUpdated(nextUpdated);
500
+ };
1478
501
 
1479
- useEffect(() => {
1480
- if (!isEditFlow && isFullMode) {
502
+ initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
503
+ initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
504
+ }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
505
+
506
+ useEffect(() => {
507
+ if(!isEditFlow && isFullMode){
1481
508
  setRcsVideoSrc({});
1482
509
  updateRcsImageSrc('');
1483
- setRcsThumbnailSrc('');
510
+ setUpdateRcsImageSrc('');
511
+ updateRcsThumbnailSrc('');
1484
512
  setAssetList({});
1485
- }
1486
- }, [templateMediaType]);
513
+ }
514
+ }, [templateMediaType]);
1487
515
 
1488
- /** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
1489
- const templateStatusHelper = (cardContentFirst) => {
1490
- const raw =
1491
- cardContentFirst?.Status
1492
- ?? cardContentFirst?.status
1493
- ?? cardContentFirst?.approvalStatus
1494
- ?? '';
1495
- const status = typeof raw === 'string' ? raw.trim() : String(raw);
1496
- const n = status.toLowerCase();
1497
- switch (n) {
516
+ const templateStatusHelper = (details) => {
517
+ const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
518
+ switch (status) {
1498
519
  case RCS_STATUSES.approved:
1499
520
  setTemplateStatus(RCS_STATUSES.approved);
1500
521
  break;
1501
522
  case RCS_STATUSES.pending:
1502
523
  setTemplateStatus(RCS_STATUSES.pending);
1503
524
  break;
1504
- case RCS_STATUSES.awaitingApproval.toLowerCase():
525
+ case RCS_STATUSES.awaitingApproval:
1505
526
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
1506
527
  break;
1507
528
  case RCS_STATUSES.unavailable:
@@ -1510,9 +531,6 @@ export const Rcs = (props) => {
1510
531
  case RCS_STATUSES.rejected:
1511
532
  setTemplateStatus(RCS_STATUSES.rejected);
1512
533
  break;
1513
- case RCS_STATUSES.created:
1514
- setTemplateStatus(RCS_STATUSES.created);
1515
- break;
1516
534
  default:
1517
535
  setTemplateStatus(status);
1518
536
  break;
@@ -1566,357 +584,48 @@ export const Rcs = (props) => {
1566
584
  };
1567
585
 
1568
586
  useEffect(() => {
1569
- const details = rcsHydrationDetails;
587
+ const details = isFullMode ? rcsData?.templateDetails : templateData;
1570
588
  if (details && Object.keys(details).length > 0) {
1571
- // Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
1572
- const cardFromVersions = get(
1573
- details,
1574
- 'versions.base.content.RCS.rcsContent.cardContent[0]',
1575
- );
1576
- const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
1577
- const cardType = (rcsContent?.cardType || '').toString();
1578
-
1579
- setEditFlow(true);
1580
- setTemplateName(details?.name || details?.creativeName || '');
1581
-
1582
- const cardFromTop = get(details, 'rcsContent.cardContent[0]');
1583
- const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
1584
- const cardVarMappedFromCardContent =
1585
- card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
1586
- ? card0.cardVarMapped
1587
- : {};
1588
- const cardVarMappedFromRootMirror =
1589
- details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
1590
- ? details.rcsCardVarMapped
1591
- : {};
1592
- // Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
1593
- // nested versions.cardContent[0].cardVarMapped is dropped on reload.
1594
- const mergedCardVarMappedFromPayload = {
1595
- ...cardVarMappedFromRootMirror,
1596
- ...cardVarMappedFromCardContent,
1597
- };
1598
- const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
1599
- const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
1600
- const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
1601
- Object.keys(mergedCardVarMappedFromPayload)
1602
- .sort()
1603
- .reduce((accumulator, mapKey) => {
1604
- accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
1605
- return accumulator;
1606
- }, {}),
1607
- )}`;
1608
- if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
1609
- lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
1610
- setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
1611
- }
1612
- const tokenListForMap = [
1613
- ...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
1614
- ...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
1615
- ];
1616
- const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
1617
- // Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
1618
- // getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
1619
- const cardVarMappedBeforeCoalesce = isFullMode
1620
- ? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
1621
- : { ...mergedCardVarMappedFromPayload };
1622
- const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
1623
- cardVarMappedBeforeCoalesce,
1624
- loadedTitleForMap,
1625
- loadedDescForMap,
1626
- rcsVarRegex,
1627
- );
1628
- const cardVarMappedAfterNumericSlotSync = !isFullMode
1629
- ? syncCardVarMappedSemanticsFromSlots(
1630
- cardVarMappedAfterCoalesce,
1631
- loadedTitleForMap,
1632
- loadedDescForMap,
1633
- rcsVarRegex,
1634
- )
1635
- : cardVarMappedAfterCoalesce;
1636
- const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
1637
- // Pre-populate variable/tag mappings while opening an existing template in edit flows
1638
- setCardVarMapped((previousVarMapState) => {
1639
- const previousVarMap = previousVarMapState ?? {};
1640
- if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
1641
- const previousVarMapKeys = Object.keys(previousVarMap);
1642
- const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
1643
- if (previousVarMapKeys.length === nextVarMapKeys.length) {
1644
- const allSlotValuesMatchPrevious = previousVarMapKeys.every(
1645
- (key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
1646
- );
1647
- if (allSlotValuesMatchPrevious) return previousVarMapState;
1648
- }
1649
- return hydratedCardVarMappedResult;
1650
- });
1651
-
1652
- if (cardType.toUpperCase() === contentType.carousel) {
1653
-
1654
- setTemplateType(contentType.carousel);
1655
- setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1656
- // Library/campaign payloads can store cardSettings/cardContent at the flat top-level
1657
- // `rcsContent` instead of nested under versions.base.content.RCS (same fallback already
1658
- // used for the rich_card media lookup below, line ~1793). Without it, cardWidth/height
1659
- // silently fall back to SMALL/MEDIUM, mismatching the dimensions the thumbnail was
1660
- // actually uploaded at and causing object-fit:cover to crop the preview image.
1661
- const cardSettings = rcsContent?.cardSettings
1662
- || get(details, 'rcsContent.cardSettings', {});
1663
- const cardWidth = cardSettings?.cardWidth || SMALL;
1664
- setSelectedCarouselWidth(cardWidth);
1665
-
1666
- const cards = Array.isArray(rcsContent?.cardContent)
1667
- ? rcsContent.cardContent
1668
- : get(details, 'rcsContent.cardContent', []);
1669
- const firstHeight = cards?.[0]?.media?.height || MEDIUM;
1670
- setSelectedCarouselHeight(firstHeight);
1671
- setSelectedCarousel(`${firstHeight}_${cardWidth}`);
1672
- setActiveCarouselIndex('0');
1673
-
1674
- const hydratedCards = cards.map((c = {}, idx) => {
1675
- const mediaType = c.mediaType;
1676
- const media = c.media || {};
1677
- const mediaUrl = media.mediaUrl || '';
1678
- const thumbUrl = media.thumbnailUrl || '';
1679
- const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
1680
- const suggestions = idx === 0 && rawSuggestions.length === 0
1681
- ? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
1682
- : rawSuggestions;
1683
- return {
1684
- title: c.title || '',
1685
- description: c.description || '',
1686
- mediaType,
1687
- imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
1688
- videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
1689
- videoSrc: mediaUrl,
1690
- previewUrl: thumbUrl,
1691
- videoThumbnail: thumbUrl,
1692
- videoName: c?.media?.videoName || '',
1693
- } : {},
1694
- thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
1695
- suggestions,
1696
- };
1697
- });
1698
- setCarouselData(
1699
- hydratedCards.length > 0
1700
- ? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
1701
- : [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
1702
- );
1703
- setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
1704
-
1705
- // Status bar uses first card's Status, keep existing behavior.
1706
- if (isHostInfoBip) {
1707
- setTemplateStatus('');
1708
- } else {
1709
- const firstCard = cards?.[0] || {};
1710
- const cardForCarouselStatus = {
1711
- ...firstCard,
1712
- Status:
1713
- firstCard.Status
1714
- ?? firstCard.status
1715
- ?? firstCard.approvalStatus
1716
- ?? get(details, 'templateStatus')
1717
- ?? get(details, 'approvalStatus')
1718
- ?? get(details, 'creativeStatus')
1719
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
1720
- ?? '',
1721
- };
1722
- templateStatusHelper(cardForCarouselStatus);
1723
- }
1724
-
1725
- // Hydrate SMS fallback for carousel — same logic as for non-carousel types below.
1726
- // Without this the early `return` skips fallback hydration entirely, so reopening
1727
- // a carousel template never restores the saved SMS fallback data.
1728
- const carouselSmsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
1729
- const carouselSmsBase = get(carouselSmsFallbackContent, 'versions.base', {});
1730
- const carouselUpdatedEditor = carouselSmsBase['updated-sms-editor'] ?? carouselSmsBase['sms-editor'];
1731
- const carouselSmsEditor = carouselSmsBase['sms-editor'];
1732
- const carouselFromNested = Array.isArray(carouselUpdatedEditor)
1733
- ? carouselUpdatedEditor.join('')
1734
- : (typeof carouselUpdatedEditor === 'string' ? carouselUpdatedEditor : (carouselSmsEditor || ''));
1735
- const carouselFallbackMessage = carouselSmsFallbackContent.smsContent
1736
- || carouselSmsFallbackContent.smsTemplateContent
1737
- || carouselSmsFallbackContent.message
1738
- || carouselFromNested
1739
- || '';
1740
- const carouselVarMappedFromPayload = carouselSmsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
1741
- const carouselHasVarMapped = Object.keys(carouselVarMappedFromPayload).length > 0;
1742
- const carouselHasFallbackPayload =
1743
- carouselSmsFallbackContent
1744
- && Object.keys(carouselSmsFallbackContent).length > 0
1745
- && (
1746
- !!carouselSmsFallbackContent.smsTemplateName
1747
- || !!carouselFallbackMessage
1748
- || carouselHasVarMapped
1749
- );
1750
- if (carouselHasFallbackPayload) {
1751
- const carouselUnicodeFromApi =
1752
- typeof carouselSmsFallbackContent.unicodeValidity === 'boolean'
1753
- ? carouselSmsFallbackContent.unicodeValidity
1754
- : (typeof carouselSmsBase['unicode-validity'] === 'boolean' ? carouselSmsBase['unicode-validity'] : true);
1755
- const carouselRegisteredSenderIds =
1756
- extractRegisteredSenderIdsFromSmsFallbackRecord(carouselSmsFallbackContent);
1757
- const carouselNextSmsState = {
1758
- templateName: carouselSmsFallbackContent.smsTemplateName || '',
1759
- content: carouselFallbackMessage,
1760
- templateContent: carouselFallbackMessage,
1761
- unicodeValidity: carouselUnicodeFromApi,
1762
- ...(carouselHasVarMapped && { rcsSmsFallbackVarMapped: carouselVarMappedFromPayload }),
1763
- ...(Array.isArray(carouselRegisteredSenderIds) && carouselRegisteredSenderIds.length > 0
1764
- ? { registeredSenderIds: carouselRegisteredSenderIds }
1765
- : {}),
1766
- };
1767
- const carouselHydrationKey = JSON.stringify({
1768
- creativeKey: details._id || details.name || details.creativeName || '',
1769
- templateName: carouselNextSmsState.templateName,
1770
- content: carouselNextSmsState.content,
1771
- unicodeValidity: carouselNextSmsState.unicodeValidity,
1772
- varMapped: carouselNextSmsState.rcsSmsFallbackVarMapped || {},
1773
- senderIds: Array.isArray(carouselRegisteredSenderIds)
1774
- ? carouselRegisteredSenderIds.join('')
1775
- : '',
1776
- });
1777
- if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== carouselHydrationKey) {
1778
- lastSmsFallbackHydrationKeyRef.current = carouselHydrationKey;
1779
- setSmsFallbackData(carouselNextSmsState);
1780
- }
1781
- } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
1782
- lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
1783
- setSmsFallbackData(null);
1784
- }
1785
-
1786
- return;
589
+ if (!isFullMode) {
590
+ const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
591
+ setCardVarMapped(tempCardVarMapped);
1787
592
  }
1788
-
1789
- const mediaType =
1790
- card0.mediaType
1791
- || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
1792
- if (cardType.toUpperCase() !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
593
+ const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
594
+ if (mediaType === RCS_MEDIA_TYPES.NONE) {
1793
595
  setTemplateType(contentType.text_message);
1794
- } else if (cardType.toUpperCase() !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
596
+ } else {
1795
597
  setTemplateType(contentType.rich_card);
1796
598
  }
1797
-
1798
- const loadedTitle = loadedTitleForMap;
1799
- const loadedDesc = loadedDescForMap;
1800
- const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
1801
- loadedTitle,
1802
- loadedDesc,
1803
- isFullMode,
1804
- cardVarMappedAfterHydration: hydratedCardVarMappedResult,
1805
- rcsVarRegex,
1806
- });
599
+ setEditFlow(true);
600
+ setTemplateName(details.name || '');
601
+ const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
602
+ const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
603
+ const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
604
+ const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
605
+ const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
1807
606
  setTemplateTitle(normalizedTitle);
1808
607
  setTemplateDesc(normalizedDesc);
1809
- setSuggestions(
1810
- Array.isArray(card0.suggestions)
1811
- ? card0.suggestions
1812
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
1813
- );
1814
- const cardForStatus = {
1815
- ...card0,
1816
- Status:
1817
- card0.Status
1818
- ?? card0.status
1819
- ?? card0.approvalStatus
1820
- ?? get(details, 'templateStatus')
1821
- ?? get(details, 'approvalStatus')
1822
- ?? get(details, 'creativeStatus')
1823
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
1824
- ?? '',
1825
- };
1826
- if (isHostInfoBip) {
1827
- setTemplateStatus('');
1828
- } else {
1829
- templateStatusHelper(cardForStatus);
1830
- }
1831
- const mediaData =
1832
- card0.media != null && card0.media !== ''
1833
- ? card0.media
1834
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
1835
- const cardSettings =
1836
- get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
1837
- || get(details, 'rcsContent.cardSettings', '');
608
+ setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
609
+ templateStatusHelper(details);
610
+ const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
611
+ const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
1838
612
  setMediaData(mediaData, mediaType, cardSettings);
1839
613
  if (details?.edit) {
1840
614
  const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
1841
615
  setRcsAccount(rcsAccountId);
1842
616
  }
1843
-
1844
- const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
1845
- const base = get(smsFallbackContent, 'versions.base', {});
1846
- const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
1847
- const smsEditor = base['sms-editor'];
1848
- const fromNested = Array.isArray(updatedEditor)
1849
- ? updatedEditor.join('')
1850
- : (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
1851
- const fallbackMessage = smsFallbackContent.smsContent
1852
- || smsFallbackContent.smsTemplateContent
1853
- || smsFallbackContent.message
1854
- || fromNested
1855
- || '';
1856
- const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
1857
- const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
1858
- const hasFallbackPayload =
1859
- smsFallbackContent
1860
- && Object.keys(smsFallbackContent).length > 0
1861
- && (
1862
- !!smsFallbackContent.smsTemplateName
1863
- || !!fallbackMessage
1864
- || hasVarMapped
1865
- );
1866
- if (hasFallbackPayload) {
1867
- if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
1868
- console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
1869
- }
1870
- const unicodeFromApi =
1871
- typeof smsFallbackContent.unicodeValidity === 'boolean'
1872
- ? smsFallbackContent.unicodeValidity
1873
- : (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
1874
- const registeredSenderIdsFromApi =
1875
- extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
1876
- const nextSmsState = {
1877
- templateName: smsFallbackContent.smsTemplateName || '',
1878
- content: fallbackMessage,
1879
- templateContent: fallbackMessage,
1880
- unicodeValidity: unicodeFromApi,
1881
- ...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
1882
- ...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
1883
- ? { registeredSenderIds: registeredSenderIdsFromApi }
1884
- : {}),
1885
- };
1886
- const hydrationKey = JSON.stringify({
1887
- creativeKey: details._id || details.name || details.creativeName || '',
1888
- templateName: nextSmsState.templateName,
1889
- content: nextSmsState.content,
1890
- unicodeValidity: nextSmsState.unicodeValidity,
1891
- varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
1892
- senderIds:
1893
- Array.isArray(registeredSenderIdsFromApi)
1894
- ? registeredSenderIdsFromApi.join('\u001f')
1895
- : '',
1896
- });
1897
- if (
1898
- isFullMode
1899
- || lastSmsFallbackHydrationKeyRef.current !== hydrationKey
1900
- ) {
1901
- lastSmsFallbackHydrationKeyRef.current = hydrationKey;
1902
- setSmsFallbackData(nextSmsState);
1903
- }
1904
- } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
1905
- lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
1906
- setSmsFallbackData(null);
1907
- }
1908
617
  }
1909
- }, [rcsHydrationDetails, isFullMode, isHostInfoBip]);
618
+ }, [rcsData, templateData, isFullMode, isEditFlow]);
619
+
1910
620
 
1911
621
  useEffect(() => {
1912
622
  if (templateType === contentType.text_message) {
1913
623
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1914
- // Full-mode create only: switching to plain text clears draft title/media. Never clear when
1915
- // hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
624
+ setTemplateTitle('');
625
+ setTemplateTitleError('');
1916
626
  if (!isEditFlow && isFullMode) {
1917
- setTemplateTitle('');
1918
- setTemplateTitleError('');
1919
627
  setUpdateRcsImageSrc('');
628
+ setUpdateRcsVideoSrc({});
1920
629
  setRcsVideoSrc({});
1921
630
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
1922
631
  }
@@ -1943,8 +652,7 @@ export const Rcs = (props) => {
1943
652
  if (!showDltContainer) {
1944
653
  const { type, module } = location.query || {};
1945
654
  const isEmbedded = type === EMBEDDED;
1946
- // Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
1947
- const context = isEmbedded ? module : 'outbound';
655
+ const context = isEmbedded ? module : DEFAULT;
1948
656
  const embedded = isEmbedded ? type : FULL;
1949
657
  const query = {
1950
658
  layout: SMS,
@@ -1955,9 +663,9 @@ export const Rcs = (props) => {
1955
663
  if (getDefaultTags) {
1956
664
  query.context = getDefaultTags;
1957
665
  }
1958
- fetchTagSchemaIfNewQuery(query);
666
+ globalActions.fetchSchemaForEntity(query);
1959
667
  }
1960
- }, [showDltContainer, fetchTagSchemaIfNewQuery]);
668
+ }, [showDltContainer]);
1961
669
 
1962
670
  useEffect(() => {
1963
671
  let tag = get(metaEntities, `tags.standard`, []);
@@ -1980,128 +688,16 @@ export const Rcs = (props) => {
1980
688
  context,
1981
689
  embedded,
1982
690
  };
1983
- if (getDefaultTags) {
1984
- query.context = getDefaultTags;
1985
- }
1986
- fetchTagSchemaIfNewQuery(query);
1987
691
  globalActions.fetchSchemaForEntity(query);
1988
692
  };
1989
693
 
1990
- const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
1991
- if (!templateStr || !numericVarName || !tagName) return templateStr;
1992
- const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
1993
- return templateStr.replace(re, `{{${tagName}}}`);
1994
- };
1995
-
1996
- const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
1997
- if (!varSegmentCompositeDomId) return;
1998
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
1999
- if (underscoreIndexInCompositeId === -1) return;
2000
- const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
2001
- if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
2002
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
2003
- const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
2004
- if (!semanticOrNumericVarName) return;
2005
- const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
2006
- const templateStringForField =
2007
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
2008
- const titleOrMessageFieldType =
2009
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
2010
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
2011
- varSegmentCompositeDomId,
2012
- templateStringForField,
2013
- titleOrMessageFieldType,
2014
- );
2015
- const cardVarMappedNumericSlotKey =
2016
- globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
2017
- ? String(globalVarSlotIndexZeroBased + 1)
2018
- : null;
2019
-
2020
- setCardVarMapped((previousCardVarMapped) => {
2021
- const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
2022
- if (isNumericPlaceholderSlot) {
2023
- const existingValueBeforeAppend = (
2024
- previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
2025
- ).toString();
2026
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
2027
- delete updatedCardVarMapped[semanticOrNumericVarName];
2028
- updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
2029
- } else {
2030
- // Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
2031
- // "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
2032
- // read/write the global numeric slot only and drop the shared semantic key.
2033
- const existingValueBeforeAppend = cardVarMappedNumericSlotKey
2034
- ? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
2035
- : String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
2036
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
2037
- delete updatedCardVarMapped[semanticOrNumericVarName];
2038
- if (cardVarMappedNumericSlotKey) {
2039
- updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
2040
- // Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
2041
- const fieldOffset = titleOrMessageFieldType === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
2042
- const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
2043
- let varOrdinal = 0;
2044
- for (let i = 0; i < fieldSegments.length; i++) {
2045
- if (rcsVarTestRegex.test(fieldSegments[i])) {
2046
- const slotVarName = getVarNameFromToken(fieldSegments[i]);
2047
- const globalSlot = fieldOffset + varOrdinal;
2048
- varOrdinal += 1;
2049
- if (slotVarName === semanticOrNumericVarName && globalSlot !== globalVarSlotIndexZeroBased) {
2050
- updatedCardVarMapped[String(globalSlot + 1)] = mappedValueAfterAppendingTag;
2051
- }
2052
- }
2053
- }
2054
- } else {
2055
- updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
2056
- }
2057
- }
2058
- return updatedCardVarMapped;
2059
- });
2060
-
2061
- if (
2062
- isNumericPlaceholderSlot
2063
- && (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
2064
- ) {
2065
- if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
2066
- setTemplateTitle((previousTitle) => {
2067
- const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
2068
- previousTitle || '',
2069
- semanticOrNumericVarName,
2070
- selectedTagNameFromPicker,
2071
- );
2072
- if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
2073
- setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
2074
- // Remount segment editor: tag insert replaces {{n}} with e.g. {{tag.FORMAT_1}} — slot ids change; avoids stale UI vs manual typing in full-mode TextArea
2075
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
2076
- return titleAfterReplacingNumericPlaceholder;
2077
- });
2078
- } else {
2079
- setTemplateDesc((previousDescription) => {
2080
- const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
2081
- previousDescription || '',
2082
- semanticOrNumericVarName,
2083
- selectedTagNameFromPicker,
2084
- );
2085
- if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
2086
- return previousDescription;
2087
- }
2088
- setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
2089
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
2090
- return descriptionAfterReplacingNumericPlaceholder;
2091
- });
2092
- }
2093
- }
2094
- };
2095
-
2096
- const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
2097
-
2098
- const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
2099
-
2100
- const onCarouselTagSelect = (data) => {
2101
- if (!carouselFocusedVarId) return;
2102
- const sep = carouselFocusedVarId.lastIndexOf('_');
694
+ const onTagSelect = (data, areaId) => {
695
+ if (!areaId) return;
696
+ const sep = areaId.lastIndexOf('_');
2103
697
  if (sep === -1) return;
2104
- const token = carouselFocusedVarId.slice(0, sep);
698
+ const numId = Number(areaId.slice(sep + 1));
699
+ if (isNaN(numId)) return;
700
+ const token = areaId.slice(0, sep);
2105
701
  const variableName = getVarNameFromToken(token);
2106
702
  if (!variableName) return;
2107
703
  setCardVarMapped((prev) => {
@@ -2114,6 +710,10 @@ export const Rcs = (props) => {
2114
710
  });
2115
711
  };
2116
712
 
713
+ const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
714
+
715
+ const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
716
+
2117
717
  const onTagSelectFallback = (data) => {
2118
718
  const tempMsg = `${fallbackMessage}{{${data}}}`;
2119
719
  const error = fallbackMessageErrorHandler(tempMsg);
@@ -2133,14 +733,15 @@ export const Rcs = (props) => {
2133
733
  };
2134
734
  // tag Code end
2135
735
 
2136
- const renderLabel = (value, desc) => {
736
+ const renderLabel = (value, showLabel, desc) => {
737
+ const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
2137
738
  return (
2138
739
  <>
2139
- <div className="rcs-form-section-heading">
740
+ <RcsLabel>
2140
741
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
2141
- </div>
742
+ </RcsLabel>
2142
743
  {desc && (
2143
- <CapLabel type="label3" className="rcs-form-field-caption">
744
+ <CapLabel type="label3" style={{ marginBottom: '17px' }}>
2144
745
  {formatMessage(messages[desc])}
2145
746
  </CapLabel>
2146
747
  )}
@@ -2159,8 +760,13 @@ export const Rcs = (props) => {
2159
760
  },
2160
761
  {
2161
762
  value: contentType.carousel,
2162
- label: formatMessage(messages.carousel),
2163
- }
763
+ label: (
764
+ <CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
765
+ {formatMessage(messages.carousel)}
766
+ </CapTooltip>
767
+ ),
768
+ disabled: true,
769
+ },
2164
770
  ];
2165
771
 
2166
772
  const onTemplateNameChange = ({ target: { value } }) => {
@@ -2171,10 +777,6 @@ export const Rcs = (props) => {
2171
777
 
2172
778
  const onTemplateTypeChange = ({ target: { value } }) => {
2173
779
  setTemplateType(value);
2174
- // Carousel has per-card media; keep template-level media type neutral.
2175
- if (value === contentType.carousel) {
2176
- setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
2177
- }
2178
780
  };
2179
781
 
2180
782
 
@@ -2195,39 +797,8 @@ export const Rcs = (props) => {
2195
797
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
2196
798
  setTemplateMediaType(value);
2197
799
  };
2198
- const renderedRCSEditMessage = (descArray, type) => {
2199
- const renderArray = [];
2200
- if (descArray?.length) {
2201
- descArray.forEach((elem, index) => {
2202
- if (rcsVarTestRegex.test(elem)) {
2203
- // Variable input
2204
- renderArray.push(
2205
- <TextArea
2206
- id={`${elem}_${index}`}
2207
- key={`${elem}_${index}`}
2208
- placeholder={`enter the value for ${elem}`}
2209
- autosize={{ minRows: 1, maxRows: 3 }}
2210
- onChange={e => textAreaValueChange(e, type)}
2211
- value={textAreaValue(index, type)}
2212
- onFocus={(e) => setTextAreaId(e, type)}
2213
- />
2214
- );
2215
- } else if (elem) {
2216
- // Static text
2217
- renderArray.push(
2218
- <TextArea
2219
- key={`static_${index}`}
2220
- value={elem}
2221
- autosize={{ minRows: 1, maxRows: 3 }}
2222
- disabled
2223
- className="rcs-edit-template-message-static-textarea"
2224
- />
2225
- );
2226
- }
2227
- });
2228
- }
2229
- return renderArray;
2230
- };
800
+
801
+
2231
802
  const onTemplateTitleChange = ({ target: { value } }) => {
2232
803
  let errorMessage = false;
2233
804
  if (templateType === contentType.rich_card && !value.trim()) {
@@ -2243,7 +814,7 @@ export const Rcs = (props) => {
2243
814
 
2244
815
  const onTemplateDescChange = ({ target: { value } }) => {
2245
816
  let errorMessage = false;
2246
- if(templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)){
817
+ if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
2247
818
  errorMessage = formatMessage(messages.templateMessageLengthError);
2248
819
  } else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
2249
820
  errorMessage = formatMessage(messages.templateMessageLengthError);
@@ -2259,16 +830,16 @@ export const Rcs = (props) => {
2259
830
 
2260
831
  const templateDescErrorHandler = (value) => {
2261
832
  let errorMessage = false;
2262
- const { unsupportedTags, isBraceError } = validateTags({
833
+ const { isBraceError } = validateTags({
2263
834
  content: value,
2264
835
  tagsParam: tags,
2265
- injectedTagsParams: injectedTags,
2266
836
  location,
2267
837
  tagModule: getDefaultTags,
838
+ isFullMode,
2268
839
  }) || {};
2269
840
 
2270
841
  const maxLength = templateType === contentType.text_message
2271
- ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)
842
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH
2272
843
  : RCS_RICH_CARD_MAX_LENGTH;
2273
844
 
2274
845
  if (value === '' && isMediaTypeText) {
@@ -2291,25 +862,10 @@ export const Rcs = (props) => {
2291
862
  };
2292
863
 
2293
864
  const fallbackMessageErrorHandler = (value) => {
2294
- let errorMessage = false;
2295
- const { unsupportedTags } = validateTags({
2296
- content: value,
2297
- tagsParam: tags,
2298
- injectedTagsParams: injectedTags,
2299
- location,
2300
- tagModule: getDefaultTags,
2301
- }) || {};
2302
865
  if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
2303
- errorMessage = formatMessage(messages.fallbackMsgLenError);
2304
- } else if (unsupportedTags?.length > 0) {
2305
- errorMessage = formatMessage(
2306
- globalMessages.unsupportedTagsValidationError,
2307
- {
2308
- unsupportedTags,
2309
- },
2310
- );
866
+ return formatMessage(messages.fallbackMsgLenError);
2311
867
  }
2312
- return errorMessage;
868
+ return false;
2313
869
  };
2314
870
 
2315
871
  // Check for forbidden characters: square brackets [] and single curly braces {}
@@ -2356,43 +912,53 @@ export const Rcs = (props) => {
2356
912
  if(!isFullMode){
2357
913
  return false;
2358
914
  }
2359
- // Allow Liquid-style param names: letters, digits, underscore, dots (e.g. dynamic_expiry_date_after_3_days.FORMAT_1)
2360
- if (!/^[\w.]+$/.test(paramName)) {
915
+ if (!/^\w+$/.test(paramName)) {
2361
916
  return formatMessage(messages.unknownCharactersError);
2362
917
  }
2363
918
  }
2364
919
  return false;
2365
920
  };
921
+
922
+ const templateHeaderErrorHandler = (value) => {
923
+ let errorMessage = false;
924
+ if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
925
+ errorMessage = formatMessage(messages.templateHeaderLengthError);
926
+ } else {
927
+ errorMessage = variableErrorHandling(value);
928
+ }
929
+ return errorMessage;
930
+ };
931
+
932
+
933
+ const templateMessageErrorHandler = (value) => {
934
+ let errorMessage = false;
935
+ if (value === '') {
936
+ errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
937
+ } else if (
938
+ value?.length
939
+ > TEMPLATE_MESSAGE_MAX_LENGTH
940
+ ) {
941
+ errorMessage = formatMessage(messages.templateMessageLengthError);
942
+ } else {
943
+ errorMessage = variableErrorHandling(value);
944
+ }
945
+ return errorMessage;
946
+ };
947
+
2366
948
 
2367
949
  const onMessageAddVar = () => {
2368
- onAddVar(templateDesc);
950
+ onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
2369
951
  };
2370
952
 
2371
- /**
2372
- * Returns the smallest positive integer not already used as a `{{N}}` variable
2373
- * in either the title or description fields, or null if the limit (19) is reached.
2374
- * Scans both fields so title and description vars never share the same number
2375
- * (duplicate numbers would share a cardVarMapped key and bleed values across fields).
2376
- */
2377
- const getNextRcsNumericVarNumber = (titleStr, descStr) => {
2378
- const allExistingVars = [
2379
- ...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
2380
- ...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
2381
- ];
2382
- const existingNumbers = allExistingVars.flatMap(v => {
2383
- const m = v.match(/\d+/);
2384
- return m ? [parseInt(m[0], 10)] : [];
2385
- });
953
+ const onAddVar = (type, messageContent, regex) => {
954
+ // Always append the next variable at the end, like WhatsApp
955
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
956
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
2386
957
  let nextNumber = 1;
2387
958
  while (existingNumbers.includes(nextNumber)) {
2388
959
  nextNumber++;
2389
960
  }
2390
- return nextNumber > 19 ? null : nextNumber;
2391
- };
2392
-
2393
- const onAddVar = (messageContent) => {
2394
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
2395
- if (nextNumber === null) {
961
+ if (nextNumber > 19) {
2396
962
  return;
2397
963
  }
2398
964
  const nextVar = `{{${nextNumber}}}`;
@@ -2404,13 +970,15 @@ const onAddVar = (messageContent) => {
2404
970
  };
2405
971
 
2406
972
  const onTitleAddVar = () => {
2407
- // Scan both title AND description so the new title var number doesn't
2408
- // duplicate a number already used in the description. Duplicate numeric
2409
- // names would share the same cardVarMapped semantic key, causing the
2410
- // description slot to reflect the title slot value and vice-versa.
973
+ // Always append the next variable at the end, like WhatsApp
2411
974
  const messageContent = templateTitle;
2412
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
2413
- if (nextNumber === null) {
975
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
976
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
977
+ let nextNumber = 1;
978
+ while (existingNumbers.includes(nextNumber)) {
979
+ nextNumber++;
980
+ }
981
+ if (nextNumber > 19) {
2414
982
  return;
2415
983
  }
2416
984
  const nextVar = `{{${nextNumber}}}`;
@@ -2422,35 +990,26 @@ const onTitleAddVar = () => {
2422
990
  setTemplateTitleError(error);
2423
991
  };
2424
992
 
2425
- // Carousel: global variables across the whole carousel (all cards, title+body)
2426
- const getNextCarouselVarToken = () => {
2427
- const nums = [];
2428
- (carouselData || []).forEach((c = {}) => {
2429
- const titleMatches = (c.title || '').match(RCS_NUMERIC_VAR_TOKEN_REGEX) || [];
2430
- const descMatches = (c.description || '').match(RCS_NUMERIC_VAR_TOKEN_REGEX) || [];
2431
- [...titleMatches, ...descMatches].forEach((tok) => {
2432
- const n = parseInt((tok.match(/\d+/) || [])[0], 10);
2433
- if (!Number.isNaN(n)) nums.push(n);
2434
- });
2435
- });
2436
- const existing = new Set(nums);
2437
- let nextNumber = 1;
2438
- while (existing.has(nextNumber)) nextNumber++;
2439
- if (nextNumber > 19) return '';
2440
- return `{{${nextNumber}}}`;
2441
- };
2442
-
2443
- const appendVarToCarouselField = (cardIndex, fieldName) => {
2444
- const token = getNextCarouselVarToken();
2445
- if (!token) return;
2446
- setCarouselData((prev = []) => {
2447
- const updated = cloneDeep(prev);
2448
- if (!updated[cardIndex]) return prev;
2449
- const current = (updated[cardIndex][fieldName] || '').toString();
2450
- updated[cardIndex][fieldName] = `${current}${token}`;
2451
- return updated;
2452
- });
2453
- };
993
+
994
+ const splitTemplateVarString = (str) => {
995
+ if (!str) return [];
996
+ const validVarArr = str.match(rcsVarRegex) || [];
997
+ const templateVarArray = [];
998
+ let content = str;
999
+ while (content?.length !== 0) {
1000
+ const index = content.indexOf(validVarArr?.[0]);
1001
+ if (index !== -1) {
1002
+ templateVarArray.push(content.substring(0, index));
1003
+ templateVarArray.push(validVarArr?.[0]);
1004
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length);
1005
+ validVarArr?.shift();
1006
+ } else {
1007
+ templateVarArray.push(content);
1008
+ break;
1009
+ }
1010
+ }
1011
+ return templateVarArray.filter(Boolean);
1012
+ };
2454
1013
 
2455
1014
  const textAreaValue = (idValue, type) => {
2456
1015
  if (idValue >= 0) {
@@ -2466,46 +1025,6 @@ const onTitleAddVar = () => {
2466
1025
  return "";
2467
1026
  };
2468
1027
 
2469
- // Carousel: render variable-value editor for a given template string (title/description).
2470
- // This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
2471
- const renderCarouselEditMessage = (templateStr) => {
2472
- const renderArray = [];
2473
- const templateArr = splitTemplateVarString(templateStr);
2474
- if (templateArr?.length) {
2475
- templateArr.forEach((elem, index) => {
2476
- if (rcsVarTestRegex.test(elem)) {
2477
- const varName = getVarNameFromToken(elem);
2478
- renderArray.push(
2479
- <div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
2480
- <TextArea
2481
- id={`${elem}_${index}`}
2482
- placeholder={`enter the value for ${elem}`}
2483
- autosize={{ minRows: 1, maxRows: 3 }}
2484
- onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
2485
- value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
2486
- onFocus={(e) => {
2487
- const id = e?.target?.id || e?.currentTarget?.id || '';
2488
- setCarouselFocusedVarId(id);
2489
- }}
2490
- />
2491
- </div>
2492
- );
2493
- } else if (elem) {
2494
- renderArray.push(
2495
- <CapHeading
2496
- key={`static_${index}`}
2497
- type="h4"
2498
- className="rcs-edit-template-message-split"
2499
- >
2500
- {elem}
2501
- </CapHeading>
2502
- );
2503
- }
2504
- });
2505
- }
2506
- return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
2507
- };
2508
-
2509
1028
  const textAreaValueChange = (e, type) => {
2510
1029
  const value = e?.target?.value ?? '';
2511
1030
  const id = e?.target?.id || e?.currentTarget?.id || '';
@@ -2525,9 +1044,7 @@ const onTitleAddVar = () => {
2525
1044
  };
2526
1045
 
2527
1046
  const setTextAreaId = (e, type) => {
2528
- // VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
2529
- // have an `.target.id` shape. Support both.
2530
- const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
1047
+ const id = e?.target?.id || e?.currentTarget?.id || '';
2531
1048
  if (!id) return;
2532
1049
  if (type === TITLE_TEXT) setTitleTextAreaId(id);
2533
1050
  else setDescTextAreaId(id);
@@ -2546,12 +1063,10 @@ const onTitleAddVar = () => {
2546
1063
  </CapRow>
2547
1064
  }
2548
1065
  description={
2549
- !isEditFlow && (
2550
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
2551
- )
1066
+ <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
2552
1067
  }
2553
1068
  />
2554
-
1069
+
2555
1070
  <CapActionButton
2556
1071
  buttonType={buttonType}
2557
1072
  updateButtonChange={updateButtonChange}
@@ -2560,85 +1075,44 @@ const onTitleAddVar = () => {
2560
1075
  isEditFlow={isEditFlow}
2561
1076
  isFullMode={isFullMode}
2562
1077
  maxButtons={MAX_BUTTONS}
2563
- host={hostName}
2564
- />
1078
+ />
2565
1079
  </>
2566
1080
  );
2567
1081
  };
2568
1082
 
2569
- const getRcsValueMap = (fieldTemplateString, fieldType) => {
2570
- if (!fieldTemplateString) return {};
2571
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
2572
- const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
2573
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
2574
- const segmentIdToResolvedValue = {};
2575
- let varOrdinal = 0;
2576
- templateSegments.forEach((segmentToken, segmentIndexInField) => {
2577
- if (rcsVarTestRegex.test(segmentToken)) {
2578
- const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
2579
- const varName = getVarNameFromToken(segmentToken);
2580
- const globalSlot = slotOffset + varOrdinal;
2581
- varOrdinal += 1;
2582
- segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
2583
- cardVarMapped,
2584
- varName,
2585
- globalSlot,
2586
- isEditLike,
2587
- rcsSpanningSemanticVarNames.has(varName),
2588
- );
2589
- }
2590
- });
2591
- return segmentIdToResolvedValue;
2592
- };
2593
-
2594
- const titleVarSegmentValueMapById = useMemo(
2595
- () => getRcsValueMap(templateTitle, TITLE_TEXT),
2596
- [templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
2597
- );
2598
- const descriptionVarSegmentValueMapById = useMemo(
2599
- () => getRcsValueMap(templateDesc, MESSAGE_TEXT),
2600
- [templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
2601
- );
2602
-
2603
- const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
2604
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
2605
- if (underscoreIndexInCompositeId === -1) return;
2606
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
2607
- const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
2608
- if (variableName === undefined || variableName === null || variableName === '') return;
2609
- const isInvalidValue = value?.trim() === '';
2610
- const coercedSlotValue = isInvalidValue ? '' : value;
2611
- const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
2612
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
2613
- varSegmentCompositeDomId,
2614
- templateStringForField,
2615
- type,
2616
- );
2617
- const fieldOffset = type === TITLE_TEXT ? 0 : (templateTitle?.match(rcsVarRegex) ?? []).length;
2618
- setCardVarMapped((previousCardVarMapped) => {
2619
- const updatedCardVarMapped = { ...previousCardVarMapped };
2620
- // Remove stale semantic key: keeping it causes every other slot sharing the same
2621
- // variable name (e.g. {{adv}} in both title and description) to read the same value
2622
- // via the semantic-key fallback in resolveCardVarMappedSlotValue.
2623
- delete updatedCardVarMapped[variableName];
2624
- if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
2625
- updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
2626
- // Sync all other intra-field occurrences of the same variable (cross-field slots stay independent)
2627
- const fieldSegments = splitTemplateVarStringRcs(templateStringForField ?? '');
2628
- let varOrdinal = 0;
2629
- for (let i = 0; i < fieldSegments.length; i++) {
2630
- if (rcsVarTestRegex.test(fieldSegments[i])) {
2631
- const slotVarName = getVarNameFromToken(fieldSegments[i]);
2632
- const globalSlot = fieldOffset + varOrdinal;
2633
- varOrdinal += 1;
2634
- if (slotVarName === variableName && globalSlot !== globalVarSlotIndexZeroBased) {
2635
- updatedCardVarMapped[String(globalSlot + 1)] = coercedSlotValue;
2636
- }
2637
- }
1083
+ const renderedRCSEditMessage = (descArray, type) => {
1084
+ const renderArray = [];
1085
+ if (descArray?.length) {
1086
+ descArray.forEach((elem, index) => {
1087
+ if (rcsVarTestRegex.test(elem)) {
1088
+ // Variable input
1089
+ renderArray.push(
1090
+ <TextArea
1091
+ id={`${elem}_${index}`}
1092
+ key={`${elem}_${index}`}
1093
+ placeholder={`enter the value for ${elem}`}
1094
+ autosize={{ minRows: 1, maxRows: 3 }}
1095
+ onChange={e => textAreaValueChange(e, type)}
1096
+ value={textAreaValue(index, type)}
1097
+ onFocus={(e) => setTextAreaId(e, type)}
1098
+ />
1099
+ );
1100
+ } else if (elem) {
1101
+ // Static text
1102
+ renderArray.push(
1103
+ <TextArea
1104
+ key={`static_${index}`}
1105
+ value={elem}
1106
+ autosize={{ minRows: 1, maxRows: 3 }}
1107
+ disabled
1108
+ className="rcs-edit-template-message-static-textarea"
1109
+ style={{ background: '#fafafa', color: '#888' }}
1110
+ />
1111
+ );
2638
1112
  }
2639
- }
2640
- return updatedCardVarMapped;
2641
- });
1113
+ });
1114
+ }
1115
+ return renderArray;
2642
1116
  };
2643
1117
 
2644
1118
  const renderTextComponent = () => {
@@ -2658,7 +1132,6 @@ const onTitleAddVar = () => {
2658
1132
  }
2659
1133
  suffix={
2660
1134
  <>
2661
-
2662
1135
  {(isEditFlow || !isFullMode) ? (
2663
1136
  <TagList
2664
1137
  label={formatMessage(globalMessages.addLabels)}
@@ -2675,28 +1148,18 @@ const onTitleAddVar = () => {
2675
1148
  type="flat"
2676
1149
  isAddBtn
2677
1150
  onClick={onTitleAddVar}
2678
- disabled={templateTitleError}
1151
+ disabled={!!templateTitleError}
2679
1152
  >
2680
1153
  {formatMessage(messages.addVar)}
2681
1154
  </CapButton>
2682
- )}
1155
+ )}
2683
1156
  </>
2684
- }
2685
- />
2686
-
2687
- {(isEditFlow || !isFullMode) ? (
2688
- <VarSegmentMessageEditor
2689
- key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
2690
- templateString={templateTitle}
2691
- valueMap={titleVarSegmentValueMapById}
2692
- onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
2693
- onFocus={(id) => setTitleTextAreaId(id)}
2694
- varRegex={rcsVarRegex}
2695
- placeholderPrefix=""
2696
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1157
+ }
2697
1158
  />
1159
+ <div className="rcs_text_area_wrapper">
1160
+ {(isEditFlow || !isFullMode) ? (
1161
+ renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
2698
1162
  ) : (
2699
- <div className="rcs_text_area_wrapper">
2700
1163
  <CapInput
2701
1164
  className={`rcs-template-title-input ${
2702
1165
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -2709,8 +1172,8 @@ const onTitleAddVar = () => {
2709
1172
  errorMessage={templateTitleError}
2710
1173
  disabled={isEditFlow || !isFullMode}
2711
1174
  />
2712
- </div>
2713
1175
  )}
1176
+ </div>
2714
1177
  {(isEditFlow || !isFullMode) && templateTitleError && (
2715
1178
  <CapError className="rcs-template-title-error">
2716
1179
  {templateTitleError}
@@ -2718,7 +1181,7 @@ const onTitleAddVar = () => {
2718
1181
  )}
2719
1182
  {!isEditFlow && isFullMode && renderTitleCharacterCount()}
2720
1183
  </>
2721
- )}
1184
+ )}
2722
1185
 
2723
1186
  {/* Template Message */}
2724
1187
  <CapRow useLegacy id="rcs-template-message-label">
@@ -2756,21 +1219,9 @@ const onTitleAddVar = () => {
2756
1219
  />
2757
1220
  </CapRow>
2758
1221
  <CapRow useLegacy className="rcs-create-template-message-input">
2759
- {/* Edit/library: segmented inputs (split on {{…}}). Full-mode create: single TextArea below — manual entry there never hits segment split. TagList replaces {{n}} in template string here. */}
2760
- <CapRow className="rcs_text_area_wrapper">
2761
- {(isEditFlow || !isFullMode)?
2762
- (
2763
- <VarSegmentMessageEditor
2764
- key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
2765
- templateString={templateDesc}
2766
- valueMap={descriptionVarSegmentValueMapById}
2767
- onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
2768
- onFocus={(id) => setDescTextAreaId(id)}
2769
- varRegex={rcsVarRegex}
2770
- placeholderPrefix=""
2771
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
2772
- />
2773
- )
1222
+ <div className="rcs_text_area_wrapper">
1223
+ {(isEditFlow || !isFullMode)
1224
+ ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
2774
1225
  : (
2775
1226
  <>
2776
1227
  <TextArea
@@ -2808,15 +1259,13 @@ const onTitleAddVar = () => {
2808
1259
  </>
2809
1260
  )
2810
1261
  }
2811
- </CapRow>
1262
+ </div>
2812
1263
  {(isEditFlow || !isFullMode) && templateDescError && (
2813
1264
  <CapError className="rcs-template-message-error">
2814
1265
  {templateDescError}
2815
1266
  </CapError>
2816
1267
  )}
2817
- {(isEditFlow || !isFullMode)
2818
- ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
2819
- : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1268
+ {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
2820
1269
  {!isFullMode && hasTag() && (
2821
1270
  <CapAlert
2822
1271
  message={
@@ -2830,13 +1279,24 @@ const onTitleAddVar = () => {
2830
1279
  />
2831
1280
  )}
2832
1281
  </CapRow>
2833
- {((!isEditFlow && isFullMode) || (isEditFlow && (suggestions?.length ?? 0) > 0)) &&
2834
- renderButtonComponent()}
1282
+ {renderButtonComponent()}
2835
1283
  </>
2836
1284
 
2837
1285
  );
2838
1286
  };
2839
1287
 
1288
+
1289
+ const fallbackSmsLength = () => (
1290
+ <CapLabel type="label1" className="fallback-sms-length">
1291
+ {formatMessage(messages.totalCharacters, {
1292
+ smsCount: Math.ceil(
1293
+ fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1294
+ ),
1295
+ number: fallbackMessage?.length,
1296
+ })}
1297
+ </CapLabel>
1298
+ );
1299
+
2840
1300
  // Get character count for title (rich card only)
2841
1301
  const getTitleCharacterCount = () => {
2842
1302
  if (templateType === contentType.text_message) return 0;
@@ -2851,7 +1311,7 @@ const onTitleAddVar = () => {
2851
1311
  // Get max length for description based on template type
2852
1312
  const getDescriptionMaxLength = () => {
2853
1313
  return templateType === contentType.text_message
2854
- ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH) // 160 for text message
1314
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
2855
1315
  : RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
2856
1316
  };
2857
1317
 
@@ -2877,58 +1337,6 @@ const onTitleAddVar = () => {
2877
1337
  );
2878
1338
  };
2879
1339
 
2880
- const rcsDltCardDeleteHandler = () => {
2881
- closeDltContainerHandler();
2882
- setDltEditData({});
2883
- setFallbackMessage('');
2884
- setFallbackMessageError(false);
2885
- };
2886
-
2887
- const dltFallbackListingPreviewhandler = (data) => {
2888
- const {
2889
- 'updated-sms-editor': updatedSmsEditor = [],
2890
- 'sms-editor': smsEditor = '',
2891
- } = data.versions.base || {};
2892
- };
2893
-
2894
- const getDltContentCardList = (content, channel) => {
2895
- const extra = [
2896
- <CapIcon
2897
- type="edit"
2898
- className="rcs-dlt-edit-icon"
2899
- onClick={() => rcsDltEditSelectHandler(dltEditData)}
2900
- />,
2901
- <CapDropdown
2902
- overlay={(
2903
- <CapMenu>
2904
- <>
2905
- <CapMenu.Item
2906
- className="ant-dropdown-menu-item"
2907
- onClick={() => {}}
2908
- >
2909
- {formatMessage(globalMessages.preview)}
2910
- </CapMenu.Item>
2911
- <CapMenu.Item
2912
- className="ant-dropdown-menu-item"
2913
- onClick={rcsDltCardDeleteHandler}
2914
- >
2915
- {formatMessage(globalMessages.delete)}
2916
- </CapMenu.Item>
2917
- </>
2918
- </CapMenu>
2919
- )}
2920
- >
2921
- <CapIcon type="more" />
2922
- </CapDropdown>,
2923
- ];
2924
- return {
2925
- title: channel,
2926
- content,
2927
- cardType: channel,
2928
- extra,
2929
- };
2930
- };
2931
-
2932
1340
  // Render character count for description/message
2933
1341
  const renderDescriptionCharacterCount = (className = "rcs-character-count") => {
2934
1342
  const currentLength = getDescriptionCharacterCount();
@@ -2944,26 +1352,6 @@ const onTitleAddVar = () => {
2944
1352
  );
2945
1353
  };
2946
1354
 
2947
- // Carousel: per-card character counts (same limits as rich card)
2948
- const getCarouselTitleCharacterCount = (cardIndex) => {
2949
- const titleText = carouselData?.[cardIndex]?.title || '';
2950
- return titleText ? titleText.length : 0;
2951
- };
2952
-
2953
- const getCarouselDescriptionCharacterCount = (cardIndex) => {
2954
- const descriptionText = carouselData?.[cardIndex]?.description || '';
2955
- return descriptionText ? descriptionText.length : 0;
2956
- };
2957
-
2958
- const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
2959
- <CapLabel type="label1" className={className}>
2960
- {formatMessage(messages.templateMessageLength, {
2961
- currentLength,
2962
- maxLength,
2963
- })}
2964
- </CapLabel>
2965
- );
2966
-
2967
1355
  // Check if any RCS variables contain tags (similar to Zalo hasTag logic)
2968
1356
  const hasTag = () => {
2969
1357
  // Check cardVarMapped values for tags
@@ -3006,27 +1394,78 @@ const onTitleAddVar = () => {
3006
1394
  setDltEditData(data);
3007
1395
  };
3008
1396
 
3009
- const rcsDltEditCompletionHandler = (data) => {
3010
- const tempData = {
3011
- versions: {
3012
- // eslint-disable-next-line no-undef
3013
- base: data?.value?.base || {},
3014
- },
1397
+ const rcsDltEditCompletionHandler = (data) => {
1398
+ const tempData = {
1399
+ versions: {
1400
+ // eslint-disable-next-line no-undef
1401
+ base: data?.value?.base || {},
1402
+ },
1403
+ };
1404
+ const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1405
+ '',
1406
+ );
1407
+ closeDltContainerHandler();
1408
+ setDltEditData(tempData);
1409
+ setFallbackMessage(fallMsg);
1410
+ setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1411
+ setShowDltCard(true);
1412
+ };
1413
+
1414
+ const rcsDltCardDeleteHandler = () => {
1415
+ closeDltContainerHandler();
1416
+ setDltEditData({});
1417
+ setFallbackMessage('');
1418
+ setFallbackMessageError(false);
1419
+ setShowDltCard(false);
1420
+ };
1421
+
1422
+ const dltFallbackListingPreviewhandler = (data) => {
1423
+ const {
1424
+ 'updated-sms-editor': updatedSmsEditor = [],
1425
+ 'sms-editor': smsEditor = '',
1426
+ } = data.versions.base || {};
1427
+ setFallbackPreviewmode(true);
1428
+ setDltPreviewData(
1429
+ updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1430
+ );
1431
+ };
1432
+
1433
+ const getDltContentCardList = (content, channel) => {
1434
+ const extra = [
1435
+ <CapIcon
1436
+ type="edit"
1437
+ style={{ marginRight: '8px' }}
1438
+ onClick={() => rcsDltEditSelectHandler(dltEditData)}
1439
+ />,
1440
+ <CapDropdown
1441
+ overlay={(
1442
+ <CapMenu>
1443
+ <>
1444
+ <CapMenu.Item
1445
+ className="ant-dropdown-menu-item"
1446
+ onClick={() => setFallbackPreviewmode(true)}
1447
+ >
1448
+ {formatMessage(globalMessages.preview)}
1449
+ </CapMenu.Item>
1450
+ <CapMenu.Item
1451
+ className="ant-dropdown-menu-item"
1452
+ onClick={rcsDltCardDeleteHandler}
1453
+ >
1454
+ {formatMessage(globalMessages.delete)}
1455
+ </CapMenu.Item>
1456
+ </>
1457
+ </CapMenu>
1458
+ )}
1459
+ >
1460
+ <CapIcon type="more" />
1461
+ </CapDropdown>,
1462
+ ];
1463
+ return {
1464
+ title: channel,
1465
+ content,
1466
+ cardType: channel,
1467
+ extra,
3015
1468
  };
3016
- const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
3017
- '',
3018
- );
3019
- const templateNameFromDlt = get(dltEditData, 'name', '')
3020
- || get(tempData, 'versions.base.name', '')
3021
- || '';
3022
- closeDltContainerHandler();
3023
- setDltEditData(tempData);
3024
- const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
3025
- setSmsFallbackData({
3026
- templateName: templateNameFromDlt,
3027
- content: fallMsg,
3028
- ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
3029
- });
3030
1469
  };
3031
1470
 
3032
1471
  const getDltSlideBoxContent = () => {
@@ -3074,34 +1513,148 @@ const onTitleAddVar = () => {
3074
1513
  return { dltHeader, dltContent };
3075
1514
  };
3076
1515
 
3077
- const renderFallBackSmsComponent = () => (
3078
- <SmsFallback
3079
- value={smsFallbackData}
3080
- onChange={setSmsFallbackData}
3081
- parentLocation={location}
3082
- smsRegister={smsRegister}
3083
- isFullMode={isFullMode}
3084
- selectedOfferDetails={selectedOfferDetails}
3085
- channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
3086
- sectionTitle={
3087
- smsFallbackData
3088
- ? formatMessage(messages.fallbackLabel)
3089
- : formatMessage(messages.smsFallbackOptional)
3090
- }
3091
- templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
3092
- templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
3093
- /* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
3094
- showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
3095
- disableSelectTemplate={isEditFlow}
3096
- eventContextTags={eventContextTags}
3097
- onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
3098
- isRcsEditFlow={isEditFlow}
3099
- />
3100
- );
1516
+ const renderFallBackSmsComponent = () => {
1517
+ // Completely disable fallback functionality when DLT is disabled
1518
+ return null;
1519
+
1520
+ // const contentArr = [];
1521
+ // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1522
+ // const showCardForDlt = isDltEnabled && showDltCard;
1523
+ // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1524
+ // //pushing common fallback sms headings
1525
+ // contentArr.push(
1526
+ // <CapRow
1527
+ // style={{
1528
+ // marginBottom: isDltEnabled ? '20px' : '10px',
1529
+ // }}
1530
+ // >
1531
+ // <CapHeader
1532
+ // title={(
1533
+ // <CapRow type="flex">
1534
+ // <CapHeading type="h4">
1535
+ // {formatMessage(messages.fallbackLabel)}
1536
+ // </CapHeading>
1537
+ // <CapTooltipWithInfo
1538
+ // placement="right"
1539
+ // infoIconProps={{
1540
+ // style: { marginLeft: '4px', marginTop: '3px' },
1541
+ // }}
1542
+ // title={formatMessage(messages.fallbackToolTip)}
1543
+ // />
1544
+ // </CapRow>
1545
+ // )}
1546
+ // description={formatMessage(messages.fallbackDesc)}
1547
+ // suffix={
1548
+ // isDltEnabled ? null : (
1549
+ // <CapButton
1550
+ // type="flat"
1551
+ // className="fallback-preview-btn"
1552
+ // prefix={<CapIcon type="eye" />}
1553
+ // style={{ color: CAP_SECONDARY.base }}
1554
+ // onClick={() => setFallbackPreviewmode(true)}
1555
+ // disabled={fallbackMessage === '' || fallbackMessageError}
1556
+ // >
1557
+ // {formatMessage(globalMessages.preview)}
1558
+ // </CapButton>
1559
+ // )
1560
+ // }
1561
+ // />
1562
+ // </CapRow>,
1563
+ // );
1564
+
1565
+ //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1566
+ // showAddCreativeBtnForDlt
1567
+ // && contentArr.push(
1568
+ // <CapCard className="rcs-dlt-fallback-card">
1569
+ // <CapRow type="flex" justify="center" align="middle">
1570
+ // <CapColumn span={10}>
1571
+ // <CapImage src={addCreativesIcon} />
1572
+ // </CapColumn>
1573
+ // <CapColumn span={14}>
1574
+ // <CapButton
1575
+ // className="add-dlt-btn"
1576
+ // type="secondary"
1577
+ // onClick={addDltMsgHandler}
1578
+ // >
1579
+ // {formatMessage(messages.addSmsCreative)}
1580
+ // </CapButton>
1581
+ // </CapColumn>
1582
+ // </CapRow>
1583
+ // </CapCard>,
1584
+ // );
1585
+
1586
+ // //dlt is enabled and dlt content is added, show it in a card
1587
+ // showCardForDlt
1588
+ // && contentArr.push(
1589
+ // <CapCustomCardList
1590
+ // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1591
+ // className="rcs-dlt-card"
1592
+ // />,
1593
+ // fallbackMessageError && (
1594
+ // <CapError className="rcs-fallback-len-error">
1595
+ // {formatMessage(messages.fallbackMsgLenError)}
1596
+ // </CapError>
1597
+ // ),
1598
+ // );
1599
+
1600
+ // //dlt is not enabled, show non dlt text area
1601
+ // showNonDltFallbackComp
1602
+ // && contentArr.push(
1603
+ // <>
1604
+ // <CapRow>
1605
+ // <CapHeader
1606
+ // title={(
1607
+ // <CapHeading type="h4">
1608
+ // {formatMessage(messages.fallbackTextAreaLabel)}
1609
+ // </CapHeading>
1610
+ // )}
1611
+ // suffix={(
1612
+ // <TagList
1613
+ // label={formatMessage(globalMessages.addLabels)}
1614
+ // onTagSelect={onTagSelectFallback}
1615
+ // location={location}
1616
+ // tags={tags || []}
1617
+ // onContextChange={handleOnTagsContextChange}
1618
+ // injectedTags={injectedTags || {}}
1619
+ // selectedOfferDetails={selectedOfferDetails}
1620
+ // />
1621
+ // )}
1622
+ // />
1623
+ // </CapRow>
1624
+ // <div className="rcs_fallback_msg_textarea_wrapper">
1625
+ // <TextArea
1626
+ // id="rcs_fallback_message_textarea"
1627
+ // autosize={{ minRows: 3, maxRows: 5 }}
1628
+ // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1629
+ // onChange={onFallbackMessageChange}
1630
+ // errorMessage={fallbackMessageError}
1631
+ // value={fallbackMessage || ""}
1632
+ // />
1633
+ // {!aiContentBotDisabled && (
1634
+ // <CapAskAira.ContentGenerationBot
1635
+ // text={fallbackMessage || ""}
1636
+ // setText={(text) => {
1637
+ // onFallbackMessageChange({ target: { value: text } });
1638
+ // }}
1639
+ // iconPlacement="float-br"
1640
+ // rootStyle={{
1641
+ // bottom: "0.5rem",
1642
+ // right: "0.5rem",
1643
+ // position: "absolute",
1644
+ // }}
1645
+ // />
1646
+ // )}
1647
+ // </div>
1648
+ // <CapRow>{fallbackSmsLength()}</CapRow>
1649
+ // </>
1650
+ // );
1651
+
1652
+ // return <>{contentArr}</>;
1653
+ };
3101
1654
 
3102
1655
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
3103
1656
  setImageError(null);
3104
- const isRcsThumbnail = isThumbnailAssetIndex(index);
1657
+ const isRcsThumbnail = index === 1;
3105
1658
  actions.uploadRcsAsset(file, type, {
3106
1659
  isRcsThumbnail,
3107
1660
  ...fileParams,
@@ -3139,7 +1692,10 @@ const onTitleAddVar = () => {
3139
1692
  const updateOnRcsImageReUpload = useCallback(() => {
3140
1693
  setUpdateRcsImageSrc('');
3141
1694
  }, []);
1695
+
1696
+
3142
1697
  const uploadRcsVideo = (file, type, fileParams) => {
1698
+ setImageError(null);
3143
1699
  actions.uploadRcsAsset(file, type, {
3144
1700
  ...fileParams,
3145
1701
  type: 'video',
@@ -3148,6 +1704,9 @@ const onTitleAddVar = () => {
3148
1704
  });
3149
1705
  };
3150
1706
 
1707
+ const updateRcsVideoSrc = (val) => {
1708
+ setRcsVideoSrc(val);
1709
+ };
3151
1710
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
3152
1711
  setRcsVideoSrc(val);
3153
1712
  setAssetList(val);
@@ -3170,13 +1729,13 @@ const onTitleAddVar = () => {
3170
1729
  updateRcsThumbnailSrc('');
3171
1730
  };
3172
1731
 
3173
- const renderThumbnailComponent = () => {
1732
+ const renderThumbnailComponent = () => {
3174
1733
  const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
3175
1734
  return !isEditFlow && (
3176
1735
  <>
3177
1736
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
3178
1737
  <CapImageUpload
3179
- className="rcs-image-upload-with-top-padding"
1738
+ style={{ paddingTop: '20px' }}
3180
1739
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
3181
1740
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
3182
1741
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -3194,7 +1753,6 @@ const onTitleAddVar = () => {
3194
1753
  channel={RCS}
3195
1754
  channelSpecificStyle={!isFullMode}
3196
1755
  skipDimensionValidation={true}
3197
- showReUploadButton={!isEditFlow && isFullMode}
3198
1756
  />
3199
1757
  </>
3200
1758
  )
@@ -3220,7 +1778,7 @@ const onTitleAddVar = () => {
3220
1778
  value: dim.type,
3221
1779
  label: `${dim.label}`
3222
1780
  }))}
3223
- className="rcs-dimension-select--bottom-spacing"
1781
+ style={{ marginBottom: '20px' }}
3224
1782
  />
3225
1783
  </>
3226
1784
  )}
@@ -3234,7 +1792,7 @@ const onTitleAddVar = () => {
3234
1792
  </div>
3235
1793
  ) : (
3236
1794
  <CapImageUpload
3237
- className="rcs-image-upload-with-top-padding"
1795
+ style={{ paddingTop: '20px' }}
3238
1796
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
3239
1797
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
3240
1798
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -3245,7 +1803,7 @@ const onTitleAddVar = () => {
3245
1803
  updateImageSrc={setUpdateRcsImageSrc}
3246
1804
  updateOnReUpload={updateOnRcsImageReUpload}
3247
1805
  index={0}
3248
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1806
+ className="cap-custom-image-upload"
3249
1807
  key={`rcs-uploaded-image-${selectedDimension}`}
3250
1808
  imageData={rcsData}
3251
1809
  channel={RCS}
@@ -3256,7 +1814,7 @@ const onTitleAddVar = () => {
3256
1814
 
3257
1815
  </>
3258
1816
  );
3259
- }
1817
+ }
3260
1818
 
3261
1819
  const renderVideoComponent = () => {
3262
1820
  const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
@@ -3275,7 +1833,7 @@ const onTitleAddVar = () => {
3275
1833
  value: dim.type,
3276
1834
  label: `${dim.label}`
3277
1835
  }))}
3278
- className="rcs-dimension-select--bottom-spacing"
1836
+ style={{ marginBottom: '20px' }}
3279
1837
  />
3280
1838
  )}
3281
1839
  {(isEditFlow || !isFullMode) ? (
@@ -3333,48 +1891,10 @@ const onTitleAddVar = () => {
3333
1891
  };
3334
1892
 
3335
1893
  const getRcsPreview = () => {
3336
-
3337
- if (templateType === contentType.carousel) {
3338
- const cardsForPreview = buildCarouselCardsForPreview(carouselData);
3339
- const carouselDimKey = getCarouselDimensionKey();
3340
- const carouselImgDims =
3341
- RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
3342
- const carouselVidDims =
3343
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
3344
- || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
3345
- // Debug log for embedded/library mode preview payload (carousel)
3346
- // eslint-disable-next-line no-console
3347
- return (
3348
- <UnifiedPreview
3349
- channel={RCS}
3350
- content={{
3351
- carouselData: cardsForPreview,
3352
- carouselPreviewDimensions: {
3353
- imageWidth: carouselImgDims.width,
3354
- imageHeight: carouselImgDims.height,
3355
- videoThumbWidth: carouselVidDims.width,
3356
- videoThumbHeight: carouselVidDims.height,
3357
- },
3358
- }}
3359
- device={ANDROID}
3360
- showDeviceToggle={false}
3361
- showHeader={false}
3362
- formatMessage={formatMessage}
3363
- />
3364
- );
3365
- }
3366
1894
 
3367
1895
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
3368
- const isSlotMappingMode = isEditFlow || !isFullMode;
3369
- const titleVarCountForResolve = isMediaTypeText
3370
- ? 0
3371
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
3372
- const resolvedTitle = isMediaTypeText
3373
- ? ''
3374
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
3375
- const resolvedDesc = isSlotMappingMode
3376
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
3377
- : templateDesc;
1896
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1897
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
3378
1898
  return (
3379
1899
  <UnifiedPreview
3380
1900
  channel={RCS}
@@ -3397,108 +1917,51 @@ const onTitleAddVar = () => {
3397
1917
  );
3398
1918
  };
3399
1919
 
1920
+ const getUnmappedDesc = (str, mapping) => {
1921
+ if (!str) return '';
1922
+ if (!mapping || Object.keys(mapping).length === 0) return str;
1923
+ let result = str;
1924
+ const replacements = [];
1925
+ Object.entries(mapping).forEach(([key, value]) => {
1926
+ const raw = (value ?? '').toString();
1927
+ if (!raw || raw?.trim?.() === '') return;
1928
+ const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1929
+ replacements.push({ key, needle: raw });
1930
+ if (braced !== raw) replacements.push({ key, needle: braced });
1931
+ });
1932
+ const seen = new Set();
1933
+ const uniq = replacements
1934
+ .filter(({ key, needle }) => {
1935
+ const id = `${key}::${needle}`;
1936
+ if (seen.has(id)) return false;
1937
+ seen.add(id);
1938
+ return true;
1939
+ })
1940
+ .sort((a, b) => (b.needle.length - a.needle.length));
1941
+
1942
+ uniq.forEach(({ key, needle }) => {
1943
+ if (!needle) return;
1944
+ const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1945
+ const regex = new RegExp(escaped, 'g');
1946
+ result = result.replace(regex, `{{${key}}}`);
1947
+ });
1948
+ return result;
1949
+ };
1950
+
3400
1951
  const createPayload = () => {
3401
- const isSlotMappingMode = isEditFlow || !isFullMode;
1952
+ const base = get(dltEditData, `versions.base`, {});
1953
+ const {
1954
+ template_id: templateId = '',
1955
+ template_name = '',
1956
+ 'sms-editor': template = '',
1957
+ header: registeredSenderIds = [],
1958
+ } = base;
1959
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1960
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
3402
1961
  const alignment = isMediaTypeImage
3403
1962
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
3404
1963
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
3405
1964
 
3406
- const heightTypeForCardWidth = isMediaTypeText
3407
- ? undefined
3408
- : isMediaTypeImage
3409
- ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
3410
- : isMediaTypeVideo
3411
- ? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
3412
- : undefined;
3413
- const cardWidthFromSelection =
3414
- heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
3415
-
3416
- /** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
3417
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
3418
- const smsFallbackMerged = !isFullMode
3419
- ? (() => {
3420
- const local =
3421
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
3422
- return {
3423
- ...smsFromApiShape,
3424
- ...local,
3425
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
3426
- };
3427
- })()
3428
- : (smsFallbackData || {});
3429
- const smsFallbackForPayload = (() => {
3430
- if (isFullMode) {
3431
- return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
3432
- }
3433
- const mapped = {
3434
- templateName:
3435
- smsFallbackMerged.templateName
3436
- || smsFallbackMerged.smsTemplateName
3437
- || '',
3438
- // Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
3439
- content:
3440
- smsFallbackMerged.content
3441
- || smsFallbackMerged.smsContent
3442
- || smsFallbackMerged.smsTemplateContent
3443
- || smsFallbackMerged.message
3444
- || '',
3445
- templateContent:
3446
- pickFirstSmsFallbackTemplateString(smsFallbackMerged)
3447
- || '',
3448
- ...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
3449
- && { unicodeValidity: smsFallbackMerged.unicodeValidity }),
3450
- ...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
3451
- && Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
3452
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
3453
- smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
3454
- }),
3455
- };
3456
- return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
3457
- })();
3458
-
3459
- const carouselCardContent = isCarouselType
3460
- ? (carouselData || []).map((card = {}) => {
3461
- const cardMediaType = card.mediaType;
3462
- const isCardVideo = cardMediaType === RCS_MEDIA_TYPES.VIDEO;
3463
- const isCardImage = cardMediaType === RCS_MEDIA_TYPES.IMAGE;
3464
- const mediaUrl = isCardVideo
3465
- ? (card.videoAsset?.videoSrc || '')
3466
- : (card.imageSrc || '');
3467
- const thumbnailUrl = isCardVideo
3468
- ? (card.thumbnailSrc || card.videoAsset?.videoThumbnail || '')
3469
- : '';
3470
- const cardVarTokens = [
3471
- ...((card.title || '').match(rcsVarRegex) ?? []),
3472
- ...((card.description || '').match(rcsVarRegex) ?? []),
3473
- ];
3474
- const cardVarMappedForCard = {};
3475
- if (isSlotMappingMode && cardVarTokens.length > 0) {
3476
- cardVarTokens.forEach((token) => {
3477
- const varName = getVarNameFromToken(token);
3478
- if (!varName) return;
3479
- cardVarMappedForCard[varName] = sanitizeCardVarMappedValue(cardVarMapped?.[varName] ?? '');
3480
- });
3481
- }
3482
- return {
3483
- title: card.title || '',
3484
- description: card.description || '',
3485
- mediaType: cardMediaType,
3486
- ...((isCardImage || isCardVideo) && {
3487
- media: {
3488
- mediaUrl,
3489
- thumbnailUrl,
3490
- height: selectedCarouselHeight || MEDIUM,
3491
- ...(isCardVideo && card.videoAsset?.videoName && { videoName: card.videoAsset.videoName }),
3492
- },
3493
- }),
3494
- ...(Array.isArray(card.suggestions) && card.suggestions.length > 0 && {
3495
- suggestions: card.suggestions,
3496
- }),
3497
- ...(Object.keys(cardVarMappedForCard).length > 0 && { cardVarMapped: cardVarMappedForCard }),
3498
- };
3499
- })
3500
- : null;
3501
-
3502
1965
  const payload = {
3503
1966
  name: templateName,
3504
1967
  versions: {
@@ -3507,22 +1970,16 @@ const onTitleAddVar = () => {
3507
1970
  RCS: {
3508
1971
  rcsContent: {
3509
1972
  ...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
3510
- cardType: isCarouselType ? contentType.carousel : STANDALONE,
3511
- cardSettings: isCarouselType
3512
- ? { cardOrientation: VERTICAL, cardWidth: selectedCarouselWidth || SMALL }
3513
- : {
3514
- cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
3515
- ...(alignment && { mediaAlignment: alignment }),
3516
- cardWidth: SMALL,
3517
- },
3518
- cardContent: isCarouselType
3519
- ? carouselCardContent
3520
- : [
1973
+ cardType: STANDALONE,
1974
+ cardSettings: {
1975
+ cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
1976
+ ...(alignment && { mediaAlignment: alignment }),
1977
+ cardWidth: SMALL,
1978
+ },
1979
+ cardContent: [
3521
1980
  {
3522
- // Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
3523
- // / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
3524
- title: templateTitle,
3525
- description: templateDesc,
1981
+ title: resolvedTitle,
1982
+ description: resolvedDesc,
3526
1983
  mediaType: templateMediaType,
3527
1984
  ...(!isMediaTypeText && {media: {
3528
1985
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -3531,32 +1988,23 @@ const onTitleAddVar = () => {
3531
1988
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
3532
1989
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
3533
1990
  }}),
3534
- ...(isSlotMappingMode && (() => {
3535
- const templateVarTokens = [
3536
- ...(templateTitle?.match(rcsVarRegex) ?? []),
3537
- ...(templateDesc?.match(rcsVarRegex) ?? []),
1991
+ ...(!isFullMode && (() => {
1992
+ const tokens = [
1993
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1994
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
3538
1995
  ];
3539
- const cardVarMappedForRcsCardOnly = pickRcsCardVarMappedEntries(
3540
- cardVarMapped,
3541
- );
3542
- // Persist numeric slot keys only ("1","2",…) — avoids duplicating the same value under
3543
- // semantic names (e.g. both "1" and dynamic_expiry_date_after_3_days.FORMAT_1). Hydration
3544
- // and coalesceCardVarMappedToTemplate still resolve from numeric keys + template tokens.
3545
- const persistedSlotVarMap = {};
3546
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
3547
- const varName = getVarNameFromToken(token);
3548
- if (!varName) return;
3549
- const resolvedRawValue = resolveCardVarMappedSlotValue(
3550
- cardVarMappedForRcsCardOnly,
3551
- varName,
3552
- slotIndexZeroBased,
3553
- isSlotMappingMode,
3554
- rcsSpanningSemanticVarNames.has(varName),
3555
- );
3556
- const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
3557
- persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
1996
+ const allowedKeys = tokens
1997
+ .map((t) => getVarNameFromToken(t))
1998
+ .filter(Boolean);
1999
+ const nextMap = {};
2000
+ allowedKeys.forEach((k) => {
2001
+ if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
2002
+ nextMap[k] = cardVarMapped[k];
2003
+ } else {
2004
+ nextMap[k] = '';
2005
+ }
3558
2006
  });
3559
- return { cardVarMapped: persistedSlotVarMap };
2007
+ return { cardVarMapped: nextMap };
3560
2008
  })()),
3561
2009
  ...(suggestions.length > 0 && { suggestions }),
3562
2010
  }
@@ -3564,109 +2012,17 @@ const onTitleAddVar = () => {
3564
2012
  contentType: isFullMode ? templateType : RICHCARD,
3565
2013
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
3566
2014
  },
3567
- ...(smsFallbackForPayload && (() => {
3568
- const smsBodyText =
3569
- smsFallbackForPayload.content
3570
- || smsFallbackForPayload.templateContent
3571
- || smsFallbackForPayload.message
3572
- || smsFallbackForPayload.smsContent
3573
- || '';
3574
- /**
3575
- * Campaigns `getTraiSenderIds` / Iris read `smsFallBackContent.templateConfigs.registeredSenderIds`.
3576
- * Library `smsFallbackForPayload` omits ids — use merged state (`smsFallbackMerged`) like test preview.
3577
- */
3578
- const m = smsFallbackMerged || {};
3579
- const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
3580
- ? m.templateConfigs
3581
- : {};
3582
- const smsFallbackTemplateId =
3583
- (m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
3584
- ? String(m.smsTemplateId)
3585
- : '')
3586
- || (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
3587
- ? String(tcSibling.templateId)
3588
- : '');
3589
- const smsFallbackTemplateStr =
3590
- pickFirstSmsFallbackTemplateString(m)
3591
- || (typeof m.templateContent === 'string' ? m.templateContent : '')
3592
- || (typeof tcSibling.template === 'string' ? tcSibling.template : '')
3593
- || '';
3594
- const smsFallbackTemplateName =
3595
- m.templateName
3596
- || m.smsTemplateName
3597
- || tcSibling.templateName
3598
- || tcSibling.name
3599
- || '';
3600
- const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
3601
- ? m.registeredSenderIds
3602
- : Array.isArray(tcSibling.registeredSenderIds)
3603
- ? tcSibling.registeredSenderIds
3604
- : Array.isArray(tcSibling.header)
3605
- ? tcSibling.header
3606
- : null;
3607
- const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
3608
- const smsFallbackTemplateConfigs =
3609
- smsFallbackTemplateId || hasRegisteredSenderIds
3610
- ? {
3611
- ...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
3612
- ...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
3613
- ...(smsFallbackTemplateName && {
3614
- templateName: smsFallbackTemplateName,
3615
- }),
3616
- ...(hasRegisteredSenderIds && {
3617
- registeredSenderIds: registeredSenderIdsForPayload,
3618
- }),
3619
- }
3620
- : null;
3621
- const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
3622
- return {
3623
- smsFallBackContent: isFullMode
3624
- ? {
3625
- smsTemplateName: smsFallbackForPayload.templateName || '',
3626
- smsContent: smsBodyText,
3627
- // cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
3628
- message: smsBodyText,
3629
- ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
3630
- unicodeValidity: smsFallbackForPayload.unicodeValidity,
3631
- }),
3632
- ...(smsFallbackForPayload.rcsSmsFallbackVarMapped
3633
- && Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
3634
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
3635
- }),
3636
- ...(smsFallbackTemplateConfigs && {
3637
- templateConfigs: smsFallbackTemplateConfigs,
3638
- }),
3639
- }
3640
- : {
3641
- // Round-trip storage: full shape so reopening the editor restores template
3642
- // name, sender IDs, and var mappings. normalizeRcsMessageContentForApi
3643
- // (called in CreativesContainer getCreativesData) strips this to
3644
- // { message, templateConfigs } before the API call — the extra fields here
3645
- // are only used for re-hydration, never sent to the API.
3646
- message: smsBodyText,
3647
- ...(smsFallbackForPayload.templateName && {
3648
- smsTemplateName: smsFallbackForPayload.templateName,
3649
- }),
3650
- ...(smsFallbackForPayload.templateContent && {
3651
- templateContent: smsFallbackForPayload.templateContent,
3652
- }),
3653
- ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
3654
- unicodeValidity: smsFallbackForPayload.unicodeValidity,
3655
- }),
3656
- ...(Array.isArray(registeredSenderIdsForPayload) && {
3657
- registeredSenderIds: registeredSenderIdsForPayload,
3658
- }),
3659
- ...(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
3660
- && Object.keys(smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
3661
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
3662
- smsFallbackForPayload[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
3663
- }),
3664
- ...(isDltCampaign && smsFallbackTemplateConfigs && {
3665
- templateConfigs: smsFallbackTemplateConfigs,
3666
- }),
3667
- },
3668
- };
3669
- })()),
2015
+ smsFallBackContent: {
2016
+ message: fallbackMessage,
2017
+ ...(isDltEnabled && {
2018
+ templateConfigs: {
2019
+ templateId,
2020
+ templateName: template_name,
2021
+ template,
2022
+ registeredSenderIds,
2023
+ },
2024
+ }),
2025
+ },
3670
2026
  },
3671
2027
  },
3672
2028
  },
@@ -3676,141 +2032,6 @@ const onTitleAddVar = () => {
3676
2032
  return payload;
3677
2033
  };
3678
2034
 
3679
- /** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
3680
- const testPreviewFormData = useMemo(() => {
3681
- const payload = createPayload();
3682
- const rcs = payload?.versions?.base?.content?.RCS;
3683
- if (!rcs) return null;
3684
- // createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
3685
- const accountIdForCreateMessageMeta =
3686
- (wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
3687
- ? String(wecrmAccountId)
3688
- : accountId;
3689
- const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
3690
- let rcsForTest = {
3691
- ...rcs,
3692
- rcsContent: {
3693
- ...rcs.rcsContent,
3694
- ...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
3695
- },
3696
- };
3697
- /** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
3698
- const cardContent = rcsForTest.rcsContent?.cardContent;
3699
- if (Array.isArray(cardContent) && cardContent[0]) {
3700
- if (isCarouselType) {
3701
- // Carousel: resolve {{N}} slot tokens to the actual tag expressions / static values the
3702
- // user mapped via cardVarMapped. Run this for ALL modes (create, edit, consumer) so that:
3703
- // - buildRcsTestMessagePayload sends real Capillary tag names to the test API, and
3704
- // - prepareTagExtractionPayload can extract tag metadata from the card content.
3705
- // Track cumulative slotOffset across cards: each card's title/description vars occupy
3706
- // sequential global slot indices ({{1}},{{2}} in card 0; {{3}},{{4}} in card 1, …).
3707
- // Without the offset, every card restarts at slotKey="1" and resolves against card 0's
3708
- // mappings, causing tags from card 1+ to be missing or wrong.
3709
- let carouselSlotOffset = 0;
3710
- rcsForTest = {
3711
- ...rcsForTest,
3712
- rcsContent: {
3713
- ...rcsForTest.rcsContent,
3714
- cardContent: cardContent.map((card) => {
3715
- const rawTitle = card.title || '';
3716
- const rawDesc = card.description || '';
3717
- const titleVarCount = (rawTitle.match(rcsVarRegex) || []).length;
3718
- const descVarCount = (rawDesc.match(rcsVarRegex) || []).length;
3719
- const resolvedTitle = resolveTemplateWithMap(rawTitle, carouselSlotOffset);
3720
- const resolvedDesc = resolveTemplateWithMap(rawDesc, carouselSlotOffset + titleVarCount);
3721
- carouselSlotOffset += titleVarCount + descVarCount;
3722
- return { ...card, title: resolvedTitle, description: resolvedDesc };
3723
- }),
3724
- },
3725
- };
3726
- } else if (isSlotMappingModeForPreview) {
3727
- // Standalone card: coalesce cardVarMapped with the template's slot names so the preview
3728
- // API receives a correctly-keyed var map for non-carousel templates.
3729
- const fullCardVarMapped = coalesceCardVarMappedToTemplate(
3730
- pickRcsCardVarMappedEntries(cardVarMapped),
3731
- templateTitle,
3732
- templateDesc,
3733
- rcsVarRegex,
3734
- );
3735
- rcsForTest = {
3736
- ...rcsForTest,
3737
- rcsContent: {
3738
- ...rcsForTest.rcsContent,
3739
- cardContent: [
3740
- { ...cardContent[0], cardVarMapped: fullCardVarMapped },
3741
- ...cardContent.slice(1),
3742
- ],
3743
- },
3744
- };
3745
- }
3746
- }
3747
- const out = {
3748
- versions: {
3749
- base: {
3750
- content: {
3751
- RCS: rcsForTest,
3752
- },
3753
- },
3754
- },
3755
- };
3756
- // Use the smsFallbackTemplateConfigs already built by createPayload() — it uses smsFallbackMerged
3757
- // which merges smsFromApiShape (API baseline with templateId, registeredSenderIds, templateName, etc.)
3758
- // with local smsFallbackData state. Reading raw smsFallbackData here would miss the API-sourced
3759
- // smsTemplateId in campaign mode (smsFallbackMerged = { ...smsFromApiShape, ...local } there).
3760
- // This also keeps templateName (and all other fields) in sync with the approval payload automatically.
3761
- const smsFallbackTcFromPayload = rcs?.rcsContent?.smsFallBackContent?.templateConfigs;
3762
- if (smsFallbackTcFromPayload && typeof smsFallbackTcFromPayload === 'object') {
3763
- out.templateConfigs = {
3764
- ...smsFallbackTcFromPayload,
3765
- traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
3766
- };
3767
- }
3768
- return out;
3769
- }, [
3770
- templateName,
3771
- templateTitle,
3772
- templateDesc,
3773
- templateMediaType,
3774
- cardVarMapped,
3775
- carouselData,
3776
- suggestions,
3777
- rcsImageSrc,
3778
- rcsVideoSrc,
3779
- rcsThumbnailSrc,
3780
- selectedDimension,
3781
- smsFallbackData,
3782
- isFullMode,
3783
- isEditFlow,
3784
- templateType,
3785
- accountId,
3786
- wecrmAccountId,
3787
- accessToken,
3788
- accountName,
3789
- hostName,
3790
- smsRegister,
3791
- ]);
3792
-
3793
- /**
3794
- * Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
3795
- * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
3796
- * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
3797
- */
3798
- const librarySmsFallbackMergedForValidation = useMemo(() => {
3799
- if (isFullMode) {
3800
- return smsFallbackData;
3801
- }
3802
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
3803
- const local =
3804
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
3805
- return {
3806
- ...smsFromApiShape,
3807
- ...local,
3808
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
3809
- };
3810
- }, [isFullMode, templateData, smsFallbackData]);
3811
-
3812
-
3813
-
3814
2035
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
3815
2036
  // eslint-disable-next-line no-undef
3816
2037
  const error = errorMessage?.message || errorMessage;
@@ -3840,9 +2061,6 @@ const onTitleAddVar = () => {
3840
2061
  _id: params?.id,
3841
2062
  validity: true,
3842
2063
  type: RCS,
3843
- // CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
3844
- // the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
3845
- closeSlideBoxAfterSubmit: !isFullMode,
3846
2064
  };
3847
2065
  getFormData(formDataParams);
3848
2066
  };
@@ -3856,7 +2074,6 @@ const onTitleAddVar = () => {
3856
2074
  actionCallback({ resp, errorMessage });
3857
2075
  setSpin(false); // Always turn off spinner
3858
2076
  if (!errorMessage) {
3859
- setTemplateStatus(RCS_STATUSES.pending);
3860
2077
  onCreateComplete();
3861
2078
  }
3862
2079
  });
@@ -3868,91 +2085,6 @@ const onTitleAddVar = () => {
3868
2085
  }
3869
2086
  };
3870
2087
 
3871
- /** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
3872
- const smsFallbackBlocksDone = () => {
3873
- // Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
3874
- if (
3875
- !isFullMode
3876
- && !isTraiDLTEnable(isFullMode, smsRegister)
3877
- && smsFallbackData == null
3878
- && hasMeaningfulSmsFallbackShape(
3879
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
3880
- )
3881
- ) {
3882
- return true;
3883
- }
3884
- if (!smsFallbackData) return false;
3885
- // Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
3886
- // concern, not a structural requirement for approval — the registered SMS template body stands on
3887
- // its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
3888
- if (isFullMode) return false;
3889
- const merged = librarySmsFallbackMergedForValidation;
3890
- const templateText = pickFirstSmsFallbackTemplateString(merged);
3891
- if (!templateText) {
3892
- return true;
3893
- }
3894
- const rawVarMap =
3895
- merged.rcsSmsFallbackVarMapped
3896
- || merged['rcs-sms-fallback-var-mapped'];
3897
- const varMap =
3898
- rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
3899
- return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
3900
- };
3901
-
3902
- /**
3903
- * Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
3904
- * semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
3905
- * / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
3906
- */
3907
- const isLibraryCampaignCardVarMappingIncomplete = () => {
3908
- if (isFullMode) return false;
3909
- const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
3910
- rcsVarTestRegex.test(elem),
3911
- );
3912
- const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
3913
- rcsVarTestRegex.test(elem),
3914
- );
3915
- const orderedVarNames = [
3916
- ...titleTokens.map((t) => t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
3917
- ...descTokens.map((t) => t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '')),
3918
- ];
3919
- if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
3920
- return true;
3921
- }
3922
- return orderedVarNames.some((name, globalIdx) => {
3923
- const v = resolveCardVarMappedSlotValue(
3924
- cardVarMapped,
3925
- name,
3926
- globalIdx,
3927
- true,
3928
- rcsSpanningSemanticVarNames.has(name),
3929
- );
3930
- const s = v == null ? '' : String(v);
3931
- return s.trim() === '';
3932
- });
3933
- };
3934
-
3935
- const isCarouselLibraryIncomplete = () => {
3936
- if (!isCarouselType || isFullMode) return false;
3937
- if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
3938
- // Block if any card's title or description has no content at all (no static text and no variable tokens).
3939
- const hasEmptyField = (carouselData || []).some((card) =>
3940
- ['title', 'description'].some((field) => !(card?.[field] || '').trim())
3941
- );
3942
- if (hasEmptyField) return true;
3943
- const unfilledVar = (carouselData || []).some((card) =>
3944
- ['title', 'description'].some((field) => {
3945
- const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((t) => rcsVarTestRegex.test(t));
3946
- return tokens.some((t) => {
3947
- const name = t.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
3948
- const v = cardVarMapped?.[name];
3949
- return v == null || String(v).trim() === '';
3950
- });
3951
- })
3952
- );
3953
- return unfilledVar;
3954
- };
3955
-
3956
2088
  const isDisableDone = () => {
3957
2089
  if(isEditFlow){
3958
2090
  return false;
@@ -3963,26 +2095,46 @@ const onTitleAddVar = () => {
3963
2095
  }
3964
2096
  }
3965
2097
 
3966
- if (isCarouselLibraryIncomplete()) {
3967
- return true;
3968
- }
2098
+ if(!isFullMode){
2099
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2100
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2101
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
3969
2102
 
3970
- if (isLibraryCampaignCardVarMappingIncomplete()) {
3971
- return true;
3972
- }
2103
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2104
+ return true;
2105
+ }
3973
2106
 
3974
- if (smsFallbackBlocksDone()) {
3975
- return true;
2107
+ const hasEmptyMapping =
2108
+ cardVarMapped &&
2109
+ Object.keys(cardVarMapped).length > 0 &&
2110
+ Object.entries(cardVarMapped).some(([_, v]) => {
2111
+ if (typeof v !== 'string') return !v; // null/undefined
2112
+ return v.trim() === ''; // empty string
2113
+ });
2114
+
2115
+ if (hasEmptyMapping) {
2116
+ return true;
2117
+ }
2118
+
2119
+ const anyMissing = allVars.some(name => {
2120
+ const v = cardVarMapped?.[name];
2121
+ if (typeof v !== 'string') return !v;
2122
+ return v.trim() === '';
2123
+ });
2124
+ if (anyMissing) {
2125
+ return true;
2126
+ }
3976
2127
  }
3977
2128
 
3978
- if (!isCarouselType && isMediaTypeText && templateDesc.trim() === '') {
2129
+ if (isMediaTypeText && templateDesc.trim() === '') {
3979
2130
  return true;
2131
+
3980
2132
  }
3981
- if (!isCarouselType && isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2133
+ if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
3982
2134
  return true;
3983
2135
  }
3984
2136
 
3985
- if (!isCarouselType && isMediaTypeVideo && (!rcsVideoSrc.videoSrc || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
2137
+ if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
3986
2138
  return true;
3987
2139
  }
3988
2140
  if (buttonType.includes(CTA)) {
@@ -3994,60 +2146,72 @@ const onTitleAddVar = () => {
3994
2146
  return true;
3995
2147
  }
3996
2148
  }
3997
- if (templateDescError || templateTitleError) {
3998
- return true;
3999
- }
4000
- if (
4001
- smsFallbackData?.content
4002
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
4003
- ) {
2149
+ if (templateDescError || templateTitleError || fallbackMessageError) {
4004
2150
  return true;
4005
2151
  }
4006
2152
  return false;
4007
2153
  };
4008
2154
 
4009
2155
  const isEditDisableDone = () => {
4010
- if (isFullMode && !isHostInfoBip && templateStatus !== RCS_STATUSES.approved) {
2156
+
2157
+ if (templateStatus !== RCS_STATUSES.approved) {
4011
2158
  return true;
4012
2159
  }
4013
2160
 
4014
- if (isCarouselLibraryIncomplete()) {
4015
- return true;
2161
+ if (!isFullMode) {
2162
+ if (templateName.trim() === '' || templateNameError) {
2163
+ return true;
2164
+ }
4016
2165
  }
2166
+ if(!isFullMode){
2167
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2168
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2169
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
4017
2170
 
4018
- if (isLibraryCampaignCardVarMappingIncomplete()) {
4019
- return true;
4020
- }
2171
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2172
+ return true;
2173
+ }
4021
2174
 
4022
- if (smsFallbackBlocksDone()) {
4023
- return true;
4024
- }
2175
+ const hasEmptyMapping =
2176
+ cardVarMapped &&
2177
+ Object.keys(cardVarMapped).length > 0 &&
2178
+ Object.entries(cardVarMapped).some(([_, v]) => {
2179
+ if (typeof v !== 'string') return !v; // null/undefined
2180
+ return v.trim() === ''; // empty string
2181
+ });
2182
+
2183
+ if (hasEmptyMapping) {
2184
+ return true;
2185
+ }
4025
2186
 
4026
- if (!isCarouselType && isMediaTypeText && templateDesc.trim() === '') {
2187
+ const anyMissing = allVars.some(name => {
2188
+ const v = cardVarMapped?.[name];
2189
+ if (typeof v !== 'string') return !v;
2190
+ return v.trim() === '';
2191
+ });
2192
+ if (anyMissing) {
2193
+ return true;
2194
+ }
2195
+ }
2196
+ if (isMediaTypeText && templateDesc.trim() === '') {
4027
2197
  return true;
4028
2198
  }
4029
- if (!isCarouselType && isMediaTypeImage && rcsImageSrc === '') {
2199
+ if (isMediaTypeImage && rcsImageSrc === '') {
4030
2200
  return true;
4031
2201
  }
4032
- if (!isCarouselType && isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
2202
+ if(isMediaTypeVideo && (rcsThumbnailSrc === '' || rcsVideoSrc.videoSrc === '')) {
4033
2203
  return true;
4034
2204
  }
4035
2205
 
4036
2206
  if (buttonType.includes(CTA)) {
4037
- const hasValidButtons = suggestions.every(suggestion =>
2207
+ const hasValidButtons = suggestions.every(suggestion =>
4038
2208
  suggestion.text && suggestion.url && !suggestionError && !forbiddenCharactersValidation(suggestion.text)
4039
2209
  );
4040
2210
  if (!hasValidButtons) {
4041
2211
  return true;
4042
2212
  }
4043
2213
  }
4044
- if (templateTitleError || templateDescError) {
4045
- return true;
4046
- }
4047
- if (
4048
- smsFallbackData?.content
4049
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
4050
- ) {
2214
+ if (templateTitleError || templateDescError || fallbackMessageError) {
4051
2215
  return true;
4052
2216
  }
4053
2217
  return false;
@@ -4097,111 +2261,85 @@ const onTitleAddVar = () => {
4097
2261
  };
4098
2262
 
4099
2263
  const getMainContent = () => {
4100
- // Slideboxes are rendered outside the page-level spinner to avoid
4101
- // stacking/blur issues during initial loads.
4102
- if (showDltContainer) return null;
2264
+ if (showDltContainer && !fallbackPreviewmode) {
2265
+ const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2266
+ const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2267
+ return (
2268
+ <CapSlideBox
2269
+ show={showDltContainer}
2270
+ header={dltHeader}
2271
+ content={dltContent}
2272
+ handleClose={closeDltContainerHandler}
2273
+ size="size-xl"
2274
+ />
2275
+ );
2276
+ }
2277
+
4103
2278
  return (
4104
2279
  <>
4105
- {templateStatus !== '' && (
4106
- <CapRow useLegacy className="template-status-container">
4107
- <CapColumn span={14}>
4108
- <CapLabel type="label2">
4109
- {formatMessage(messages.templateStatusLabel)}
4110
- </CapLabel>
4111
-
4112
- {!isHostInfoBip && templateStatus && (
4113
- <CapAlert
4114
- message={getTemplateStatusMessage()}
4115
- type={getTemplateStatusType(templateStatus)}
4116
- />
4117
- )}
4118
- </CapColumn>
4119
- </CapRow>
2280
+ {templateStatus !== '' && (<CapRow useLegacy className="template-status-container">
2281
+ <CapLabel type="label2">
2282
+ {formatMessage(messages.templateStatusLabel)}
2283
+ </CapLabel>
2284
+
2285
+ {templateStatus && (
2286
+ <CapAlert
2287
+ message={getTemplateStatusMessage()}
2288
+ type={getTemplateStatusType(templateStatus)}
2289
+ />
2290
+ )}
2291
+ </CapRow>
4120
2292
  )}
4121
- <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2293
+ <CapRow className="cap-rcs-creatives">
4122
2294
  <CapColumn span={14}>
4123
2295
  {/* template name */}
4124
2296
  {isFullMode && (
4125
- isEditFlow ? (
4126
- <div className="rcs-creative-name-readonly">
4127
- <CapHeading type="h4">
4128
- {formatMessage(globalMessages.creativeNameLabel)}
4129
- </CapHeading>
4130
- <CapHeading type="h5" className="rcs-creative-name-value">
4131
- {templateName || '-'}
4132
- </CapHeading>
4133
- </div>
4134
- ) : (
4135
- <CapInput
4136
- id="rcs_template_name_input"
4137
- data-testid="template_name"
4138
- onChange={onTemplateNameChange}
4139
- errorMessage={templateNameError}
4140
- placeholder={formatMessage(
4141
- globalMessages.templateNamePlaceholder,
4142
- )}
4143
- value={templateName || ''}
4144
- size="default"
4145
- label={formatMessage(globalMessages.creativeNameLabel)}
4146
- disabled={(isEditFlow || !isFullMode)}
4147
- />
4148
- )
4149
- )}
4150
- {isEditFlow ? (
4151
- <div className="rcs-creative-name-readonly">
4152
- <CapHeading type="h4">
4153
- {formatMessage(messages.templateTypeLabel)}
4154
- </CapHeading>
4155
- <CapHeading type="h5" className="rcs-creative-name-value">
4156
- {TEMPLATE_TYPE_OPTIONS.find((option) => option.value === templateType)?.label || '-'}
4157
- </CapHeading>
4158
- </div>
4159
- ) : (
4160
- <>
4161
- {renderLabel('templateTypeLabel')}
4162
- <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
4163
- <CapRadioGroup
4164
- id="select-rcs-template-type"
4165
- options={TEMPLATE_TYPE_OPTIONS}
4166
- onChange={onTemplateTypeChange}
4167
- value={templateType}
4168
- disabled={!isFullMode}
4169
- />
4170
- </ConfigProvider>
4171
- </>
2297
+ <CapInput
2298
+ id="rcs_template_name_input"
2299
+ data-testid="template_name"
2300
+ onChange={onTemplateNameChange}
2301
+ errorMessage={templateNameError}
2302
+ placeholder={formatMessage(
2303
+ globalMessages.templateNamePlaceholder,
2304
+ )}
2305
+ value={templateName || ''}
2306
+ size="default"
2307
+ label={formatMessage(globalMessages.creativeNameLabel)}
2308
+ disabled={(isEditFlow || !isFullMode)}
2309
+ />
4172
2310
  )}
2311
+ {renderLabel('templateTypeLabel')}
2312
+ <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
2313
+ <CapRadioGroup
2314
+ id="select-rcs-template-type"
2315
+ options={TEMPLATE_TYPE_OPTIONS}
2316
+ onChange={onTemplateTypeChange}
2317
+ value={templateType}
2318
+ disabled={(isEditFlow || !isFullMode)}
2319
+ />
2320
+ </ConfigProvider>
4173
2321
 
4174
- {templateType === contentType.carousel ? (
4175
- renderCarouselSection()
4176
- ) : (
2322
+ {/* Show media only for rich_card or carousel */}
2323
+ {(templateType === contentType.rich_card || templateType === contentType.carousel) && (
4177
2324
  <>
4178
- {/* Show media only for rich_card */}
4179
- {templateType === contentType.rich_card && (
4180
- <>
4181
- {renderLabel('mediaLabel')}
4182
- <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
2325
+ {renderLabel('mediaLabel')}
2326
+ <ConfigProvider theme={{ components: { Radio: { radioSize: 20, dotSize: 8 } } }}>
4183
2327
  <CapRadioGroup
4184
- options={mediaRadioOptions || []}
4185
- value={templateMediaType}
4186
- onChange={onTemplateMediaTypeChange}
4187
- disabled={(isEditFlow || !isFullMode)}
4188
- className="rcs-radio"
4189
- />
4190
- </ConfigProvider>
2328
+ options={mediaRadioOptions || []}
2329
+ value={templateMediaType}
2330
+ onChange={onTemplateMediaTypeChange}
2331
+ disabled={(isEditFlow || !isFullMode)}
2332
+ className="rcs-radio"
2333
+ />
2334
+ </ConfigProvider>
4191
2335
  <div className="rcs-container-image">
4192
- {getMediaBasedComponent()}
4193
- </div>
4194
- </>
4195
- )}
4196
- {renderTextComponent()}
4197
- </>
4198
- )}
4199
- {(!isEditFlow || hasMeaningfulSmsFallbackShape(smsFallbackData)) && (
4200
- <>
4201
- <CapDivider className="rcs-fallback-section-divider" />
4202
- {renderFallBackSmsComponent()}
2336
+ {getMediaBasedComponent()}
2337
+ </div>
4203
2338
  </>
4204
2339
  )}
2340
+ {renderTextComponent()}
2341
+ <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2342
+ {renderFallBackSmsComponent()}
4205
2343
  <div className="rcs-scroll-div" />
4206
2344
  </CapColumn>
4207
2345
  <CapColumn span={8} offset={2}>
@@ -4212,8 +2350,7 @@ const onTitleAddVar = () => {
4212
2350
 
4213
2351
 
4214
2352
  <div className="rcs-footer">
4215
- {/* Full-mode create only: send-for-approval + disabled test/preview. Library mode uses Done below — onDoneCallback() is undefined when !isFullMode, so do not render this row (avoids a no-op primary button). */}
4216
- {!isEditFlow && isFullMode && (
2353
+ {!isEditFlow && (
4217
2354
  <>
4218
2355
  <div className="button-disabled-tooltip-wrapper">
4219
2356
  <CapButton
@@ -4221,9 +2358,7 @@ const onTitleAddVar = () => {
4221
2358
  disabled={isDisableDone()}
4222
2359
  className="rcs-done-btn"
4223
2360
  >
4224
- <FormattedMessage
4225
- {...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
4226
- />
2361
+ <FormattedMessage {...messages.sendForApprovalButtonLabel} />
4227
2362
  </CapButton>
4228
2363
  </div>
4229
2364
  <CapTooltip
@@ -4236,6 +2371,7 @@ const onTitleAddVar = () => {
4236
2371
  className="rcs-test-preview-btn"
4237
2372
  type="secondary"
4238
2373
  disabled={true}
2374
+ style={{ marginLeft: "8px" }}
4239
2375
  >
4240
2376
  <FormattedMessage {...creativesMessages.testAndPreview} />
4241
2377
  </CapButton>
@@ -4256,7 +2392,7 @@ const onTitleAddVar = () => {
4256
2392
  </div>
4257
2393
  </>
4258
2394
  )}
4259
- {isEditFlow && (isHostInfoBip || templateStatus === RCS_STATUSES.approved) && (
2395
+ {isEditFlow && templateStatus === RCS_STATUSES.approved && (
4260
2396
  <>
4261
2397
  <CapButton
4262
2398
  onClick={handleTestAndPreview}
@@ -4268,6 +2404,51 @@ const onTitleAddVar = () => {
4268
2404
  </>
4269
2405
  )}
4270
2406
  </div>
2407
+
2408
+
2409
+ {fallbackPreviewmode && (
2410
+ <CapSlideBox
2411
+ className="rcs-fallback-preview"
2412
+ show={fallbackPreviewmode}
2413
+ header={(
2414
+ <CapHeading type="h7" style={{ color: CAP_G01 }}>
2415
+ {formatMessage(messages.fallbackPreviewtitle)}
2416
+ </CapHeading>
2417
+ )}
2418
+ content={(
2419
+ <>
2420
+ <UnifiedPreview
2421
+ channel={RCS}
2422
+ content={{
2423
+ rcsPreviewContent: {
2424
+ rcsDesc: tempMsg,
2425
+ },
2426
+ }}
2427
+ device={ANDROID}
2428
+ showDeviceToggle={false}
2429
+ showHeader={false}
2430
+ formatMessage={formatMessage}
2431
+ />
2432
+ <CapHeading
2433
+ type="h3"
2434
+ style={{ textAlign: 'center', width: '100%' }}
2435
+ className="margin-t-16"
2436
+ >
2437
+ {formatMessage(messages.totalCharacters, {
2438
+ smsCount: Math.ceil(
2439
+ tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2440
+ ),
2441
+ number: tempMsg.length,
2442
+ })}
2443
+ </CapHeading>
2444
+ </>
2445
+ )}
2446
+ handleClose={() => {
2447
+ setFallbackPreviewmode(false);
2448
+ setDltPreviewData('');
2449
+ }}
2450
+ />
2451
+ )}
4271
2452
  </>
4272
2453
  );
4273
2454
  };
@@ -4276,57 +2457,23 @@ const onTitleAddVar = () => {
4276
2457
  <CapSpin spinning={loadingTags || spin}>
4277
2458
  {getMainContent()}
4278
2459
  </CapSpin>
4279
-
4280
- {showDltContainer && (() => {
4281
- const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
4282
- return (
4283
- <CapSlideBox
4284
- show={showDltContainer}
4285
- header={dltHeader}
4286
- content={dltContent}
4287
- handleClose={closeDltContainerHandler}
4288
- size="size-xl"
4289
- />
4290
- );
4291
- })()}
4292
-
4293
2460
  <TestAndPreviewSlidebox
4294
2461
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
4295
2462
  onClose={handleCloseTestAndPreview}
4296
- formData={testPreviewFormData}
4297
- content={testAndPreviewContent}
2463
+ formData={null} // RCS doesn't use formData structure like SMS
2464
+ content={getTemplateContent()}
4298
2465
  currentChannel={RCS}
4299
- orgUnitId={orgUnitId}
4300
- rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
4301
- smsFallbackContent={
4302
- smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
4303
- ? {
4304
- templateContent:
4305
- smsFallbackData.templateContent || smsFallbackData.content || '',
4306
- templateName: smsFallbackData.templateName || '',
4307
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
4308
- ? mergeRcsSmsFallbackVarMapLayers(
4309
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
4310
- smsFallbackData,
4311
- )
4312
- : mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
4313
- }
4314
- : null
4315
- }
4316
- smsRegister={smsRegister}
4317
2466
  />
4318
2467
  </>
4319
2468
  );
4320
2469
  };
4321
2470
 
4322
-
4323
2471
  const mapStateToProps = createStructuredSelector({
4324
2472
  rcsData: makeSelectRcs(),
4325
2473
  accountData: makeSelectAccount(),
4326
2474
  metaEntities: makeSelectMetaEntities(),
4327
2475
  loadingTags: isLoadingMetaEntities(),
4328
2476
  injectedTags: setInjectedTags(),
4329
- currentOrgDetails: selectCurrentOrgDetails(),
4330
2477
  });
4331
2478
 
4332
2479
  const mapDispatchToProps = (dispatch) => ({