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

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 (130) hide show
  1. package/constants/unified.js +0 -29
  2. package/package.json +1 -1
  3. package/services/tests/api.test.js +0 -13
  4. package/utils/commonUtils.js +1 -19
  5. package/v2Components/CapActionButton/constants.js +0 -7
  6. package/v2Components/CapActionButton/index.js +109 -167
  7. package/v2Components/CapActionButton/index.scss +6 -157
  8. package/v2Components/CapActionButton/messages.js +3 -19
  9. package/v2Components/CapActionButton/tests/index.test.js +17 -41
  10. package/v2Components/CapTagList/index.js +0 -10
  11. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  15. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  16. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  17. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  18. package/v2Components/CommonTestAndPreview/SendTestMessage.js +5 -10
  19. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js +15 -160
  20. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +76 -341
  21. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  22. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +0 -11
  23. package/v2Components/CommonTestAndPreview/constants.js +2 -38
  24. package/v2Components/CommonTestAndPreview/index.js +186 -676
  25. package/v2Components/CommonTestAndPreview/messages.js +3 -49
  26. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  27. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +284 -308
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  29. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  30. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  31. package/v2Components/CommonTestAndPreview/tests/PreviewSection.test.js +1 -8
  32. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +13 -34
  33. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/RcsPreviewContent.test.js +283 -281
  34. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  35. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -132
  36. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  37. package/v2Components/FormBuilder/index.js +10 -8
  38. package/v2Components/TemplatePreview/_templatePreview.scss +23 -33
  39. package/v2Components/TemplatePreview/index.js +28 -143
  40. package/v2Components/TemplatePreview/tests/index.test.js +0 -142
  41. package/v2Components/TestAndPreviewSlidebox/index.js +1 -13
  42. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  43. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  44. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  45. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  46. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  47. package/v2Containers/CreativesContainer/constants.js +0 -9
  48. package/v2Containers/CreativesContainer/index.js +103 -300
  49. package/v2Containers/CreativesContainer/index.scss +1 -51
  50. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  51. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  52. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  53. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  54. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +15 -20
  55. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  56. package/v2Containers/Email/reducer.js +12 -3
  57. package/v2Containers/Email/sagas.js +9 -4
  58. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +4 -0
  59. package/v2Containers/Email/tests/reducer.test.js +47 -0
  60. package/v2Containers/Email/tests/sagas.test.js +146 -6
  61. package/v2Containers/Rcs/constants.js +8 -119
  62. package/v2Containers/Rcs/index.js +811 -2383
  63. package/v2Containers/Rcs/index.scss +6 -276
  64. package/v2Containers/Rcs/messages.js +3 -38
  65. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +70073 -98018
  66. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  67. package/v2Containers/Rcs/tests/index.test.js +121 -152
  68. package/v2Containers/Rcs/tests/mockData.js +0 -38
  69. package/v2Containers/Rcs/tests/utils.test.js +30 -646
  70. package/v2Containers/Rcs/utils.js +11 -478
  71. package/v2Containers/Sms/Create/index.js +40 -100
  72. package/v2Containers/SmsTrai/Create/index.js +4 -9
  73. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  74. package/v2Containers/SmsTrai/Edit/index.js +130 -636
  75. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  76. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2296 -4249
  77. package/v2Containers/SmsWrapper/index.js +8 -37
  78. package/v2Containers/TagList/index.js +0 -6
  79. package/v2Containers/Templates/_templates.scss +2 -163
  80. package/v2Containers/Templates/actions.js +0 -11
  81. package/v2Containers/Templates/constants.js +0 -2
  82. package/v2Containers/Templates/index.js +54 -119
  83. package/v2Containers/Templates/sagas.js +12 -57
  84. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  85. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  86. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  87. package/v2Containers/TemplatesV2/index.js +23 -86
  88. package/v2Containers/Whatsapp/index.js +20 -3
  89. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -578
  90. package/utils/rcsPayloadUtils.js +0 -92
  91. package/utils/templateVarUtils.js +0 -201
  92. package/utils/tests/templateVarUtils.test.js +0 -204
  93. package/v2Components/CommonTestAndPreview/UnifiedPreview/RcsPreviewContent.js.rej +0 -18
  94. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  95. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  96. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  97. package/v2Components/SmsFallback/constants.js +0 -73
  98. package/v2Components/SmsFallback/index.js +0 -955
  99. package/v2Components/SmsFallback/index.scss +0 -265
  100. package/v2Components/SmsFallback/messages.js +0 -78
  101. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -118
  102. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  103. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  104. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  105. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  106. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -277
  107. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  108. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  109. package/v2Components/TemplatePreview/constants.js +0 -2
  110. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  111. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  112. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  113. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  114. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  115. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  116. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  117. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  118. package/v2Containers/Rcs/index.js.rej +0 -1336
  119. package/v2Containers/Rcs/index.scss.rej +0 -74
  120. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -225
  121. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap.rej +0 -128
  122. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  123. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  124. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  125. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  126. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  127. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  128. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  129. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  130. 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';
@@ -33,7 +34,6 @@ import CapError from '@capillarytech/cap-ui-library/CapError';
33
34
  import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
34
35
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
35
36
  import CapLink from '@capillarytech/cap-ui-library/CapLink';
36
- import CapTab from '@capillarytech/cap-ui-library/CapTab';
37
37
 
38
38
  import {
39
39
  CAP_G01,
@@ -49,30 +49,17 @@ import {
49
49
  import CapVideoUpload from '../../v2Components/CapVideoUpload';
50
50
  import * as globalActions from '../Cap/actions';
51
51
  import CapActionButton from '../../v2Components/CapActionButton';
52
- import TemplatePreview from '../../v2Components/TemplatePreview';
53
52
  import { makeSelectRcs, makeSelectAccount } from './selectors';
54
53
  import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
55
54
  import {
56
55
  isLoadingMetaEntities,
57
56
  makeSelectMetaEntities,
58
57
  setInjectedTags,
59
- selectCurrentOrgDetails,
60
58
  } from '../Cap/selectors';
61
59
  import * as RcsActions from './actions';
62
60
  import { isAiContentBotDisabled } from '../../utils/common';
63
61
  import * as TemplatesActions from '../Templates/actions';
64
62
  import './index.scss';
65
- import {
66
- normalizeLibraryLoadedTitleDesc,
67
- mergeRcsSmsFallBackContentFromDetails,
68
- mergeRcsSmsFallbackVarMapLayers,
69
- extractRegisteredSenderIdsFromSmsFallbackRecord,
70
- pickFirstSmsFallbackTemplateString,
71
- syncCardVarMappedSemanticsFromSlots,
72
- hasMeaningfulSmsFallbackShape,
73
- getLibrarySmsFallbackApiBaselineFromTemplateData,
74
- pickRcsCardVarMappedEntries,
75
- } from './rcsLibraryHydrationUtils';
76
63
  import {
77
64
  RCS,
78
65
  SMS,
@@ -89,7 +76,6 @@ import {
89
76
  RCS_IMG_SIZE,
90
77
  RCS_DLT_MODE,
91
78
  CTA,
92
- AI_CONTENT_BOT_DISABLED,
93
79
  RCS_STATUSES,
94
80
  TITLE_TEXT,
95
81
  MESSAGE_TEXT,
@@ -104,15 +90,9 @@ import {
104
90
  rcsVarTestRegex,
105
91
  RCS_IMAGE_DIMENSIONS,
106
92
  RCS_TEXT_MESSAGE_MAX_LENGTH,
107
- RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP,
108
93
  RCS_RICH_CARD_MAX_LENGTH,
109
94
  RCS_VIDEO_THUMBNAIL_DIMENSIONS,
110
- RCS_CAROUSEL_IMAGE_DIMENSIONS,
111
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS,
112
- RCS_CAROUSEL_IMG_SIZE,
113
- RCS_CAROUSEL_VIDEO_SIZE,
114
95
  MAX_BUTTONS,
115
- INITIAL_SUGGESTIONS,
116
96
  INITIAL_SUGGESTIONS_DATA_STOP,
117
97
  RCS_BUTTON_TYPES,
118
98
  titletype,
@@ -122,47 +102,25 @@ import {
122
102
  SMALL,
123
103
  MEDIUM,
124
104
  RICHCARD,
125
- HOST_INFOBIP,
126
- HOST_ICS,
127
- CAROUSEL_HEIGHT_OPTIONS,
128
- CAROUSEL_WIDTH_OPTIONS,
129
- STOP,
130
- RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS,
131
- RCS_NUMERIC_VAR_NAME_REGEX,
132
- RCS_NUMERIC_VAR_TOKEN_REGEX,
133
- RCS_TAG_AREA_FIELD_TITLE,
134
- RCS_TAG_AREA_FIELD_DESC,
135
105
  } from './constants';
136
106
  import globalMessages from '../Cap/messages';
137
107
  import messages from './messages';
138
108
  import creativesMessages from '../CreativesContainer/messages';
139
109
  import withCreatives from '../../hoc/withCreatives';
140
110
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
141
- import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
142
- import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
111
+ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
143
112
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
144
- import { splitTemplateVarString } from '../../utils/templateVarUtils';
145
113
  import CapImageUpload from '../../v2Components/CapImageUpload';
114
+ import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
146
115
  import Templates from '../Templates';
147
116
  import SmsTraiEdit from '../SmsTrai/Edit';
148
- import SmsFallback from '../../v2Components/SmsFallback';
149
- import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
150
117
  import TagList from '../TagList';
151
118
  import { validateTags } from '../../utils/tagValidations';
152
- import { isTraiDLTEnable } from '../../utils/common';
119
+ import { getCdnUrl } from '../../utils/cdnTransformation';
153
120
  import { isTagIncluded } from '../../utils/commonUtils';
154
121
  import injectReducer from '../../utils/injectReducer';
155
122
  import v2RcsReducer from './reducer';
156
- import {
157
- areAllRcsSmsFallbackVarSlotsFilled,
158
- buildRcsNumericMustachePlaceholderRegex,
159
- getTemplateStatusType,
160
- normalizeCardVarMapped,
161
- coalesceCardVarMappedToTemplate,
162
- getRcsSemanticVarNamesSpanningTitleAndDesc,
163
- resolveCardVarMappedSlotValue,
164
- sanitizeCardVarMappedValue,
165
- } from './utils';
123
+ import { getTemplateStatusType } from './utils';
166
124
 
167
125
 
168
126
  const { Group: CapCheckboxGroup } = CapCheckbox;
@@ -179,18 +137,19 @@ export const Rcs = (props) => {
179
137
  templatesActions,
180
138
  globalActions,
181
139
  location,
140
+ handleClose,
182
141
  getDefaultTags,
183
142
  supportedTags,
184
143
  metaEntities,
185
144
  injectedTags,
186
145
  loadingTags,
187
146
  getFormData,
147
+ isDltEnabled,
188
148
  smsRegister,
189
- orgUnitId,
190
149
  selectedOfferDetails,
191
150
  eventContextTags,
151
+ waitEventContextTags,
192
152
  accountData = {},
193
- currentOrgDetails,
194
153
  // TestAndPreviewSlidebox props
195
154
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
196
155
  handleTestAndPreview: propsHandleTestAndPreview,
@@ -199,25 +158,7 @@ export const Rcs = (props) => {
199
158
  const { formatMessage } = intl;
200
159
  const { TextArea } = CapInput;
201
160
  const { CapCustomCardList } = CapCustomCard;
202
-
203
- // Defensive: React cannot render plain objects as children (crashes with
204
- // "Objects are not valid as a React child"). Some campaigns (!isFullMode) flows
205
- // can accidentally set an error state to an object (e.g. `{}`).
206
- const normalizeErrorMessage = (err) => {
207
- if (!err) return '';
208
- if (React.isValidElement(err)) return err;
209
- if (typeof err === 'string') return err;
210
- if (typeof err === 'number') return String(err);
211
- if (typeof err === 'object' && typeof err.message === 'string') return err.message;
212
- try {
213
- return JSON.stringify(err);
214
- } catch (e) {
215
- return String(err);
216
- }
217
- };
218
-
219
161
  const [isEditFlow, setEditFlow] = useState(false);
220
- const isEditLike = isEditFlow || !isFullMode;
221
162
  const [tags, updateTags] = useState([]);
222
163
  const [spin, setSpin] = useState(false);
223
164
  //template
@@ -226,21 +167,33 @@ export const Rcs = (props) => {
226
167
  const [templateMediaType, setTemplateMediaType] = useState(
227
168
  RCS_MEDIA_TYPES.NONE,
228
169
  );
170
+ const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
229
171
  const [templateTitle, setTemplateTitle] = useState('');
230
172
  const [templateDesc, setTemplateDesc] = useState('');
231
173
  const [templateDescError, setTemplateDescError] = useState(false);
232
174
  const [templateStatus, setTemplateStatus] = useState('');
175
+ const [templateDate, setTemplateDate] = useState('');
176
+ //fallback
177
+ const [fallbackMessage, setFallbackMessage] = useState('');
178
+ const [fallbackMessageError, setFallbackMessageError] = useState(false);
233
179
  //fallback dlt
234
180
  const [showDltContainer, setShowDltContainer] = useState(false);
235
181
  const [dltMode, setDltMode] = useState('');
236
182
  const [dltEditData, setDltEditData] = useState({});
237
- /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
238
- const [smsFallbackData, setSmsFallbackData] = useState(undefined);
183
+ const [showDltCard, setShowDltCard] = useState(false);
184
+ const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
185
+ const [dltPreviewData, setDltPreviewData] = useState('');
239
186
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
240
- const buttonType = RCS_BUTTON_TYPES.NONE;
187
+ const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
241
188
  const [suggestionError, setSuggestionError] = useState(true);
242
- const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
243
189
  const [templateType, setTemplateType] = useState('text_message');
190
+ const [templateHeader, setTemplateHeader] = useState('');
191
+ const [templateMessage, setTemplateMessage] = useState('');
192
+ const [templateHeaderError, setTemplateHeaderError] = useState('');
193
+ const [templateMessageError, setTemplateMessageError] = useState('');
194
+ const validVarRegex = /\{\{(\d+)\}\}/g;
195
+ const [updatedTitleData, setUpdatedTitleData] = useState([]);
196
+ const [updatedDescData, setUpdatedDescData] = useState([]);
244
197
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
245
198
  const [descVarMappedData, setDescVarMappedData] = useState({});
246
199
  const [titleTextAreaId, setTitleTextAreaId] = useState();
@@ -251,58 +204,75 @@ export const Rcs = (props) => {
251
204
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
252
205
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
253
206
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
254
- // Carousel (UI-only) state
255
- const [selectedCarousel, setSelectedCarousel] = useState('');
256
- const [selectedCarouselHeight, setSelectedCarouselHeight] = useState(MEDIUM);
257
- const [selectedCarouselWidth, setSelectedCarouselWidth] = useState(SMALL);
258
- const [carouselData, setCarouselData] = useState([]);
259
- const [carouselErrors, setCarouselErrors] = useState([]); // [{ title: string|false, description: string|false }]
260
- const [activeCarouselIndex, setActiveCarouselIndex] = useState('0');
261
- const [carouselResetNonce, setCarouselResetNonce] = useState(0);
262
- const [carouselFocusedVarId, setCarouselFocusedVarId] = useState('');
263
- const [imageError, setImageError] = useState(null);
207
+ const [imageError, setImageError] = useState(null);
264
208
  const [templateTitleError, setTemplateTitleError] = useState(false);
265
209
  const [cardVarMapped, setCardVarMapped] = useState({});
266
- /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
267
- const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
268
- const lastHydratedRcsCardVarSignatureRef = useRef(null);
269
-
270
- /**
271
- * Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
272
- * without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
273
- * fallback card / content stopped appearing until re-selected.
274
- */
275
- const rcsHydrationDetails = useMemo(
276
- () => (isFullMode ? rcsData?.templateDetails : templateData),
277
- [isFullMode, rcsData?.templateDetails, templateData],
278
- );
279
-
280
- /** 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'). */
281
- const lastTagSchemaQueryKeyRef = useRef(null);
282
- /**
283
- * Library: parent often passes a new `templateData` object reference every render. Re-applying the same
284
- * SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
285
- */
286
- const lastSmsFallbackHydrationKeyRef = useRef(null);
287
-
288
- const fetchTagSchemaIfNewQuery = useCallback(
289
- (query) => {
290
- const key = JSON.stringify(query);
291
- if (lastTagSchemaQueryKeyRef.current === key) {
292
- return;
293
- }
294
- lastTagSchemaQueryKeyRef.current = key;
295
- globalActions.fetchSchemaForEntity(query);
296
- },
297
- [globalActions],
298
- );
299
210
 
300
211
  // TestAndPreviewSlidebox state
301
212
  const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
213
+ const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
214
+
215
+ const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
216
+
217
+ // Get template content for TestAndPreviewSlidebox
218
+ // Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
219
+ // getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
220
+ // renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
221
+ // Note: templateHeader and templateMessage are defined but NOT used in the component
222
+ const getTemplateContent = useCallback(() => {
223
+ const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
224
+ const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
225
+ const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
226
+
227
+ // Build media preview object (same pattern as getRcsPreview)
228
+ const mediaPreview = {};
229
+ if (isMediaTypeImage && rcsImageSrc) {
230
+ mediaPreview.rcsImageSrc = rcsImageSrc;
231
+ }
232
+ if (isMediaTypeVideo && !isMediaTypeText) {
233
+ // For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
234
+ if (rcsThumbnailSrc) {
235
+ mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
236
+ } else if (rcsVideoSrc?.videoSrc) {
237
+ mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
238
+ }
239
+ // Also include thumbnailSrc separately if available
240
+ if (rcsThumbnailSrc) {
241
+ mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
242
+ }
243
+ }
244
+
245
+ // Build content object
246
+ // Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
247
+ // templateTitle is used for rich_card/carousel title, empty for text_message
248
+ // templateDesc is used for ALL types (text message body or rich card description)
249
+ // For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
250
+ const contentObj = {
251
+ // Map templateTitle to templateHeader and templateDesc to templateMessage
252
+ templateHeader: templateTitle,
253
+ templateMessage: templateDesc,
254
+ ...mediaPreview,
255
+ ...(suggestions.length > 0 && {
256
+ suggestions: suggestions,
257
+ }),
258
+ };
259
+
260
+ return contentObj;
261
+ }, [
262
+ templateMediaType,
263
+ templateTitle,
264
+ templateDesc,
265
+ rcsImageSrc,
266
+ rcsVideoSrc,
267
+ rcsThumbnailSrc,
268
+ suggestions,
269
+ selectedDimension,
270
+ ]);
302
271
 
303
272
  // Handle Test and Preview button click
304
273
  const handleTestAndPreview = useCallback(() => {
305
274
  setShowTestAndPreviewSlidebox(true);
275
+ setIsTestAndPreviewMode(true);
306
276
  if (propsHandleTestAndPreview) {
307
277
  propsHandleTestAndPreview();
308
278
  }
@@ -326,721 +296,31 @@ export const Rcs = (props) => {
326
296
  // For video
327
297
  return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
328
298
  };
329
- /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
330
- const handleSmsFallbackEditorStateChange = useCallback((patch) => {
331
- setSmsFallbackData((prev) => {
332
- if (!patch || typeof patch !== 'object') return prev;
333
- // Bail out when no template has been selected yet — SmsTraiEdit fires
334
- // onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
335
- // which would create a non-null smsFallbackData from nothing and cause
336
- // the card to appear as "Untitled creative" before any save.
337
- if (!prev) return prev;
338
- return { ...prev, ...patch };
339
- });
340
- }, []);
341
299
 
342
- /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
343
300
  const [accountId, setAccountId] = useState('');
344
- /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
345
- const [wecrmAccountId, setWecrmAccountId] = useState('');
346
301
  const [accessToken, setAccessToken] = useState('');
347
302
  const [hostName, setHostName] = useState('');
348
303
  const [accountName, setAccountName] = useState('');
349
- const isHostInfoBip = hostName === HOST_INFOBIP;
350
- const isHostIcs = hostName === HOST_ICS;
351
-
352
- useEffect(() => {
353
- setSuggestions(isHostIcs ? INITIAL_SUGGESTIONS_DATA_STOP : []);
354
- }, [isHostIcs]);
304
+ const [rcsAccount, setRcsAccount] = useState('');
355
305
  useEffect(() => {
356
306
  const accountObj = accountData.selectedRcsAccount || {};
357
307
  if (!isEmpty(accountObj)) {
358
308
  const {
359
- id: wecrmId,
360
309
  sourceAccountIdentifier = '',
361
310
  configs = {},
362
311
  } = accountObj;
312
+
363
313
  setAccountId(sourceAccountIdentifier);
364
- setWecrmAccountId(
365
- wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
366
- );
367
314
  setAccessToken(configs.accessToken || '');
368
315
  setHostName(accountObj.hostName || '');
369
316
  setAccountName(accountObj.name || '');
370
- } else {
371
- setAccountId('');
372
- setWecrmAccountId('');
373
- setAccessToken('');
374
- setHostName('');
375
- setAccountName('');
317
+ setRcsAccount(accountObj.id || '');
376
318
  }
377
319
  }, [accountData.selectedRcsAccount]);
378
320
 
379
321
  const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
380
322
  const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
381
323
  const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
382
- const isCarouselType = templateType === contentType.carousel;
383
-
384
- const MAX_RCS_CAROUSEL_ALLOWED = 10;
385
- // Uploads for RCS are stored in redux under dynamic keys `uploadedAssetData${index}`.
386
- // Carousel needs per-card indices; otherwise all cards "restore" the last uploaded asset
387
- // and show the same media/thumbnail.
388
- const RCS_CAROUSEL_ASSET_INDEX_BASE = 10; // keep away from standalone indices 0 (media) and 1 (thumbnail)
389
- const getCarouselImageAssetIndex = (cardIndex) =>
390
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3);
391
- const getCarouselVideoAssetIndex = (cardIndex) =>
392
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 1;
393
- const getCarouselThumbnailAssetIndex = (cardIndex) =>
394
- RCS_CAROUSEL_ASSET_INDEX_BASE + (cardIndex * 3) + 2;
395
- const isThumbnailAssetIndex = (index) => {
396
- if (index === 1) return true; // standalone thumbnail
397
- if (index >= RCS_CAROUSEL_ASSET_INDEX_BASE) {
398
- return ((index - RCS_CAROUSEL_ASSET_INDEX_BASE) % 3) === 2; // carousel thumbnail slot
399
- }
400
- return false;
401
- };
402
-
403
- // Carousel dimension key: `${HEIGHT}_${WIDTH}` (e.g., SHORT_SMALL)
404
- const getCarouselDimensionKey = () => `${selectedCarouselHeight}_${selectedCarouselWidth}`;
405
-
406
- const clearCarouselCardMedia = (cardIndex, { clearImage = true, clearVideo = true, clearThumb = true } = {}) => {
407
- setCarouselData((prev = []) => {
408
- const updated = cloneDeep(prev);
409
- if (!updated?.[cardIndex]) return updated;
410
- if (clearImage) updated[cardIndex].imageSrc = '';
411
- if (clearVideo) updated[cardIndex].videoAsset = {};
412
- if (clearThumb) updated[cardIndex].thumbnailSrc = '';
413
- return updated;
414
- });
415
-
416
- if (clearImage) actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIndex));
417
- if (clearVideo) actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIndex));
418
- if (clearThumb) actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIndex));
419
- };
420
-
421
- const resetCarouselMediaForAllCards = () => {
422
- setCarouselData((prev = []) => {
423
- const updated = cloneDeep(prev);
424
- updated.forEach((card) => {
425
- if (!card) return;
426
- card.imageSrc = '';
427
- card.videoAsset = {};
428
- card.thumbnailSrc = '';
429
- });
430
- return updated;
431
- });
432
- (carouselData || []).forEach((_, idx) => {
433
- actions.clearRcsMediaAsset(getCarouselImageAssetIndex(idx));
434
- actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(idx));
435
- actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(idx));
436
- });
437
- // Force tab panes to remount after global reset (some tab implementations cache inactive panes)
438
- setCarouselResetNonce((n) => n + 1);
439
- };
440
-
441
- const RCS_CAROUSEL_INITIAL_CARD = {
442
- title: '',
443
- description: '',
444
- mediaType: RCS_MEDIA_TYPES.IMAGE, // per-card
445
- imageSrc: '',
446
- videoAsset: {}, // CapVideoUpload object shape
447
- thumbnailSrc: '',
448
- suggestions: [],
449
- };
450
-
451
- const RCS_CAROUSEL_INITIAL_FIRST_CARD = {
452
- ...RCS_CAROUSEL_INITIAL_CARD,
453
- suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
454
- };
455
-
456
- const ensureFirstCardDefaultPhoneSuggestions = (cards) => {
457
- const next = cloneDeep(cards || []);
458
- if (next.length === 0) return next;
459
- const s = next[0].suggestions;
460
- if (!Array.isArray(s) || s.length === 0) {
461
- next[0] = {
462
- ...next[0],
463
- suggestions: cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS),
464
- };
465
- }
466
- return next;
467
- };
468
-
469
- // Always use functional updates: image upload completes in CapImageUpload's useEffect and calls
470
- // updateImageSrc → this handler. If we cloned carouselData from render, we'd overwrite title/body/
471
- // suggestions typed since the last commit (stale closure).
472
- const handleCarouselValueChange = (carouselIndex, fields) => {
473
- setCarouselData((prev = []) => {
474
- const updated = cloneDeep(prev);
475
- if (!updated[carouselIndex]) return prev;
476
- fields.forEach(({ fieldName, value }) => {
477
- updated[carouselIndex][fieldName] = value;
478
- });
479
- return updated;
480
- });
481
- };
482
-
483
- const updateCarouselErrors = (carouselIndex, patch) => {
484
- setCarouselErrors((prev = []) => {
485
- const next = Array.isArray(prev) ? [...prev] : [];
486
- next[carouselIndex] = { ...(next[carouselIndex] || {}), ...patch };
487
- return next;
488
- });
489
- };
490
-
491
- const deleteCarouselCard = (index) => {
492
- let nextIdx = 0;
493
- flushSync(() => {
494
- setCarouselData((prev = []) => {
495
- const updated = cloneDeep(prev);
496
- if (index < 0 || index >= updated.length) return updated;
497
- updated.splice(index, 1);
498
- nextIdx = Math.max(0, Math.min(index - 1, updated.length - 1));
499
- return ensureFirstCardDefaultPhoneSuggestions(updated);
500
- });
501
- });
502
- setCarouselErrors((prev = []) => {
503
- const next = Array.isArray(prev) ? [...prev] : [];
504
- next.splice(index, 1);
505
- return next;
506
- });
507
- setActiveCarouselIndex(`${nextIdx}`);
508
- };
509
-
510
- const carouselButtonTextHasForbiddenChars = (value) => {
511
- if (!value) return false;
512
- if (value.includes('[') || value.includes(']')) return true;
513
- const withoutValidVariables = value.replace(/\{\{[^}]*\}\}/g, '');
514
- if (withoutValidVariables.includes('{') || withoutValidVariables.includes('}')) return true;
515
- return false;
516
- };
517
-
518
- const isCompleteSavedCarouselSuggestion = (s) => {
519
- if (!s || !s.isSaved) return false;
520
- const text = (s.text || '').trim();
521
- if (!text || !isValidText(text) || carouselButtonTextHasForbiddenChars(text)) return false;
522
- if (s.type === RCS_BUTTON_TYPES.PHONE_NUMBER) {
523
- return String(s.phoneNumber || '').length >= 5;
524
- }
525
- if (s.type === RCS_BUTTON_TYPES.CTA) {
526
- const url = String(s.url || '').trim();
527
- if (!url || url.length > URL_MAX_LENGTH) return false;
528
- const subtype = s.urlType || RCS_CTA_URL_TYPE.STATIC;
529
- if (subtype === RCS_CTA_URL_TYPE.DYNAMIC) {
530
- return true;
531
- }
532
- if (!isUrl(url)) return false;
533
- const varMatches = url.match(invalidVarRegex);
534
- return !(varMatches && varMatches.length > 0);
535
- }
536
- if (s.type === RCS_BUTTON_TYPES.QUICK_REPLY) {
537
- return true;
538
- }
539
- return false;
540
- };
541
-
542
- const isCarouselCardValid = (card, cardIndex) => {
543
- if (!card) return false;
544
- if (!card.title || !card.title.trim()) return false;
545
- if ((card.title || '').length > TEMPLATE_TITLE_MAX_LENGTH) return false;
546
- if (!card.description || !card.description.trim()) return false;
547
- if ((card.description || '').length > RCS_RICH_CARD_MAX_LENGTH) return false;
548
- let mediaOk = false;
549
- if (card.mediaType === RCS_MEDIA_TYPES.IMAGE) {
550
- mediaOk = !!(card.imageSrc && String(card.imageSrc).trim());
551
- } else if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
552
- const hasVideo = !!(card.videoAsset && card.videoAsset.videoSrc && String(card.videoAsset.videoSrc).trim());
553
- const hasThumb = !!(card.thumbnailSrc && String(card.thumbnailSrc).trim());
554
- mediaOk = hasVideo && hasThumb;
555
- } else {
556
- return false;
557
- }
558
- if (!mediaOk) return false;
559
- if (cardIndex === 0) {
560
- const sugg = Array.isArray(card.suggestions) ? card.suggestions : [];
561
- if (!sugg.some(isCompleteSavedCarouselSuggestion)) return false;
562
- }
563
- return true;
564
- };
565
-
566
- const checkDisableAddCarouselButton = () => {
567
- const idx = parseInt(activeCarouselIndex, 10);
568
- const activeCard = carouselData?.[idx];
569
- return !isCarouselCardValid(activeCard, idx);
570
- };
571
-
572
- const addCarouselCard = () => {
573
- let newIndex = 0;
574
- flushSync(() => {
575
- setCarouselData((prev = []) => {
576
- const updated = cloneDeep(prev);
577
- updated.push(cloneDeep(RCS_CAROUSEL_INITIAL_CARD));
578
- newIndex = updated.length - 1;
579
- return updated;
580
- });
581
- });
582
- setCarouselErrors((prev = []) => ([...(Array.isArray(prev) ? prev : []), {}]));
583
- setActiveCarouselIndex(`${newIndex}`);
584
- };
585
-
586
- // Initialize carousel data when switching to carousel type
587
- useEffect(() => {
588
- if (!isCarouselType) return;
589
- if (!carouselData || carouselData.length === 0) {
590
- setCarouselData([cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)]);
591
- setCarouselErrors([{}]);
592
- setActiveCarouselIndex('0');
593
- }
594
- }, [isCarouselType]);
595
-
596
- // keep derived carousel key in sync
597
- useEffect(() => {
598
- if (!isCarouselType) return;
599
- if (!selectedCarouselHeight || !selectedCarouselWidth) return;
600
- // Required format: HEIGHT_WIDTH
601
- setSelectedCarousel(`${selectedCarouselHeight}_${selectedCarouselWidth}`);
602
- }, [isCarouselType, selectedCarouselHeight, selectedCarouselWidth]);
603
-
604
- const renderCarouselDimensionSelection = () => {
605
- if (!isCarouselType) return null;
606
- return (
607
- <CapRow className="rcs-carousel-dimension-section">
608
- <CapRow gutter={16} className="rcs-carousel-dimension-row">
609
- <CapColumn span={12}>
610
- <CapHeading type="h4" className="rcs-carousel-dimension-label">Card height</CapHeading>
611
- <CapSelect
612
- id="rcs-carousel-height-select"
613
- value={selectedCarouselHeight}
614
- onChange={(val) => {
615
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
616
- resetCarouselMediaForAllCards();
617
- setSelectedCarouselHeight(val);
618
- }}
619
- options={CAROUSEL_HEIGHT_OPTIONS}
620
- disabled={isEditFlow || !isFullMode}
621
- />
622
- </CapColumn>
623
- <CapColumn span={12}>
624
- <CapHeading type="h4" className="rcs-carousel-dimension-label">Card width</CapHeading>
625
- <CapSelect
626
- id="rcs-carousel-width-select"
627
- value={selectedCarouselWidth}
628
- onChange={(val) => {
629
- // Like rich-card dimension changes: clear media so user re-uploads matching new dimensions.
630
- resetCarouselMediaForAllCards();
631
- setSelectedCarouselWidth(val);
632
- }}
633
- options={CAROUSEL_WIDTH_OPTIONS}
634
- disabled={isEditFlow || !isFullMode}
635
- />
636
- </CapColumn>
637
- </CapRow>
638
- {!!selectedCarousel && (
639
- <CapLabel type="label3" className="rcs-carousel-selected-dimension">
640
- Selected: {selectedCarousel}
641
- </CapLabel>
642
- )}
643
- </CapRow>
644
- );
645
- };
646
-
647
- // Reuse rich-card buttons UI per carousel card
648
- const renderButtonComponentForCarouselCard = (cardIndex) => {
649
- const card = carouselData?.[cardIndex] || {};
650
- const suggestionsForCard = card.suggestions || [];
651
- return (
652
- <>
653
- <CapHeader
654
- className="rcs-button-cta"
655
- title={(
656
- <CapRow type="flex">
657
- <CapHeading type="h4">
658
- {formatMessage(messages.btnLabel)}
659
- </CapHeading>
660
- </CapRow>
661
- )}
662
- description={(
663
- <CapLabel type="label3">{formatMessage(messages.btnDesc)}</CapLabel>
664
- )}
665
- />
666
- <CapActionButton
667
- buttonType={RCS_BUTTON_TYPES.NONE}
668
- updateButtonChange={(data, btnIndex) => {
669
- // Match existing behavior: allow CapActionButton to manage save gating.
670
- const updated = cloneDeep(suggestionsForCard);
671
- if (btnIndex === MAX_BUTTONS) {
672
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: data }]);
673
- return;
674
- }
675
- updated[btnIndex] = data;
676
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
677
- }}
678
- deleteButtonHandler={(btnIndex) => {
679
- if (cardIndex === 0 && btnIndex === 0) {
680
- return;
681
- }
682
- const savedCount = (suggestionsForCard || []).filter((x) => x && x.isSaved).length;
683
- const target = (suggestionsForCard || []).find((s) => s && s.index === btnIndex);
684
- if (cardIndex === 0 && target?.isSaved && savedCount <= 1) {
685
- return;
686
- }
687
- const updated = cloneDeep(suggestionsForCard)
688
- .filter((i) => i.index !== btnIndex)
689
- .map((i, idx) => ({ ...i, index: idx }));
690
- handleCarouselValueChange(cardIndex, [{ fieldName: 'suggestions', value: updated }]);
691
- }}
692
- suggestions={suggestionsForCard}
693
- isEditFlow={isEditFlow}
694
- isFullMode={isFullMode}
695
- maxButtons={MAX_BUTTONS}
696
- host={hostName}
697
- minSavedSuggestions={cardIndex === 0 ? 1 : 0}
698
- hideDeleteSuggestionIndexes={cardIndex === 0 ? [0] : []}
699
- />
700
- </>
701
- );
702
- };
703
-
704
- const renderCarouselCardMedia = (cardIndex) => {
705
- const card = carouselData?.[cardIndex] || {};
706
- const dimKey = getCarouselDimensionKey();
707
-
708
- if (card.mediaType === RCS_MEDIA_TYPES.VIDEO) {
709
- return (
710
- <>
711
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Video</CapHeading>
712
- <CapVideoUpload
713
- index={getCarouselVideoAssetIndex(cardIndex)}
714
- allowedExtensionsRegex={ALLOWED_EXTENSIONS_VIDEO_REGEX}
715
- videoSize={RCS_CAROUSEL_VIDEO_SIZE}
716
- isFullMode={isFullMode}
717
- uploadAsset={uploadRcsVideo}
718
- uploadedAssetList={card.videoAsset || {}}
719
- onVideoUploadUpdateAssestList={(_, val) => {
720
- handleCarouselValueChange(cardIndex, [{ fieldName: 'videoAsset', value: val }]);
721
- }}
722
- videoData={rcsData}
723
- className="cap-custom-video-upload"
724
- formClassName={"rcs-video-upload"}
725
- channel={RCS}
726
- errorMessage={formatMessage(messages.videoErrorMessage)}
727
- showVideoNameAndDuration={false}
728
- showReUploadButton={!isEditFlow && isFullMode}
729
- channelSpecificStyle={!isFullMode}
730
- />
731
-
732
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
733
- <CapImageUpload
734
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
735
- imgWidth={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.width}
736
- imgHeight={RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS?.[dimKey]?.height}
737
- imgSize={RCS_THUMBNAIL_MAX_SIZE}
738
- uploadAsset={uploadRcsImage}
739
- isFullMode={isFullMode}
740
- imageSrc={card.thumbnailSrc}
741
- updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: val }])}
742
- updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'thumbnailSrc', value: '' }])}
743
- minImgSize={RCS_THUMBNAIL_MIN_SIZE}
744
- index={getCarouselThumbnailAssetIndex(cardIndex)}
745
- className="cap-custom-image-upload"
746
- key={`rcs-carousel-thumb-${cardIndex}-${dimKey}`}
747
- imageData={rcsData}
748
- channel={RCS}
749
- channelSpecificStyle={!isFullMode}
750
- skipDimensionValidation={true}
751
- showReUploadButton={!isEditFlow && isFullMode}
752
- disabled={isEditFlow || !isFullMode}
753
- />
754
- </>
755
- );
756
- }
757
-
758
- // Default: IMAGE
759
- return (
760
- <>
761
- <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Image</CapHeading>
762
- <CapImageUpload
763
- allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
764
- imgWidth={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.width}
765
- imgHeight={RCS_CAROUSEL_IMAGE_DIMENSIONS?.[dimKey]?.height}
766
- imgSize={RCS_CAROUSEL_IMG_SIZE}
767
- uploadAsset={uploadRcsImage}
768
- isFullMode={isFullMode}
769
- imageSrc={card.imageSrc}
770
- updateImageSrc={(val) => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: val }])}
771
- updateOnReUpload={() => handleCarouselValueChange(cardIndex, [{ fieldName: 'imageSrc', value: '' }])}
772
- index={getCarouselImageAssetIndex(cardIndex)}
773
- className="cap-custom-image-upload"
774
- key={`rcs-carousel-image-${cardIndex}-${dimKey}`}
775
- imageData={rcsData}
776
- channel={RCS}
777
- channelSpecificStyle={!isFullMode}
778
- skipDimensionValidation={true}
779
- showReUploadButton={!isEditFlow && isFullMode}
780
- disabled={isEditFlow || !isFullMode}
781
- />
782
- </>
783
- );
784
- };
785
-
786
- const renderCarouselCardButtons = (cardIndex) => {
787
- return renderButtonComponentForCarouselCard(cardIndex);
788
- };
789
-
790
- const getCarouselTabPanes = () => {
791
- return (carouselData || []).map((card, index) => {
792
- return {
793
- key: index,
794
- tab: index + 1,
795
- content: (
796
- <CapCard
797
- title={`Card ${index + 1}`}
798
- extra={
799
- !isEditFlow &&
800
- (carouselData.length === 1 ? (
801
- <CapTooltip title={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}>
802
- <span className="button-disabled-tooltip-wrapper rcs-carousel-delete-tooltip-wrap">
803
- <CapButton
804
- className="rcs-carousel-card-delete"
805
- type="flat"
806
- onClick={() => deleteCarouselCard(index)}
807
- disabled
808
- aria-label={formatMessage(messages.rcsCarouselMinCardDeleteTooltip)}
809
- >
810
- <CapIcon type="delete" />
811
- </CapButton>
812
- </span>
813
- </CapTooltip>
814
- ) : (
815
- <CapButton
816
- className="rcs-carousel-card-delete"
817
- type="flat"
818
- onClick={() => deleteCarouselCard(index)}
819
- aria-label={formatMessage(globalMessages.delete)}
820
- >
821
- <CapIcon type="delete" />
822
- </CapButton>
823
- ))
824
- }
825
- className="rcs-carousel-card"
826
- >
827
- {/* Media selection should be at top of card */}
828
- <CapRow className="rcs-carousel-media-selection">
829
- <CapColumn className="rcs-carousel-media-selection-heading">
830
- <CapHeading type="h4">{formatMessage(messages.mediaTypeLabel)}</CapHeading>
831
- </CapColumn>
832
- <CapColumn>
833
- <CapRadioGroup
834
- id={`rcs-carousel-media-radio-${index}`}
835
- options={mediaRadioOptions}
836
- value={card.mediaType}
837
- onChange={({ target: { value } }) => {
838
- // Reset media fields when switching type
839
- if (value === RCS_MEDIA_TYPES.IMAGE) {
840
- // Switching to IMAGE: clear video + thumbnail uploads so they don't auto-restore.
841
- clearCarouselCardMedia(index, { clearImage: false, clearVideo: true, clearThumb: true });
842
- handleCarouselValueChange(index, [
843
- { fieldName: 'mediaType', value },
844
- { fieldName: 'videoAsset', value: {} },
845
- { fieldName: 'thumbnailSrc', value: '' },
846
- ]);
847
- } else {
848
- // Switching to VIDEO: clear image upload so it doesn't auto-restore.
849
- clearCarouselCardMedia(index, { clearImage: true, clearVideo: false, clearThumb: false });
850
- handleCarouselValueChange(index, [
851
- { fieldName: 'mediaType', value },
852
- { fieldName: 'imageSrc', value: '' },
853
- ]);
854
- }
855
- }}
856
- disabled={isEditFlow || !isFullMode}
857
- className="rcs-radio"
858
- />
859
- </CapColumn>
860
- </CapRow>
861
-
862
- <CapRow className="rcs-carousel-media-upload">
863
- {renderCarouselCardMedia(index)}
864
- </CapRow>
865
-
866
- {/* Title after media */}
867
- <CapRow className="rcs-carousel-card-row">
868
- <CapHeader
869
- className="rcs-template-title-label"
870
- title={<CapHeading type="h4">Card title</CapHeading>}
871
- suffix={
872
- (isEditFlow || !isFullMode) ? (
873
- <TagList
874
- label={formatMessage(globalMessages.addLabels)}
875
- onTagSelect={onCarouselTagSelect}
876
- location={location}
877
- tags={getRcsTags()}
878
- onContextChange={handleOnTagsContextChange}
879
- injectedTags={injectedTags || {}}
880
- selectedOfferDetails={selectedOfferDetails}
881
- />
882
- ) : (
883
- <CapButton
884
- data-testid={`rcs-carousel-title-add-var-${index}`}
885
- type="flat"
886
- isAddBtn
887
- onClick={() => appendVarToCarouselField(index, 'title')}
888
- disabled={!!carouselErrors?.[index]?.title}
889
- >
890
- {formatMessage(messages.addVar)}
891
- </CapButton>
892
- )
893
- }
894
- />
895
- <CapRow className="rcs_text_area_wrapper">
896
- {(isEditFlow || !isFullMode) ? (
897
- renderCarouselEditMessage(card.title || '')
898
- ) : (
899
- <CapInput
900
- value={card.title || ''}
901
- placeholder={formatMessage(messages.templateTitlePlaceholder)}
902
- onChange={({ target: { value } }) => {
903
- let error = false;
904
- if (value?.length > TEMPLATE_TITLE_MAX_LENGTH) {
905
- error = formatMessage(messages.templateHeaderLengthError);
906
- } else {
907
- error = variableErrorHandling(value);
908
- }
909
- updateCarouselErrors(index, { title: error });
910
- handleCarouselValueChange(index, [{ fieldName: 'title', value }]);
911
- }}
912
- disabled={isEditFlow || !isFullMode}
913
- errorMessage={carouselErrors?.[index]?.title}
914
- />
915
- )}
916
- </CapRow>
917
- </CapRow>
918
- {!isEditFlow && (
919
- <CapRow className="rcs-carousel-character-count-row">
920
- {renderCarouselCharacterCount(
921
- getCarouselTitleCharacterCount(index),
922
- getTitleMaxLength(),
923
- )}
924
- </CapRow>
925
- )}
926
-
927
- {/* Description after title */}
928
- <CapRow className="rcs-carousel-card-row">
929
- <CapHeader
930
- title={<CapHeading type="h4">Card body text</CapHeading>}
931
- suffix={
932
- (isEditFlow || !isFullMode) ? (
933
- <TagList
934
- label={formatMessage(globalMessages.addLabels)}
935
- onTagSelect={onCarouselTagSelect}
936
- location={location}
937
- tags={getRcsTags()}
938
- onContextChange={handleOnTagsContextChange}
939
- injectedTags={injectedTags || {}}
940
- selectedOfferDetails={selectedOfferDetails}
941
- />
942
- ) : (
943
- <CapButton
944
- data-testid={`rcs-carousel-desc-add-var-${index}`}
945
- type="flat"
946
- isAddBtn
947
- onClick={() => appendVarToCarouselField(index, 'description')}
948
- disabled={!!carouselErrors?.[index]?.description}
949
- >
950
- {formatMessage(messages.addVar)}
951
- </CapButton>
952
- )
953
- }
954
- />
955
- <CapRow className="rcs_text_area_wrapper">
956
- {(isEditFlow || !isFullMode) ? (
957
- renderCarouselEditMessage(card.description || '')
958
- ) : (
959
- <TextArea
960
- autosize={{ minRows: 3, maxRows: 5 }}
961
- value={card.description || ''}
962
- placeholder={formatMessage(messages.templateDescPlaceholder)}
963
- onChange={({ target: { value } }) => {
964
- let error = false;
965
- if (value?.length > RCS_RICH_CARD_MAX_LENGTH) {
966
- error = formatMessage(messages.templateMessageLengthError);
967
- } else {
968
- error = variableErrorHandling(value);
969
- }
970
- updateCarouselErrors(index, { description: error });
971
- handleCarouselValueChange(index, [{ fieldName: 'description', value }]);
972
- }}
973
- disabled={isEditFlow || !isFullMode}
974
- errorMessage={
975
- carouselErrors?.[index]?.description && (
976
- <CapError className="rcs-template-message-error">
977
- {carouselErrors[index].description}
978
- </CapError>
979
- )
980
- }
981
- />
982
- )}
983
- </CapRow>
984
- </CapRow>
985
- {!isEditFlow && (
986
- <CapRow className="rcs-carousel-character-count-row">
987
- {renderCarouselCharacterCount(
988
- getCarouselDescriptionCharacterCount(index),
989
- getDescriptionMaxLength(),
990
- )}
991
- </CapRow>
992
- )}
993
-
994
- <CapDivider className="rcs-carousel-card-divider" />
995
- {renderCarouselCardButtons(index)}
996
- </CapCard>
997
- ),
998
- };
999
- });
1000
- };
1001
-
1002
- const renderCarouselSection = () => {
1003
- if (!isCarouselType) return null;
1004
-
1005
- const operations = (
1006
- <>
1007
- <CapDivider type="vertical" />
1008
- <CapButton
1009
- onClick={addCarouselCard}
1010
- type="flat"
1011
- className="add-carousel-content-button"
1012
- disabled={
1013
- isEditFlow ||
1014
- !isFullMode ||
1015
- MAX_RCS_CAROUSEL_ALLOWED === (carouselData?.length || 0) ||
1016
- checkDisableAddCarouselButton()
1017
- }
1018
- >
1019
- <CapIcon type="plus" />
1020
- </CapButton>
1021
- </>
1022
- );
1023
-
1024
- return (
1025
- <CapRow className="rcs-carousel-section">
1026
- {renderCarouselDimensionSelection()}
1027
- <CapRow className="rcs-carousel-tab">
1028
- <CapTab
1029
- key={`rcs-carousel-tab-${carouselResetNonce}`}
1030
- defaultActiveKey="0"
1031
- activeKey={activeCarouselIndex}
1032
- tabBarExtraContent={operations}
1033
- onChange={(key) => {
1034
- setActiveCarouselIndex(`${key}`);
1035
- // reset focused var when switching cards (as per requirement)
1036
- setCarouselFocusedVarId('');
1037
- }}
1038
- panes={getCarouselTabPanes()}
1039
- />
1040
- </CapRow>
1041
- </CapRow>
1042
- );
1043
- };
1044
324
 
1045
325
  const mediaRadioOptions = [
1046
326
  {
@@ -1049,16 +329,11 @@ export const Rcs = (props) => {
1049
329
  },
1050
330
  {
1051
331
  value: RCS_MEDIA_TYPES.VIDEO,
1052
- label: formatMessage(
1053
- templateType === contentType.carousel
1054
- ? messages.carouselMediaVideoOption
1055
- : messages.mediaVideo,
1056
- ),
332
+ label: formatMessage(messages.mediaVideo),
1057
333
  },
1058
334
  ];
1059
335
  const aiContentBotDisabled = isAiContentBotDisabled();
1060
336
 
1061
-
1062
337
  const updateButtonChange = (data, index) => {
1063
338
  if (data && data.text) {
1064
339
  const forbiddenError = forbiddenCharactersValidation(data.text);
@@ -1095,9 +370,7 @@ export const Rcs = (props) => {
1095
370
  if (isFullMode) return;
1096
371
  if (loadingTags || !tags || tags.length === 0) return;
1097
372
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1098
- const slotOffset =
1099
- type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
1100
- const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
373
+ const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
1101
374
  if (!resolved) {
1102
375
  if (type === TITLE_TEXT) setTemplateTitleError(false);
1103
376
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -1124,16 +397,10 @@ export const Rcs = (props) => {
1124
397
  tagModule: getDefaultTags,
1125
398
  isFullMode,
1126
399
  }) || {};
1127
- const unsupportedTagsLengthCheck =
1128
- validationResponse?.unsupportedTags?.length > 0;
1129
- const errorMsg =
1130
- (unsupportedTagsLengthCheck &&
1131
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1132
- unsupportedTags: validationResponse.unsupportedTags,
1133
- })) ||
1134
- (validationResponse.isBraceError &&
1135
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1136
- false;
400
+ const errorMsg =
401
+ (validationResponse?.isBraceError &&
402
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
403
+ false;
1137
404
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
1138
405
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
1139
406
  };
@@ -1146,98 +413,15 @@ export const Rcs = (props) => {
1146
413
  validateResolvedTagsForType(MESSAGE_TEXT);
1147
414
  }, [cardVarMapped, templateDesc, tags, injectedTags, loadingTags]);
1148
415
 
1149
- useEffect(() => {
1150
- if (isFullMode || !isCarouselType) return;
1151
- if (loadingTags || !tags || tags.length === 0) return;
1152
- (carouselData || []).forEach((card, idx) => {
1153
- ['title', 'description'].forEach((field) => {
1154
- const templateStr = card?.[field] || '';
1155
- if (!templateStr) return;
1156
- const resolved = resolveTemplateWithMap(templateStr);
1157
- if (!resolved) {
1158
- updateCarouselErrors(idx, { [field]: false });
1159
- return;
1160
- }
1161
- let contentForValidation = resolved;
1162
- const placeholderTokens = templateStr.match(rcsVarRegex) || [];
1163
- placeholderTokens.forEach((t) => {
1164
- const escaped = t.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1165
- contentForValidation = contentForValidation.replace(new RegExp(escaped, 'g'), '');
1166
- });
1167
- if (!contentForValidation.trim()) {
1168
- updateCarouselErrors(idx, { [field]: false });
1169
- return;
1170
- }
1171
- const validationResponse = validateTags({
1172
- content: contentForValidation,
1173
- tagsParam: tags,
1174
- injectedTagsParams: injectedTags,
1175
- location,
1176
- tagModule: getDefaultTags,
1177
- eventContextTags,
1178
- isFullMode,
1179
- }) || {};
1180
- const errorMsg =
1181
- (validationResponse?.unsupportedTags?.length > 0 &&
1182
- formatMessage(globalMessages.unsupportedTagsValidationError, {
1183
- unsupportedTags: validationResponse.unsupportedTags,
1184
- })) ||
1185
- (validationResponse.isBraceError &&
1186
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
1187
- false;
1188
- updateCarouselErrors(idx, { [field]: errorMsg });
1189
- });
1190
- });
1191
- }, [cardVarMapped, carouselData, tags, injectedTags, loadingTags, isCarouselType]);
1192
-
1193
416
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
1194
417
 
1195
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
1196
-
1197
- /** Same `{{tag}}` in both title and description must not share one semantic map entry. */
1198
- const rcsSpanningSemanticVarNames = useMemo(
1199
- () => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
1200
- [templateTitle, templateDesc],
1201
- );
1202
-
1203
- /** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
1204
- const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
1205
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
1206
- const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
1207
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
1208
- let varOrdinal = 0;
1209
- for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
1210
- const segmentToken = templateSegments[segmentIndexInField];
1211
- if (rcsVarTestRegex.test(segmentToken)) {
1212
- if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
1213
- return offset + varOrdinal;
1214
- }
1215
- varOrdinal += 1;
1216
- }
1217
- }
1218
- return null;
1219
- };
1220
-
1221
- /**
1222
- * Master-branch resolve: uses numeric slot keys + semantic spanning detection for correct
1223
- * multi-field variable resolution.
1224
- */
1225
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
418
+ const resolveTemplateWithMap = (str = '') => {
1226
419
  if (!str) return '';
1227
- const arr = splitTemplateVarStringRcs(str);
1228
- let varOrdinal = 0;
420
+ const arr = splitTemplateVarString(str);
1229
421
  return arr.map((elem) => {
1230
422
  if (rcsVarTestRegex.test(elem)) {
1231
423
  const key = getVarNameFromToken(elem);
1232
- const globalSlot = slotOffset + varOrdinal;
1233
- varOrdinal += 1;
1234
- const v = resolveCardVarMappedSlotValue(
1235
- cardVarMapped,
1236
- key,
1237
- globalSlot,
1238
- isEditLike,
1239
- rcsSpanningSemanticVarNames.has(key),
1240
- );
424
+ const v = cardVarMapped?.[key];
1241
425
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
1242
426
  return String(v);
1243
427
  }
@@ -1245,128 +429,39 @@ export const Rcs = (props) => {
1245
429
  }).join('');
1246
430
  };
1247
431
 
1248
- const buildCarouselCardsForPreview = (cards = []) => (cards || []).map((card = {}) => {
1249
- const videoThumb = card.thumbnailSrc || card.videoAsset?.videoThumbnail || '';
1250
- const videoSrc = card.videoAsset?.videoSrc || '';
1251
- const resolvedTitle = !isFullMode ? resolveTemplateWithMap(card.title || '') : (card.title || '');
1252
- const resolvedDesc = !isFullMode ? resolveTemplateWithMap(card.description || '') : (card.description || '');
1253
- return {
1254
- mediaType: (card.mediaType || '').toLowerCase(),
1255
- imageSrc: card.imageSrc || '',
1256
- videoSrc,
1257
- videoPreviewImg: videoThumb,
1258
- title: resolvedTitle,
1259
- bodyText: resolvedDesc,
1260
- suggestions: card.suggestions || [],
1261
- };
1262
- });
1263
432
 
1264
- /**
1265
- * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
1266
- * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
1267
- * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
1268
- */
1269
- const getTemplateContent = useCallback(() => {
1270
- if (templateType === contentType.carousel) {
1271
- const carouselDimKey = getCarouselDimensionKey();
1272
- const carouselImgDims =
1273
- RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
1274
- const carouselVidDims =
1275
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
1276
- || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
1277
- return {
1278
- carouselData: buildCarouselCardsForPreview(carouselData),
1279
- carouselPreviewDimensions: {
1280
- imageWidth: carouselImgDims.width,
1281
- imageHeight: carouselImgDims.height,
1282
- videoThumbWidth: carouselVidDims.width,
1283
- videoThumbHeight: carouselVidDims.height,
1284
- },
1285
- };
1286
- }
1287
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
1288
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
1289
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
1290
-
1291
- const isSlotMappingMode = isEditFlow || !isFullMode;
1292
- const titleVarCountForResolve = isMediaTypeText
1293
- ? 0
1294
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
1295
- const resolvedTitle = isMediaTypeText
1296
- ? ''
1297
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
1298
- const resolvedDesc = isSlotMappingMode
1299
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
1300
- : templateDesc;
1301
-
1302
- const mediaPreview = {};
1303
- if (isMediaTypeImage && rcsImageSrc) {
1304
- mediaPreview.rcsImageSrc = rcsImageSrc;
1305
- }
1306
- if (isMediaTypeVideo && !isMediaTypeText) {
1307
- if (rcsThumbnailSrc) {
1308
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
1309
- } else if (rcsVideoSrc?.videoSrc) {
1310
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
1311
- }
1312
- if (rcsThumbnailSrc) {
1313
- mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
1314
- }
1315
- }
1316
-
1317
- const contentObj = {
1318
- templateHeader: resolvedTitle,
1319
- templateMessage: resolvedDesc,
1320
- ...mediaPreview,
1321
- ...(suggestions.length > 0 && {
1322
- suggestions: suggestions,
1323
- }),
1324
- };
1325
-
1326
- return contentObj;
1327
- }, [
1328
- templateMediaType,
1329
- templateTitle,
1330
- templateDesc,
1331
- rcsImageSrc,
1332
- rcsVideoSrc,
1333
- rcsThumbnailSrc,
1334
- suggestions,
1335
- selectedDimension,
1336
- isFullMode,
1337
- isEditFlow,
1338
- cardVarMapped,
1339
- rcsSpanningSemanticVarNames,
1340
- carouselData,
1341
- selectedCarouselHeight,
1342
- selectedCarouselWidth,
1343
- ]);
433
+ useEffect(() => {
434
+ if (isFullMode || isEditFlow) return;
435
+ const tokens = [
436
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
437
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
438
+ ];
439
+ if (!tokens.length) return;
440
+ setCardVarMapped((prev) => {
441
+ const next = { ...(prev || {}) };
442
+ let changed = false;
443
+ tokens.forEach((t) => {
444
+ const name = getVarNameFromToken(t);
445
+ if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
446
+ next[name] = '';
447
+ changed = true;
448
+ }
449
+ });
450
+ return changed ? next : prev;
451
+ });
452
+ }, [isFullMode, templateTitle, templateDesc]);
1344
453
 
1345
- const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
1346
454
 
455
+ const RcsLabel = styled.div`
456
+ display: flex;
457
+ margin-top: 20px;
458
+ `;
1347
459
  const paramObj = params || {};
1348
460
  useEffect(() => {
1349
461
  const { id } = paramObj;
1350
462
  if (id && isFullMode) {
1351
463
  setSpin(true);
1352
464
  actions.getTemplateDetails(id, setSpin);
1353
- } else if (!id && isFullMode) {
1354
- // Create New: clear standalone media and ALL possible carousel card Redux slots.
1355
- // Redux persists across mounts so we must clear unconditionally (carouselData may be [] on fresh mount).
1356
- updateRcsImageSrc('');
1357
- setRcsVideoSrc({});
1358
- setRcsThumbnailSrc('');
1359
- setAssetList({});
1360
- setEditFlow(false);
1361
- actions.clearRcsMediaAsset(0);
1362
- actions.clearRcsMediaAsset(1);
1363
- for (let cardIdx = 0; cardIdx < MAX_RCS_CAROUSEL_ALLOWED; cardIdx += 1) {
1364
- actions.clearRcsMediaAsset(getCarouselImageAssetIndex(cardIdx));
1365
- actions.clearRcsMediaAsset(getCarouselVideoAssetIndex(cardIdx));
1366
- actions.clearRcsMediaAsset(getCarouselThumbnailAssetIndex(cardIdx));
1367
- }
1368
- setCarouselData([]);
1369
- setCarouselErrors([]);
1370
465
  }
1371
466
  return () => {
1372
467
  actions.clearEditResponse();
@@ -1374,79 +469,67 @@ export const Rcs = (props) => {
1374
469
  }, [paramObj.id]);
1375
470
 
1376
471
  useEffect(() => {
1377
- if (!(isEditFlow || !isFullMode)) return;
472
+ if (!(isEditFlow || !isFullMode)) return;
1378
473
 
1379
- const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
1380
-
1381
- const initField = (targetString, setVarMap, slotOffset) => {
1382
- const arr = splitTemplateVarStringRcs(targetString);
1383
- if (!arr?.length) {
1384
- setVarMap({});
1385
- return;
1386
- }
1387
- const nextVarMap = {};
1388
- let varOrdinal = 0;
1389
- arr.forEach((elem, idx) => {
1390
- // Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
1391
- if (rcsVarTestRegex.test(elem)) {
1392
- const id = `${elem}_${idx}`;
1393
- const varName = getVarNameFromToken(elem);
1394
- const globalSlot = slotOffset + varOrdinal;
1395
- varOrdinal += 1;
1396
- const mappedValue = resolveCardVarMappedSlotValue(
1397
- cardVarMapped,
1398
- varName,
1399
- globalSlot,
1400
- isEditLike,
1401
- rcsSpanningSemanticVarNames.has(varName),
1402
- );
1403
- nextVarMap[id] = mappedValue;
474
+ const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
475
+ const arr = splitTemplateVarString(targetString);
476
+ if (!arr?.length) {
477
+ setVarMap({});
478
+ setUpdated([]);
479
+ return;
1404
480
  }
1405
- });
1406
- setVarMap(nextVarMap);
1407
- };
1408
-
1409
- initField(templateTitle, setTitleVarMappedData, 0);
1410
- initField(templateDesc, setDescVarMappedData, titleTokenCount);
1411
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
481
+ const nextVarMap = {};
482
+ const nextUpdated = [...arr];
483
+ arr.forEach((elem, idx) => {
484
+ // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
485
+ if (rcsVarTestRegex.test(elem)) {
486
+ const id = `${elem}_${idx}`;
487
+ const varName = getVarNameFromToken(elem);
488
+ const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
489
+ nextVarMap[id] = mappedValue;
490
+ if (mappedValue !== '') {
491
+ nextUpdated[idx] = mappedValue;
492
+ } else {
493
+ nextUpdated[idx] = elem;
494
+ }
495
+ }
496
+ });
497
+ setVarMap(nextVarMap);
498
+ setUpdated(nextUpdated);
499
+ };
1412
500
 
1413
- useEffect(() => {
1414
- if (!isEditFlow && isFullMode) {
501
+ initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
502
+ initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
503
+ }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
504
+
505
+ useEffect(() => {
506
+ if(!isEditFlow && isFullMode){
1415
507
  setRcsVideoSrc({});
1416
508
  updateRcsImageSrc('');
1417
- setRcsThumbnailSrc('');
509
+ setUpdateRcsImageSrc('');
510
+ updateRcsThumbnailSrc('');
1418
511
  setAssetList({});
1419
- }
1420
- }, [templateMediaType]);
512
+ }
513
+ }, [templateMediaType]);
1421
514
 
1422
- /** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
1423
- const templateStatusHelper = (cardContentFirst) => {
1424
- const raw =
1425
- cardContentFirst?.Status
1426
- ?? cardContentFirst?.status
1427
- ?? cardContentFirst?.approvalStatus
1428
- ?? '';
1429
- const status = typeof raw === 'string' ? raw.trim() : String(raw);
1430
- const n = status.toLowerCase();
1431
- switch (n) {
1432
- case 'approved':
515
+ const templateStatusHelper = (details) => {
516
+ const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
517
+ switch (status) {
518
+ case RCS_STATUSES.approved:
1433
519
  setTemplateStatus(RCS_STATUSES.approved);
1434
520
  break;
1435
- case 'pending':
521
+ case RCS_STATUSES.pending:
1436
522
  setTemplateStatus(RCS_STATUSES.pending);
1437
523
  break;
1438
- case 'awaitingapproval':
524
+ case RCS_STATUSES.awaitingApproval:
1439
525
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
1440
526
  break;
1441
- case 'unavailable':
527
+ case RCS_STATUSES.unavailable:
1442
528
  setTemplateStatus(RCS_STATUSES.unavailable);
1443
529
  break;
1444
- case 'rejected':
530
+ case RCS_STATUSES.rejected:
1445
531
  setTemplateStatus(RCS_STATUSES.rejected);
1446
532
  break;
1447
- case 'created':
1448
- setTemplateStatus(RCS_STATUSES.created);
1449
- break;
1450
533
  default:
1451
534
  setTemplateStatus(status);
1452
535
  break;
@@ -1500,283 +583,48 @@ export const Rcs = (props) => {
1500
583
  };
1501
584
 
1502
585
  useEffect(() => {
1503
- const details = rcsHydrationDetails;
586
+ const details = isFullMode ? rcsData?.templateDetails : templateData;
1504
587
  if (details && Object.keys(details).length > 0) {
1505
- // Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
1506
- const cardFromVersions = get(
1507
- details,
1508
- 'versions.base.content.RCS.rcsContent.cardContent[0]',
1509
- );
1510
- const rcsContent = get(details, 'versions.base.content.RCS.rcsContent', {});
1511
- const cardType = (rcsContent?.cardType || '').toString().toLowerCase();
1512
-
1513
- setEditFlow(true);
1514
- setTemplateName(details?.name || details?.creativeName || '');
1515
-
1516
- const cardFromTop = get(details, 'rcsContent.cardContent[0]');
1517
- const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
1518
- const cardVarMappedFromCardContent =
1519
- card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
1520
- ? card0.cardVarMapped
1521
- : {};
1522
- const cardVarMappedFromRootMirror =
1523
- details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
1524
- ? details.rcsCardVarMapped
1525
- : {};
1526
- // Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
1527
- // nested versions.cardContent[0].cardVarMapped is dropped on reload.
1528
- const mergedCardVarMappedFromPayload = {
1529
- ...cardVarMappedFromRootMirror,
1530
- ...cardVarMappedFromCardContent,
1531
- };
1532
- const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
1533
- const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
1534
- const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
1535
- Object.keys(mergedCardVarMappedFromPayload)
1536
- .sort()
1537
- .reduce((accumulator, mapKey) => {
1538
- accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
1539
- return accumulator;
1540
- }, {}),
1541
- )}`;
1542
- if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
1543
- lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
1544
- setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
1545
- }
1546
- const tokenListForMap = [
1547
- ...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
1548
- ...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
1549
- ];
1550
- const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
1551
- // Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
1552
- // getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
1553
- const cardVarMappedBeforeCoalesce = isFullMode
1554
- ? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
1555
- : { ...mergedCardVarMappedFromPayload };
1556
- const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
1557
- cardVarMappedBeforeCoalesce,
1558
- loadedTitleForMap,
1559
- loadedDescForMap,
1560
- rcsVarRegex,
1561
- );
1562
- const cardVarMappedAfterNumericSlotSync = !isFullMode
1563
- ? syncCardVarMappedSemanticsFromSlots(
1564
- cardVarMappedAfterCoalesce,
1565
- loadedTitleForMap,
1566
- loadedDescForMap,
1567
- rcsVarRegex,
1568
- )
1569
- : cardVarMappedAfterCoalesce;
1570
- const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
1571
- // Pre-populate variable/tag mappings while opening an existing template in edit flows
1572
- setCardVarMapped((previousVarMapState) => {
1573
- const previousVarMap = previousVarMapState ?? {};
1574
- if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
1575
- const previousVarMapKeys = Object.keys(previousVarMap);
1576
- const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
1577
- if (previousVarMapKeys.length === nextVarMapKeys.length) {
1578
- const allSlotValuesMatchPrevious = previousVarMapKeys.every(
1579
- (key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
1580
- );
1581
- if (allSlotValuesMatchPrevious) return previousVarMapState;
1582
- }
1583
- return hydratedCardVarMappedResult;
1584
- });
1585
-
1586
- if (cardType === contentType.carousel) {
1587
-
1588
- setTemplateType(contentType.carousel);
1589
- setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1590
- const cardSettings = rcsContent?.cardSettings || {};
1591
- const cardWidth = cardSettings?.cardWidth || SMALL;
1592
- setSelectedCarouselWidth(cardWidth);
1593
-
1594
- const cards = Array.isArray(rcsContent?.cardContent) ? rcsContent.cardContent : [];
1595
- const firstHeight = cards?.[0]?.media?.height || MEDIUM;
1596
- setSelectedCarouselHeight(firstHeight);
1597
- setSelectedCarousel(`${firstHeight}_${cardWidth}`);
1598
- setActiveCarouselIndex('0');
1599
-
1600
- const hydratedCards = cards.map((c = {}, idx) => {
1601
- const mediaType = c.mediaType;
1602
- const media = c.media || {};
1603
- const mediaUrl = media.mediaUrl || '';
1604
- const thumbUrl = media.thumbnailUrl || '';
1605
- const rawSuggestions = Array.isArray(c.suggestions) ? c.suggestions : [];
1606
- const suggestions = idx === 0 && rawSuggestions.length === 0
1607
- ? cloneDeep(RCS_CAROUSEL_FIRST_CARD_DEFAULT_SUGGESTIONS)
1608
- : rawSuggestions;
1609
- return {
1610
- title: c.title || '',
1611
- description: c.description || '',
1612
- mediaType,
1613
- imageSrc: mediaType === RCS_MEDIA_TYPES.IMAGE ? mediaUrl : '',
1614
- videoAsset: mediaType === RCS_MEDIA_TYPES.VIDEO ? {
1615
- videoSrc: mediaUrl,
1616
- previewUrl: thumbUrl,
1617
- videoThumbnail: thumbUrl,
1618
- videoName: c?.media?.videoName || '',
1619
- } : {},
1620
- thumbnailSrc: mediaType === RCS_MEDIA_TYPES.VIDEO ? thumbUrl : '',
1621
- suggestions,
1622
- };
1623
- });
1624
- setCarouselData(
1625
- hydratedCards.length > 0
1626
- ? ensureFirstCardDefaultPhoneSuggestions(hydratedCards)
1627
- : [cloneDeep(RCS_CAROUSEL_INITIAL_FIRST_CARD)],
1628
- );
1629
- setCarouselErrors(new Array(hydratedCards.length > 0 ? hydratedCards.length : 1).fill({}));
1630
-
1631
- // Status bar uses first card's Status, keep existing behavior.
1632
- if (isHostInfoBip) {
1633
- setTemplateStatus('');
1634
- } else {
1635
- const firstCard = cards?.[0] || {};
1636
- const cardForCarouselStatus = {
1637
- ...firstCard,
1638
- Status:
1639
- firstCard.Status
1640
- ?? firstCard.status
1641
- ?? firstCard.approvalStatus
1642
- ?? get(details, 'templateStatus')
1643
- ?? get(details, 'approvalStatus')
1644
- ?? get(details, 'creativeStatus')
1645
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
1646
- ?? '',
1647
- };
1648
- templateStatusHelper(cardForCarouselStatus);
1649
- }
1650
- return;
588
+ if (!isFullMode) {
589
+ const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
590
+ setCardVarMapped(tempCardVarMapped);
1651
591
  }
1652
-
1653
- const mediaType =
1654
- card0.mediaType
1655
- || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
1656
- if (cardType !== contentType.carousel && mediaType === RCS_MEDIA_TYPES.NONE) {
592
+ const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
593
+ if (mediaType === RCS_MEDIA_TYPES.NONE) {
1657
594
  setTemplateType(contentType.text_message);
1658
- } else if (cardType !== contentType.carousel && mediaType !== RCS_MEDIA_TYPES.NONE) {
1659
- setTemplateType(contentType.rich_card);
1660
- }
1661
-
1662
- const loadedTitle = loadedTitleForMap;
1663
- const loadedDesc = loadedDescForMap;
1664
- const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
1665
- loadedTitle,
1666
- loadedDesc,
1667
- isFullMode,
1668
- cardVarMappedAfterHydration: hydratedCardVarMappedResult,
1669
- rcsVarRegex,
1670
- });
1671
- setTemplateTitle(normalizedTitle);
1672
- setTemplateDesc(normalizedDesc);
1673
- setSuggestions(
1674
- Array.isArray(card0.suggestions)
1675
- ? card0.suggestions
1676
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
1677
- );
1678
- const cardForStatus = {
1679
- ...card0,
1680
- Status:
1681
- card0.Status
1682
- ?? card0.status
1683
- ?? card0.approvalStatus
1684
- ?? get(details, 'templateStatus')
1685
- ?? get(details, 'approvalStatus')
1686
- ?? get(details, 'creativeStatus')
1687
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
1688
- ?? '',
1689
- };
1690
- if (isHostInfoBip) {
1691
- setTemplateStatus('');
1692
595
  } else {
1693
- templateStatusHelper(cardForStatus);
596
+ setTemplateType(contentType.rich_card);
1694
597
  }
1695
- const mediaData =
1696
- card0.media != null && card0.media !== ''
1697
- ? card0.media
1698
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
1699
- const cardSettings =
1700
- get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
1701
- || get(details, 'rcsContent.cardSettings', '');
1702
- setMediaData(mediaData, mediaType, cardSettings);
1703
-
1704
- const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
1705
- const base = get(smsFallbackContent, 'versions.base', {});
1706
- const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
1707
- const smsEditor = base['sms-editor'];
1708
- const fromNested = Array.isArray(updatedEditor)
1709
- ? updatedEditor.join('')
1710
- : (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
1711
- const fallbackMessage = smsFallbackContent.smsContent
1712
- || smsFallbackContent.smsTemplateContent
1713
- || smsFallbackContent.message
1714
- || fromNested
1715
- || '';
1716
- const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
1717
- const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
1718
- const hasFallbackPayload =
1719
- smsFallbackContent
1720
- && Object.keys(smsFallbackContent).length > 0
1721
- && (
1722
- !!smsFallbackContent.smsTemplateName
1723
- || !!fallbackMessage
1724
- || hasVarMapped
1725
- );
1726
- if (hasFallbackPayload) {
1727
- if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
1728
- console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
1729
- }
1730
- const unicodeFromApi =
1731
- typeof smsFallbackContent.unicodeValidity === 'boolean'
1732
- ? smsFallbackContent.unicodeValidity
1733
- : (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
1734
- const registeredSenderIdsFromApi =
1735
- extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
1736
- const nextSmsState = {
1737
- templateName: smsFallbackContent.smsTemplateName || '',
1738
- content: fallbackMessage,
1739
- templateContent: fallbackMessage,
1740
- unicodeValidity: unicodeFromApi,
1741
- ...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
1742
- ...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
1743
- ? { registeredSenderIds: registeredSenderIdsFromApi }
1744
- : {}),
1745
- };
1746
- const hydrationKey = JSON.stringify({
1747
- creativeKey: details._id || details.name || details.creativeName || '',
1748
- templateName: nextSmsState.templateName,
1749
- content: nextSmsState.content,
1750
- unicodeValidity: nextSmsState.unicodeValidity,
1751
- varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
1752
- senderIds:
1753
- Array.isArray(registeredSenderIdsFromApi)
1754
- ? registeredSenderIdsFromApi.join('\u001f')
1755
- : '',
1756
- });
1757
- if (
1758
- isFullMode
1759
- || lastSmsFallbackHydrationKeyRef.current !== hydrationKey
1760
- ) {
1761
- lastSmsFallbackHydrationKeyRef.current = hydrationKey;
1762
- setSmsFallbackData(nextSmsState);
1763
- }
1764
- } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
1765
- lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
1766
- setSmsFallbackData(null);
598
+ setEditFlow(true);
599
+ setTemplateName(details.name || '');
600
+ const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
601
+ const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
602
+ const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
603
+ const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
604
+ const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
605
+ setTemplateTitle(normalizedTitle);
606
+ setTemplateDesc(normalizedDesc);
607
+ setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
608
+ templateStatusHelper(details);
609
+ const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
610
+ const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
611
+ setMediaData(mediaData, mediaType, cardSettings);
612
+ if (details?.edit) {
613
+ const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
614
+ setRcsAccount(rcsAccountId);
1767
615
  }
1768
616
  }
1769
- }, [rcsHydrationDetails, isFullMode, isHostInfoBip]);
617
+ }, [rcsData, templateData, isFullMode, isEditFlow]);
618
+
1770
619
 
1771
620
  useEffect(() => {
1772
621
  if (templateType === contentType.text_message) {
1773
622
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
1774
- // Full-mode create only: switching to plain text clears draft title/media. Never clear when
1775
- // hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
623
+ setTemplateTitle('');
624
+ setTemplateTitleError('');
1776
625
  if (!isEditFlow && isFullMode) {
1777
- setTemplateTitle('');
1778
- setTemplateTitleError('');
1779
626
  setUpdateRcsImageSrc('');
627
+ setUpdateRcsVideoSrc({});
1780
628
  setRcsVideoSrc({});
1781
629
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
1782
630
  }
@@ -1803,8 +651,7 @@ export const Rcs = (props) => {
1803
651
  if (!showDltContainer) {
1804
652
  const { type, module } = location.query || {};
1805
653
  const isEmbedded = type === EMBEDDED;
1806
- // Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
1807
- const context = isEmbedded ? module : 'outbound';
654
+ const context = isEmbedded ? module : DEFAULT;
1808
655
  const embedded = isEmbedded ? type : FULL;
1809
656
  const query = {
1810
657
  layout: SMS,
@@ -1815,9 +662,9 @@ export const Rcs = (props) => {
1815
662
  if (getDefaultTags) {
1816
663
  query.context = getDefaultTags;
1817
664
  }
1818
- fetchTagSchemaIfNewQuery(query);
665
+ globalActions.fetchSchemaForEntity(query);
1819
666
  }
1820
- }, [showDltContainer, fetchTagSchemaIfNewQuery]);
667
+ }, [showDltContainer]);
1821
668
 
1822
669
  useEffect(() => {
1823
670
  let tag = get(metaEntities, `tags.standard`, []);
@@ -1840,114 +687,16 @@ export const Rcs = (props) => {
1840
687
  context,
1841
688
  embedded,
1842
689
  };
1843
- if (getDefaultTags) {
1844
- query.context = getDefaultTags;
1845
- }
1846
- fetchTagSchemaIfNewQuery(query);
1847
690
  globalActions.fetchSchemaForEntity(query);
1848
691
  };
1849
692
 
1850
- const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
1851
- if (!templateStr || !numericVarName || !tagName) return templateStr;
1852
- const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
1853
- return templateStr.replace(re, `{{${tagName}}}`);
1854
- };
1855
-
1856
- const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
1857
- if (!varSegmentCompositeDomId) return;
1858
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
1859
- if (underscoreIndexInCompositeId === -1) return;
1860
- const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
1861
- if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
1862
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
1863
- const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
1864
- if (!semanticOrNumericVarName) return;
1865
- const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
1866
- const templateStringForField =
1867
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
1868
- const titleOrMessageFieldType =
1869
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
1870
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
1871
- varSegmentCompositeDomId,
1872
- templateStringForField,
1873
- titleOrMessageFieldType,
1874
- );
1875
- const cardVarMappedNumericSlotKey =
1876
- globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
1877
- ? String(globalVarSlotIndexZeroBased + 1)
1878
- : null;
1879
-
1880
- setCardVarMapped((previousCardVarMapped) => {
1881
- const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
1882
- if (isNumericPlaceholderSlot) {
1883
- const existingValueBeforeAppend = (
1884
- previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
1885
- ).toString();
1886
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
1887
- delete updatedCardVarMapped[semanticOrNumericVarName];
1888
- updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
1889
- } else {
1890
- // Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
1891
- // "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
1892
- // read/write the global numeric slot only and drop the shared semantic key.
1893
- const existingValueBeforeAppend = cardVarMappedNumericSlotKey
1894
- ? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
1895
- : String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
1896
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
1897
- delete updatedCardVarMapped[semanticOrNumericVarName];
1898
- if (cardVarMappedNumericSlotKey) {
1899
- updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
1900
- } else {
1901
- updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
1902
- }
1903
- }
1904
- return updatedCardVarMapped;
1905
- });
1906
-
1907
- if (
1908
- isNumericPlaceholderSlot
1909
- && (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
1910
- ) {
1911
- if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
1912
- setTemplateTitle((previousTitle) => {
1913
- const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
1914
- previousTitle || '',
1915
- semanticOrNumericVarName,
1916
- selectedTagNameFromPicker,
1917
- );
1918
- if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
1919
- setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
1920
- // 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
1921
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
1922
- return titleAfterReplacingNumericPlaceholder;
1923
- });
1924
- } else {
1925
- setTemplateDesc((previousDescription) => {
1926
- const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
1927
- previousDescription || '',
1928
- semanticOrNumericVarName,
1929
- selectedTagNameFromPicker,
1930
- );
1931
- if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
1932
- return previousDescription;
1933
- }
1934
- setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
1935
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
1936
- return descriptionAfterReplacingNumericPlaceholder;
1937
- });
1938
- }
1939
- }
1940
- };
1941
-
1942
- const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
1943
-
1944
- const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
1945
-
1946
- const onCarouselTagSelect = (data) => {
1947
- if (!carouselFocusedVarId) return;
1948
- const sep = carouselFocusedVarId.lastIndexOf('_');
693
+ const onTagSelect = (data, areaId) => {
694
+ if (!areaId) return;
695
+ const sep = areaId.lastIndexOf('_');
1949
696
  if (sep === -1) return;
1950
- const token = carouselFocusedVarId.slice(0, sep);
697
+ const numId = Number(areaId.slice(sep + 1));
698
+ if (isNaN(numId)) return;
699
+ const token = areaId.slice(0, sep);
1951
700
  const variableName = getVarNameFromToken(token);
1952
701
  if (!variableName) return;
1953
702
  setCardVarMapped((prev) => {
@@ -1960,6 +709,10 @@ export const Rcs = (props) => {
1960
709
  });
1961
710
  };
1962
711
 
712
+ const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
713
+
714
+ const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
715
+
1963
716
  const onTagSelectFallback = (data) => {
1964
717
  const tempMsg = `${fallbackMessage}{{${data}}}`;
1965
718
  const error = fallbackMessageErrorHandler(tempMsg);
@@ -1979,14 +732,15 @@ export const Rcs = (props) => {
1979
732
  };
1980
733
  // tag Code end
1981
734
 
1982
- const renderLabel = (value, desc) => {
735
+ const renderLabel = (value, showLabel, desc) => {
736
+ const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
1983
737
  return (
1984
738
  <>
1985
- <div className="rcs-form-section-heading">
739
+ <RcsLabel>
1986
740
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
1987
- </div>
741
+ </RcsLabel>
1988
742
  {desc && (
1989
- <CapLabel type="label3" className="rcs-form-field-caption">
743
+ <CapLabel type="label3" style={{ marginBottom: '17px' }}>
1990
744
  {formatMessage(messages[desc])}
1991
745
  </CapLabel>
1992
746
  )}
@@ -2003,14 +757,15 @@ export const Rcs = (props) => {
2003
757
  value: contentType.rich_card,
2004
758
  label: formatMessage(messages.richCard),
2005
759
  },
2006
- ...(!isHostInfoBip
2007
- ? [
2008
- {
2009
- value: contentType.carousel,
2010
- label: formatMessage(messages.carousel),
2011
- },
2012
- ]
2013
- : []),
760
+ {
761
+ value: contentType.carousel,
762
+ label: (
763
+ <CapTooltip title={formatMessage(messages.disabledCarouselTooltip)}>
764
+ {formatMessage(messages.carousel)}
765
+ </CapTooltip>
766
+ ),
767
+ disabled: true,
768
+ },
2014
769
  ];
2015
770
 
2016
771
  const onTemplateNameChange = ({ target: { value } }) => {
@@ -2021,10 +776,6 @@ export const Rcs = (props) => {
2021
776
 
2022
777
  const onTemplateTypeChange = ({ target: { value } }) => {
2023
778
  setTemplateType(value);
2024
- // Carousel has per-card media; keep template-level media type neutral.
2025
- if (value === contentType.carousel) {
2026
- setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
2027
- }
2028
779
  };
2029
780
 
2030
781
 
@@ -2045,39 +796,8 @@ export const Rcs = (props) => {
2045
796
  const onTemplateMediaTypeChange = ({ target: { value } }) => {
2046
797
  setTemplateMediaType(value);
2047
798
  };
2048
- const renderedRCSEditMessage = (descArray, type) => {
2049
- const renderArray = [];
2050
- if (descArray?.length) {
2051
- descArray.forEach((elem, index) => {
2052
- if (rcsVarTestRegex.test(elem)) {
2053
- // Variable input
2054
- renderArray.push(
2055
- <TextArea
2056
- id={`${elem}_${index}`}
2057
- key={`${elem}_${index}`}
2058
- placeholder={`enter the value for ${elem}`}
2059
- autosize={{ minRows: 1, maxRows: 3 }}
2060
- onChange={e => textAreaValueChange(e, type)}
2061
- value={textAreaValue(index, type)}
2062
- onFocus={(e) => setTextAreaId(e, type)}
2063
- />
2064
- );
2065
- } else if (elem) {
2066
- // Static text
2067
- renderArray.push(
2068
- <TextArea
2069
- key={`static_${index}`}
2070
- value={elem}
2071
- autosize={{ minRows: 1, maxRows: 3 }}
2072
- disabled
2073
- className="rcs-edit-template-message-static-textarea"
2074
- />
2075
- );
2076
- }
2077
- });
2078
- }
2079
- return renderArray;
2080
- };
799
+
800
+
2081
801
  const onTemplateTitleChange = ({ target: { value } }) => {
2082
802
  let errorMessage = false;
2083
803
  if (templateType === contentType.rich_card && !value.trim()) {
@@ -2093,7 +813,7 @@ export const Rcs = (props) => {
2093
813
 
2094
814
  const onTemplateDescChange = ({ target: { value } }) => {
2095
815
  let errorMessage = false;
2096
- if(templateType === contentType.text_message && value?.length > (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)){
816
+ if(templateType === contentType.text_message && value?.length > RCS_TEXT_MESSAGE_MAX_LENGTH){
2097
817
  errorMessage = formatMessage(messages.templateMessageLengthError);
2098
818
  } else if(templateType === contentType.rich_card && value?.length > RCS_RICH_CARD_MAX_LENGTH){
2099
819
  errorMessage = formatMessage(messages.templateMessageLengthError);
@@ -2109,16 +829,16 @@ export const Rcs = (props) => {
2109
829
 
2110
830
  const templateDescErrorHandler = (value) => {
2111
831
  let errorMessage = false;
2112
- const { unsupportedTags, isBraceError } = validateTags({
832
+ const { isBraceError } = validateTags({
2113
833
  content: value,
2114
834
  tagsParam: tags,
2115
- injectedTagsParams: injectedTags,
2116
835
  location,
2117
836
  tagModule: getDefaultTags,
837
+ isFullMode,
2118
838
  }) || {};
2119
839
 
2120
840
  const maxLength = templateType === contentType.text_message
2121
- ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH)
841
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH
2122
842
  : RCS_RICH_CARD_MAX_LENGTH;
2123
843
 
2124
844
  if (value === '' && isMediaTypeText) {
@@ -2141,25 +861,10 @@ export const Rcs = (props) => {
2141
861
  };
2142
862
 
2143
863
  const fallbackMessageErrorHandler = (value) => {
2144
- let errorMessage = false;
2145
- const { unsupportedTags } = validateTags({
2146
- content: value,
2147
- tagsParam: tags,
2148
- injectedTagsParams: injectedTags,
2149
- location,
2150
- tagModule: getDefaultTags,
2151
- }) || {};
2152
864
  if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
2153
- errorMessage = formatMessage(messages.fallbackMsgLenError);
2154
- } else if (unsupportedTags?.length > 0) {
2155
- errorMessage = formatMessage(
2156
- globalMessages.unsupportedTagsValidationError,
2157
- {
2158
- unsupportedTags,
2159
- },
2160
- );
865
+ return formatMessage(messages.fallbackMsgLenError);
2161
866
  }
2162
- return errorMessage;
867
+ return false;
2163
868
  };
2164
869
 
2165
870
  // Check for forbidden characters: square brackets [] and single curly braces {}
@@ -2206,43 +911,53 @@ export const Rcs = (props) => {
2206
911
  if(!isFullMode){
2207
912
  return false;
2208
913
  }
2209
- // Allow Liquid-style param names: letters, digits, underscore, dots (e.g. dynamic_expiry_date_after_3_days.FORMAT_1)
2210
- if (!/^[\w.]+$/.test(paramName)) {
914
+ if (!/^\w+$/.test(paramName)) {
2211
915
  return formatMessage(messages.unknownCharactersError);
2212
916
  }
2213
917
  }
2214
918
  return false;
2215
919
  };
920
+
921
+ const templateHeaderErrorHandler = (value) => {
922
+ let errorMessage = false;
923
+ if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
924
+ errorMessage = formatMessage(messages.templateHeaderLengthError);
925
+ } else {
926
+ errorMessage = variableErrorHandling(value);
927
+ }
928
+ return errorMessage;
929
+ };
930
+
931
+
932
+ const templateMessageErrorHandler = (value) => {
933
+ let errorMessage = false;
934
+ if (value === '') {
935
+ errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
936
+ } else if (
937
+ value?.length
938
+ > TEMPLATE_MESSAGE_MAX_LENGTH
939
+ ) {
940
+ errorMessage = formatMessage(messages.templateMessageLengthError);
941
+ } else {
942
+ errorMessage = variableErrorHandling(value);
943
+ }
944
+ return errorMessage;
945
+ };
946
+
2216
947
 
2217
948
  const onMessageAddVar = () => {
2218
- onAddVar(templateDesc);
949
+ onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
2219
950
  };
2220
951
 
2221
- /**
2222
- * Returns the smallest positive integer not already used as a `{{N}}` variable
2223
- * in either the title or description fields, or null if the limit (19) is reached.
2224
- * Scans both fields so title and description vars never share the same number
2225
- * (duplicate numbers would share a cardVarMapped key and bleed values across fields).
2226
- */
2227
- const getNextRcsNumericVarNumber = (titleStr, descStr) => {
2228
- const allExistingVars = [
2229
- ...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
2230
- ...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
2231
- ];
2232
- const existingNumbers = allExistingVars.flatMap(v => {
2233
- const m = v.match(/\d+/);
2234
- return m ? [parseInt(m[0], 10)] : [];
2235
- });
952
+ const onAddVar = (type, messageContent, regex) => {
953
+ // Always append the next variable at the end, like WhatsApp
954
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
955
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
2236
956
  let nextNumber = 1;
2237
957
  while (existingNumbers.includes(nextNumber)) {
2238
958
  nextNumber++;
2239
959
  }
2240
- return nextNumber > 19 ? null : nextNumber;
2241
- };
2242
-
2243
- const onAddVar = (messageContent) => {
2244
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
2245
- if (nextNumber === null) {
960
+ if (nextNumber > 19) {
2246
961
  return;
2247
962
  }
2248
963
  const nextVar = `{{${nextNumber}}}`;
@@ -2254,13 +969,15 @@ const onAddVar = (messageContent) => {
2254
969
  };
2255
970
 
2256
971
  const onTitleAddVar = () => {
2257
- // Scan both title AND description so the new title var number doesn't
2258
- // duplicate a number already used in the description. Duplicate numeric
2259
- // names would share the same cardVarMapped semantic key, causing the
2260
- // description slot to reflect the title slot value and vice-versa.
972
+ // Always append the next variable at the end, like WhatsApp
2261
973
  const messageContent = templateTitle;
2262
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
2263
- if (nextNumber === null) {
974
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
975
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
976
+ let nextNumber = 1;
977
+ while (existingNumbers.includes(nextNumber)) {
978
+ nextNumber++;
979
+ }
980
+ if (nextNumber > 19) {
2264
981
  return;
2265
982
  }
2266
983
  const nextVar = `{{${nextNumber}}}`;
@@ -2272,35 +989,26 @@ const onTitleAddVar = () => {
2272
989
  setTemplateTitleError(error);
2273
990
  };
2274
991
 
2275
- // Carousel: global variables across the whole carousel (all cards, title+body)
2276
- const getNextCarouselVarToken = () => {
2277
- const nums = [];
2278
- (carouselData || []).forEach((c = {}) => {
2279
- const s1 = (c.title || '').match(/\{\{(\d+)\}\}/g) || [];
2280
- const s2 = (c.description || '').match(/\{\{(\d+)\}\}/g) || [];
2281
- [...s1, ...s2].forEach((tok) => {
2282
- const n = parseInt((tok.match(/\d+/) || [])[0], 10);
2283
- if (!Number.isNaN(n)) nums.push(n);
2284
- });
2285
- });
2286
- const existing = new Set(nums);
2287
- let nextNumber = 1;
2288
- while (existing.has(nextNumber)) nextNumber++;
2289
- if (nextNumber > 19) return '';
2290
- return `{{${nextNumber}}}`;
2291
- };
2292
-
2293
- const appendVarToCarouselField = (cardIndex, fieldName) => {
2294
- const token = getNextCarouselVarToken();
2295
- if (!token) return;
2296
- setCarouselData((prev = []) => {
2297
- const updated = cloneDeep(prev);
2298
- if (!updated[cardIndex]) return prev;
2299
- const current = (updated[cardIndex][fieldName] || '').toString();
2300
- updated[cardIndex][fieldName] = `${current}${token}`;
2301
- return updated;
2302
- });
2303
- };
992
+
993
+ const splitTemplateVarString = (str) => {
994
+ if (!str) return [];
995
+ const validVarArr = str.match(rcsVarRegex) || [];
996
+ const templateVarArray = [];
997
+ let content = str;
998
+ while (content?.length !== 0) {
999
+ const index = content.indexOf(validVarArr?.[0]);
1000
+ if (index !== -1) {
1001
+ templateVarArray.push(content.substring(0, index));
1002
+ templateVarArray.push(validVarArr?.[0]);
1003
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length);
1004
+ validVarArr?.shift();
1005
+ } else {
1006
+ templateVarArray.push(content);
1007
+ break;
1008
+ }
1009
+ }
1010
+ return templateVarArray.filter(Boolean);
1011
+ };
2304
1012
 
2305
1013
  const textAreaValue = (idValue, type) => {
2306
1014
  if (idValue >= 0) {
@@ -2316,46 +1024,6 @@ const onTitleAddVar = () => {
2316
1024
  return "";
2317
1025
  };
2318
1026
 
2319
- // Carousel: render variable-value editor for a given template string (title/description).
2320
- // This matches rich-card/text edit behavior: static pieces are read-only, variable tokens are editable.
2321
- const renderCarouselEditMessage = (templateStr) => {
2322
- const renderArray = [];
2323
- const templateArr = splitTemplateVarString(templateStr);
2324
- if (templateArr?.length) {
2325
- templateArr.forEach((elem, index) => {
2326
- if (rcsVarTestRegex.test(elem)) {
2327
- const varName = getVarNameFromToken(elem);
2328
- renderArray.push(
2329
- <div key={`${elem}_${index}`} className="var-segment-message-editor__var-slot">
2330
- <TextArea
2331
- id={`${elem}_${index}`}
2332
- placeholder={`enter the value for ${elem}`}
2333
- autosize={{ minRows: 1, maxRows: 3 }}
2334
- onChange={(e) => textAreaValueChange(e, TITLE_TEXT)}
2335
- value={varName ? ((cardVarMapped?.[varName] ?? '').toString()) : ''}
2336
- onFocus={(e) => {
2337
- const id = e?.target?.id || e?.currentTarget?.id || '';
2338
- setCarouselFocusedVarId(id);
2339
- }}
2340
- />
2341
- </div>
2342
- );
2343
- } else if (elem) {
2344
- renderArray.push(
2345
- <CapHeading
2346
- key={`static_${index}`}
2347
- type="h4"
2348
- className="rcs-edit-template-message-split"
2349
- >
2350
- {elem}
2351
- </CapHeading>
2352
- );
2353
- }
2354
- });
2355
- }
2356
- return <CapRow className="rcs-edit-template-message-input">{renderArray}</CapRow>;
2357
- };
2358
-
2359
1027
  const textAreaValueChange = (e, type) => {
2360
1028
  const value = e?.target?.value ?? '';
2361
1029
  const id = e?.target?.id || e?.currentTarget?.id || '';
@@ -2375,9 +1043,7 @@ const onTitleAddVar = () => {
2375
1043
  };
2376
1044
 
2377
1045
  const setTextAreaId = (e, type) => {
2378
- // VarSegmentMessageEditor calls onFocus(id) with a plain string; DOM events
2379
- // have an `.target.id` shape. Support both.
2380
- const id = typeof e === 'string' ? e : (e?.target?.id || e?.currentTarget?.id || '');
1046
+ const id = e?.target?.id || e?.currentTarget?.id || '';
2381
1047
  if (!id) return;
2382
1048
  if (type === TITLE_TEXT) setTitleTextAreaId(id);
2383
1049
  else setDescTextAreaId(id);
@@ -2408,71 +1074,44 @@ const onTitleAddVar = () => {
2408
1074
  isEditFlow={isEditFlow}
2409
1075
  isFullMode={isFullMode}
2410
1076
  maxButtons={MAX_BUTTONS}
2411
- host={hostName}
2412
- />
1077
+ />
2413
1078
  </>
2414
1079
  );
2415
1080
  };
2416
1081
 
2417
- const getRcsValueMap = (fieldTemplateString, fieldType) => {
2418
- if (!fieldTemplateString) return {};
2419
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
2420
- const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
2421
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
2422
- const segmentIdToResolvedValue = {};
2423
- let varOrdinal = 0;
2424
- templateSegments.forEach((segmentToken, segmentIndexInField) => {
2425
- if (rcsVarTestRegex.test(segmentToken)) {
2426
- const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
2427
- const varName = getVarNameFromToken(segmentToken);
2428
- const globalSlot = slotOffset + varOrdinal;
2429
- varOrdinal += 1;
2430
- segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
2431
- cardVarMapped,
2432
- varName,
2433
- globalSlot,
2434
- isEditLike,
2435
- rcsSpanningSemanticVarNames.has(varName),
2436
- );
2437
- }
2438
- });
2439
- return segmentIdToResolvedValue;
2440
- };
2441
-
2442
- const titleVarSegmentValueMapById = useMemo(
2443
- () => getRcsValueMap(templateTitle, TITLE_TEXT),
2444
- [templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
2445
- );
2446
- const descriptionVarSegmentValueMapById = useMemo(
2447
- () => getRcsValueMap(templateDesc, MESSAGE_TEXT),
2448
- [templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
2449
- );
2450
-
2451
- const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
2452
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
2453
- if (underscoreIndexInCompositeId === -1) return;
2454
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
2455
- const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
2456
- if (variableName === undefined || variableName === null || variableName === '') return;
2457
- const isInvalidValue = value?.trim() === '';
2458
- const coercedSlotValue = isInvalidValue ? '' : value;
2459
- const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
2460
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
2461
- varSegmentCompositeDomId,
2462
- templateStringForField,
2463
- type,
2464
- );
2465
- setCardVarMapped((previousCardVarMapped) => {
2466
- const updatedCardVarMapped = { ...previousCardVarMapped };
2467
- // Remove stale semantic key: keeping it causes every other slot sharing the same
2468
- // variable name (e.g. {{adv}} in both title and description) to read the same value
2469
- // via the semantic-key fallback in resolveCardVarMappedSlotValue.
2470
- delete updatedCardVarMapped[variableName];
2471
- if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
2472
- updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
2473
- }
2474
- return updatedCardVarMapped;
2475
- });
1082
+ const renderedRCSEditMessage = (descArray, type) => {
1083
+ const renderArray = [];
1084
+ if (descArray?.length) {
1085
+ descArray.forEach((elem, index) => {
1086
+ if (rcsVarTestRegex.test(elem)) {
1087
+ // Variable input
1088
+ renderArray.push(
1089
+ <TextArea
1090
+ id={`${elem}_${index}`}
1091
+ key={`${elem}_${index}`}
1092
+ placeholder={`enter the value for ${elem}`}
1093
+ autosize={{ minRows: 1, maxRows: 3 }}
1094
+ onChange={e => textAreaValueChange(e, type)}
1095
+ value={textAreaValue(index, type)}
1096
+ onFocus={(e) => setTextAreaId(e, type)}
1097
+ />
1098
+ );
1099
+ } else if (elem) {
1100
+ // Static text
1101
+ renderArray.push(
1102
+ <TextArea
1103
+ key={`static_${index}`}
1104
+ value={elem}
1105
+ autosize={{ minRows: 1, maxRows: 3 }}
1106
+ disabled
1107
+ className="rcs-edit-template-message-static-textarea"
1108
+ style={{ background: '#fafafa', color: '#888' }}
1109
+ />
1110
+ );
1111
+ }
1112
+ });
1113
+ }
1114
+ return renderArray;
2476
1115
  };
2477
1116
 
2478
1117
  const renderTextComponent = () => {
@@ -2492,7 +1131,6 @@ const onTitleAddVar = () => {
2492
1131
  }
2493
1132
  suffix={
2494
1133
  <>
2495
-
2496
1134
  {(isEditFlow || !isFullMode) ? (
2497
1135
  <TagList
2498
1136
  label={formatMessage(globalMessages.addLabels)}
@@ -2509,28 +1147,18 @@ const onTitleAddVar = () => {
2509
1147
  type="flat"
2510
1148
  isAddBtn
2511
1149
  onClick={onTitleAddVar}
2512
- disabled={templateTitleError}
1150
+ disabled={!!templateTitleError}
2513
1151
  >
2514
1152
  {formatMessage(messages.addVar)}
2515
1153
  </CapButton>
2516
- )}
1154
+ )}
2517
1155
  </>
2518
- }
2519
- />
2520
-
2521
- {(isEditFlow || !isFullMode) ? (
2522
- <VarSegmentMessageEditor
2523
- key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
2524
- templateString={templateTitle}
2525
- valueMap={titleVarSegmentValueMapById}
2526
- onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
2527
- onFocus={(id) => setTitleTextAreaId(id)}
2528
- varRegex={rcsVarRegex}
2529
- placeholderPrefix=""
2530
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1156
+ }
2531
1157
  />
1158
+ <div className="rcs_text_area_wrapper">
1159
+ {(isEditFlow || !isFullMode) ? (
1160
+ renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
2532
1161
  ) : (
2533
- <div className="rcs_text_area_wrapper">
2534
1162
  <CapInput
2535
1163
  className={`rcs-template-title-input ${
2536
1164
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -2543,8 +1171,8 @@ const onTitleAddVar = () => {
2543
1171
  errorMessage={templateTitleError}
2544
1172
  disabled={isEditFlow || !isFullMode}
2545
1173
  />
2546
- </div>
2547
1174
  )}
1175
+ </div>
2548
1176
  {(isEditFlow || !isFullMode) && templateTitleError && (
2549
1177
  <CapError className="rcs-template-title-error">
2550
1178
  {templateTitleError}
@@ -2552,7 +1180,7 @@ const onTitleAddVar = () => {
2552
1180
  )}
2553
1181
  {!isEditFlow && isFullMode && renderTitleCharacterCount()}
2554
1182
  </>
2555
- )}
1183
+ )}
2556
1184
 
2557
1185
  {/* Template Message */}
2558
1186
  <CapRow id="rcs-template-message-label">
@@ -2590,21 +1218,9 @@ const onTitleAddVar = () => {
2590
1218
  />
2591
1219
  </CapRow>
2592
1220
  <CapRow className="rcs-create-template-message-input">
2593
- {/* 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. */}
2594
- <CapRow className="rcs_text_area_wrapper">
2595
- {(isEditFlow || !isFullMode)?
2596
- (
2597
- <VarSegmentMessageEditor
2598
- key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
2599
- templateString={templateDesc}
2600
- valueMap={descriptionVarSegmentValueMapById}
2601
- onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
2602
- onFocus={(id) => setDescTextAreaId(id)}
2603
- varRegex={rcsVarRegex}
2604
- placeholderPrefix=""
2605
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
2606
- />
2607
- )
1221
+ <div className="rcs_text_area_wrapper">
1222
+ {(isEditFlow || !isFullMode)
1223
+ ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
2608
1224
  : (
2609
1225
  <>
2610
1226
  <TextArea
@@ -2642,15 +1258,13 @@ const onTitleAddVar = () => {
2642
1258
  </>
2643
1259
  )
2644
1260
  }
2645
- </CapRow>
1261
+ </div>
2646
1262
  {(isEditFlow || !isFullMode) && templateDescError && (
2647
1263
  <CapError className="rcs-template-message-error">
2648
1264
  {templateDescError}
2649
1265
  </CapError>
2650
1266
  )}
2651
- {(isEditFlow || !isFullMode)
2652
- ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
2653
- : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1267
+ {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
2654
1268
  {!isFullMode && hasTag() && (
2655
1269
  <CapAlert
2656
1270
  message={
@@ -2664,13 +1278,24 @@ const onTitleAddVar = () => {
2664
1278
  />
2665
1279
  )}
2666
1280
  </CapRow>
2667
- {((!isEditFlow && isFullMode) || (isEditFlow && (suggestions?.length ?? 0) > 0)) &&
2668
- renderButtonComponent()}
1281
+ {renderButtonComponent()}
2669
1282
  </>
2670
1283
 
2671
1284
  );
2672
1285
  };
2673
1286
 
1287
+
1288
+ const fallbackSmsLength = () => (
1289
+ <CapLabel type="label1" className="fallback-sms-length">
1290
+ {formatMessage(messages.totalCharacters, {
1291
+ smsCount: Math.ceil(
1292
+ fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1293
+ ),
1294
+ number: fallbackMessage?.length,
1295
+ })}
1296
+ </CapLabel>
1297
+ );
1298
+
2674
1299
  // Get character count for title (rich card only)
2675
1300
  const getTitleCharacterCount = () => {
2676
1301
  if (templateType === contentType.text_message) return 0;
@@ -2685,7 +1310,7 @@ const onTitleAddVar = () => {
2685
1310
  // Get max length for description based on template type
2686
1311
  const getDescriptionMaxLength = () => {
2687
1312
  return templateType === contentType.text_message
2688
- ? (isHostInfoBip ? RCS_TEXT_MESSAGE_MAX_LENGTH_INFOBIP : RCS_TEXT_MESSAGE_MAX_LENGTH) // 160 for text message
1313
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH // 160 for text message
2689
1314
  : RCS_RICH_CARD_MAX_LENGTH; // 2000 for rich card description
2690
1315
  };
2691
1316
 
@@ -2708,64 +1333,7 @@ const onTitleAddVar = () => {
2708
1333
  maxLength,
2709
1334
  })}
2710
1335
  </CapLabel>
2711
- );
2712
- };
2713
-
2714
- const rcsDltCardDeleteHandler = () => {
2715
- closeDltContainerHandler();
2716
- setDltEditData({});
2717
- setFallbackMessage('');
2718
- setFallbackMessageError(false);
2719
- setShowDltCard(false);
2720
- };
2721
-
2722
- const dltFallbackListingPreviewhandler = (data) => {
2723
- const {
2724
- 'updated-sms-editor': updatedSmsEditor = [],
2725
- 'sms-editor': smsEditor = '',
2726
- } = data.versions.base || {};
2727
- setFallbackPreviewmode(true);
2728
- setDltPreviewData(
2729
- updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
2730
- );
2731
- };
2732
-
2733
- const getDltContentCardList = (content, channel) => {
2734
- const extra = [
2735
- <CapIcon
2736
- type="edit"
2737
- style={{ marginRight: '8px' }}
2738
- onClick={() => rcsDltEditSelectHandler(dltEditData)}
2739
- />,
2740
- <CapDropdown
2741
- overlay={(
2742
- <CapMenu>
2743
- <>
2744
- <CapMenu.Item
2745
- className="ant-dropdown-menu-item"
2746
- onClick={() => setFallbackPreviewmode(true)}
2747
- >
2748
- {formatMessage(globalMessages.preview)}
2749
- </CapMenu.Item>
2750
- <CapMenu.Item
2751
- className="ant-dropdown-menu-item"
2752
- onClick={rcsDltCardDeleteHandler}
2753
- >
2754
- {formatMessage(globalMessages.delete)}
2755
- </CapMenu.Item>
2756
- </>
2757
- </CapMenu>
2758
- )}
2759
- >
2760
- <CapIcon type="more" />
2761
- </CapDropdown>,
2762
- ];
2763
- return {
2764
- title: channel,
2765
- content,
2766
- cardType: channel,
2767
- extra,
2768
- };
1336
+ );
2769
1337
  };
2770
1338
 
2771
1339
  // Render character count for description/message
@@ -2783,26 +1351,6 @@ const onTitleAddVar = () => {
2783
1351
  );
2784
1352
  };
2785
1353
 
2786
- // Carousel: per-card character counts (same limits as rich card)
2787
- const getCarouselTitleCharacterCount = (cardIndex) => {
2788
- const t = carouselData?.[cardIndex]?.title || '';
2789
- return t ? t.length : 0;
2790
- };
2791
-
2792
- const getCarouselDescriptionCharacterCount = (cardIndex) => {
2793
- const d = carouselData?.[cardIndex]?.description || '';
2794
- return d ? d.length : 0;
2795
- };
2796
-
2797
- const renderCarouselCharacterCount = (currentLength, maxLength, className = "rcs-character-count") => (
2798
- <CapLabel type="label1" className={className}>
2799
- {formatMessage(messages.templateMessageLength, {
2800
- currentLength,
2801
- maxLength,
2802
- })}
2803
- </CapLabel>
2804
- );
2805
-
2806
1354
  // Check if any RCS variables contain tags (similar to Zalo hasTag logic)
2807
1355
  const hasTag = () => {
2808
1356
  // Check cardVarMapped values for tags
@@ -2855,17 +1403,68 @@ const onTitleAddVar = () => {
2855
1403
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
2856
1404
  '',
2857
1405
  );
2858
- const templateNameFromDlt = get(dltEditData, 'name', '')
2859
- || get(tempData, 'versions.base.name', '')
2860
- || '';
2861
1406
  closeDltContainerHandler();
2862
1407
  setDltEditData(tempData);
2863
- const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
2864
- setSmsFallbackData({
2865
- templateName: templateNameFromDlt,
2866
- content: fallMsg,
2867
- ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
2868
- });
1408
+ setFallbackMessage(fallMsg);
1409
+ setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1410
+ setShowDltCard(true);
1411
+ };
1412
+
1413
+ const rcsDltCardDeleteHandler = () => {
1414
+ closeDltContainerHandler();
1415
+ setDltEditData({});
1416
+ setFallbackMessage('');
1417
+ setFallbackMessageError(false);
1418
+ setShowDltCard(false);
1419
+ };
1420
+
1421
+ const dltFallbackListingPreviewhandler = (data) => {
1422
+ const {
1423
+ 'updated-sms-editor': updatedSmsEditor = [],
1424
+ 'sms-editor': smsEditor = '',
1425
+ } = data.versions.base || {};
1426
+ setFallbackPreviewmode(true);
1427
+ setDltPreviewData(
1428
+ updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1429
+ );
1430
+ };
1431
+
1432
+ const getDltContentCardList = (content, channel) => {
1433
+ const extra = [
1434
+ <CapIcon
1435
+ type="edit"
1436
+ style={{ marginRight: '8px' }}
1437
+ onClick={() => rcsDltEditSelectHandler(dltEditData)}
1438
+ />,
1439
+ <CapDropdown
1440
+ overlay={(
1441
+ <CapMenu>
1442
+ <>
1443
+ <CapMenu.Item
1444
+ className="ant-dropdown-menu-item"
1445
+ onClick={() => setFallbackPreviewmode(true)}
1446
+ >
1447
+ {formatMessage(globalMessages.preview)}
1448
+ </CapMenu.Item>
1449
+ <CapMenu.Item
1450
+ className="ant-dropdown-menu-item"
1451
+ onClick={rcsDltCardDeleteHandler}
1452
+ >
1453
+ {formatMessage(globalMessages.delete)}
1454
+ </CapMenu.Item>
1455
+ </>
1456
+ </CapMenu>
1457
+ )}
1458
+ >
1459
+ <CapIcon type="more" />
1460
+ </CapDropdown>,
1461
+ ];
1462
+ return {
1463
+ title: channel,
1464
+ content,
1465
+ cardType: channel,
1466
+ extra,
1467
+ };
2869
1468
  };
2870
1469
 
2871
1470
  const getDltSlideBoxContent = () => {
@@ -2913,34 +1512,148 @@ const onTitleAddVar = () => {
2913
1512
  return { dltHeader, dltContent };
2914
1513
  };
2915
1514
 
2916
- const renderFallBackSmsComponent = () => (
2917
- <SmsFallback
2918
- value={smsFallbackData}
2919
- onChange={setSmsFallbackData}
2920
- parentLocation={location}
2921
- smsRegister={smsRegister}
2922
- isFullMode={isFullMode}
2923
- selectedOfferDetails={selectedOfferDetails}
2924
- channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
2925
- sectionTitle={
2926
- smsFallbackData
2927
- ? formatMessage(messages.fallbackLabel)
2928
- : formatMessage(messages.smsFallbackOptional)
2929
- }
2930
- templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
2931
- templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
2932
- /* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
2933
- showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
2934
- disableSelectTemplate={isEditFlow}
2935
- eventContextTags={eventContextTags}
2936
- onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
2937
- isRcsEditFlow={isEditFlow}
2938
- />
2939
- );
1515
+ const renderFallBackSmsComponent = () => {
1516
+ // Completely disable fallback functionality when DLT is disabled
1517
+ return null;
1518
+
1519
+ // const contentArr = [];
1520
+ // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1521
+ // const showCardForDlt = isDltEnabled && showDltCard;
1522
+ // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1523
+ // //pushing common fallback sms headings
1524
+ // contentArr.push(
1525
+ // <CapRow
1526
+ // style={{
1527
+ // marginBottom: isDltEnabled ? '20px' : '10px',
1528
+ // }}
1529
+ // >
1530
+ // <CapHeader
1531
+ // title={(
1532
+ // <CapRow type="flex">
1533
+ // <CapHeading type="h4">
1534
+ // {formatMessage(messages.fallbackLabel)}
1535
+ // </CapHeading>
1536
+ // <CapTooltipWithInfo
1537
+ // placement="right"
1538
+ // infoIconProps={{
1539
+ // style: { marginLeft: '4px', marginTop: '3px' },
1540
+ // }}
1541
+ // title={formatMessage(messages.fallbackToolTip)}
1542
+ // />
1543
+ // </CapRow>
1544
+ // )}
1545
+ // description={formatMessage(messages.fallbackDesc)}
1546
+ // suffix={
1547
+ // isDltEnabled ? null : (
1548
+ // <CapButton
1549
+ // type="flat"
1550
+ // className="fallback-preview-btn"
1551
+ // prefix={<CapIcon type="eye" />}
1552
+ // style={{ color: CAP_SECONDARY.base }}
1553
+ // onClick={() => setFallbackPreviewmode(true)}
1554
+ // disabled={fallbackMessage === '' || fallbackMessageError}
1555
+ // >
1556
+ // {formatMessage(globalMessages.preview)}
1557
+ // </CapButton>
1558
+ // )
1559
+ // }
1560
+ // />
1561
+ // </CapRow>,
1562
+ // );
1563
+
1564
+ //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1565
+ // showAddCreativeBtnForDlt
1566
+ // && contentArr.push(
1567
+ // <CapCard className="rcs-dlt-fallback-card">
1568
+ // <CapRow type="flex" justify="center" align="middle">
1569
+ // <CapColumn span={10}>
1570
+ // <CapImage src={addCreativesIcon} />
1571
+ // </CapColumn>
1572
+ // <CapColumn span={14}>
1573
+ // <CapButton
1574
+ // className="add-dlt-btn"
1575
+ // type="secondary"
1576
+ // onClick={addDltMsgHandler}
1577
+ // >
1578
+ // {formatMessage(messages.addSmsCreative)}
1579
+ // </CapButton>
1580
+ // </CapColumn>
1581
+ // </CapRow>
1582
+ // </CapCard>,
1583
+ // );
1584
+
1585
+ // //dlt is enabled and dlt content is added, show it in a card
1586
+ // showCardForDlt
1587
+ // && contentArr.push(
1588
+ // <CapCustomCardList
1589
+ // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1590
+ // className="rcs-dlt-card"
1591
+ // />,
1592
+ // fallbackMessageError && (
1593
+ // <CapError className="rcs-fallback-len-error">
1594
+ // {formatMessage(messages.fallbackMsgLenError)}
1595
+ // </CapError>
1596
+ // ),
1597
+ // );
1598
+
1599
+ // //dlt is not enabled, show non dlt text area
1600
+ // showNonDltFallbackComp
1601
+ // && contentArr.push(
1602
+ // <>
1603
+ // <CapRow>
1604
+ // <CapHeader
1605
+ // title={(
1606
+ // <CapHeading type="h4">
1607
+ // {formatMessage(messages.fallbackTextAreaLabel)}
1608
+ // </CapHeading>
1609
+ // )}
1610
+ // suffix={(
1611
+ // <TagList
1612
+ // label={formatMessage(globalMessages.addLabels)}
1613
+ // onTagSelect={onTagSelectFallback}
1614
+ // location={location}
1615
+ // tags={tags || []}
1616
+ // onContextChange={handleOnTagsContextChange}
1617
+ // injectedTags={injectedTags || {}}
1618
+ // selectedOfferDetails={selectedOfferDetails}
1619
+ // />
1620
+ // )}
1621
+ // />
1622
+ // </CapRow>
1623
+ // <div className="rcs_fallback_msg_textarea_wrapper">
1624
+ // <TextArea
1625
+ // id="rcs_fallback_message_textarea"
1626
+ // autosize={{ minRows: 3, maxRows: 5 }}
1627
+ // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1628
+ // onChange={onFallbackMessageChange}
1629
+ // errorMessage={fallbackMessageError}
1630
+ // value={fallbackMessage || ""}
1631
+ // />
1632
+ // {!aiContentBotDisabled && (
1633
+ // <CapAskAira.ContentGenerationBot
1634
+ // text={fallbackMessage || ""}
1635
+ // setText={(text) => {
1636
+ // onFallbackMessageChange({ target: { value: text } });
1637
+ // }}
1638
+ // iconPlacement="float-br"
1639
+ // rootStyle={{
1640
+ // bottom: "0.5rem",
1641
+ // right: "0.5rem",
1642
+ // position: "absolute",
1643
+ // }}
1644
+ // />
1645
+ // )}
1646
+ // </div>
1647
+ // <CapRow>{fallbackSmsLength()}</CapRow>
1648
+ // </>
1649
+ // );
1650
+
1651
+ // return <>{contentArr}</>;
1652
+ };
2940
1653
 
2941
1654
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
2942
1655
  setImageError(null);
2943
- const isRcsThumbnail = isThumbnailAssetIndex(index);
1656
+ const isRcsThumbnail = index === 1;
2944
1657
  actions.uploadRcsAsset(file, type, {
2945
1658
  isRcsThumbnail,
2946
1659
  ...fileParams,
@@ -2978,7 +1691,10 @@ const onTitleAddVar = () => {
2978
1691
  const updateOnRcsImageReUpload = useCallback(() => {
2979
1692
  setUpdateRcsImageSrc('');
2980
1693
  }, []);
1694
+
1695
+
2981
1696
  const uploadRcsVideo = (file, type, fileParams) => {
1697
+ setImageError(null);
2982
1698
  actions.uploadRcsAsset(file, type, {
2983
1699
  ...fileParams,
2984
1700
  type: 'video',
@@ -2987,6 +1703,9 @@ const onTitleAddVar = () => {
2987
1703
  });
2988
1704
  };
2989
1705
 
1706
+ const updateRcsVideoSrc = (val) => {
1707
+ setRcsVideoSrc(val);
1708
+ };
2990
1709
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
2991
1710
  setRcsVideoSrc(val);
2992
1711
  setAssetList(val);
@@ -3009,7 +1728,7 @@ const onTitleAddVar = () => {
3009
1728
  updateRcsThumbnailSrc('');
3010
1729
  };
3011
1730
 
3012
- const renderThumbnailComponent = () => {
1731
+ const renderThumbnailComponent = () => {
3013
1732
  const currentDimension = selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
3014
1733
  return !isEditFlow && (
3015
1734
  <>
@@ -3033,7 +1752,6 @@ const onTitleAddVar = () => {
3033
1752
  channel={RCS}
3034
1753
  channelSpecificStyle={!isFullMode}
3035
1754
  skipDimensionValidation={true}
3036
- showReUploadButton={!isEditFlow && isFullMode}
3037
1755
  />
3038
1756
  </>
3039
1757
  )
@@ -3059,7 +1777,7 @@ const onTitleAddVar = () => {
3059
1777
  value: dim.type,
3060
1778
  label: `${dim.label}`
3061
1779
  }))}
3062
- className="rcs-dimension-select--bottom-spacing"
1780
+ style={{ marginBottom: '20px' }}
3063
1781
  />
3064
1782
  </>
3065
1783
  )}
@@ -3073,7 +1791,7 @@ const onTitleAddVar = () => {
3073
1791
  </div>
3074
1792
  ) : (
3075
1793
  <CapImageUpload
3076
- style={{ paddingTop: '20px' }}
1794
+ style={{ paddingTop: '20px' }}
3077
1795
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
3078
1796
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
3079
1797
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -3084,7 +1802,7 @@ const onTitleAddVar = () => {
3084
1802
  updateImageSrc={setUpdateRcsImageSrc}
3085
1803
  updateOnReUpload={updateOnRcsImageReUpload}
3086
1804
  index={0}
3087
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1805
+ className="cap-custom-image-upload"
3088
1806
  key={`rcs-uploaded-image-${selectedDimension}`}
3089
1807
  imageData={rcsData}
3090
1808
  channel={RCS}
@@ -3095,7 +1813,7 @@ const onTitleAddVar = () => {
3095
1813
 
3096
1814
  </>
3097
1815
  );
3098
- }
1816
+ }
3099
1817
 
3100
1818
  const renderVideoComponent = () => {
3101
1819
  const currentDimension =selectedDimension || Object.keys(RCS_VIDEO_THUMBNAIL_DIMENSIONS)[0];
@@ -3114,7 +1832,7 @@ const onTitleAddVar = () => {
3114
1832
  value: dim.type,
3115
1833
  label: `${dim.label}`
3116
1834
  }))}
3117
- className="rcs-dimension-select--bottom-spacing"
1835
+ style={{ marginBottom: '20px' }}
3118
1836
  />
3119
1837
  )}
3120
1838
  {(isEditFlow || !isFullMode) ? (
@@ -3172,48 +1890,10 @@ const onTitleAddVar = () => {
3172
1890
  };
3173
1891
 
3174
1892
  const getRcsPreview = () => {
3175
-
3176
- if (templateType === contentType.carousel) {
3177
- const cardsForPreview = buildCarouselCardsForPreview(carouselData);
3178
- const carouselDimKey = getCarouselDimensionKey();
3179
- const carouselImgDims =
3180
- RCS_CAROUSEL_IMAGE_DIMENSIONS[carouselDimKey] || RCS_CAROUSEL_IMAGE_DIMENSIONS.MEDIUM_MEDIUM;
3181
- const carouselVidDims =
3182
- RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS[carouselDimKey]
3183
- || RCS_CAROUSEL_VIDEO_THUMBNAIL_DIMENSIONS.MEDIUM_MEDIUM;
3184
- // Debug log for embedded/library mode preview payload (carousel)
3185
- // eslint-disable-next-line no-console
3186
- return (
3187
- <UnifiedPreview
3188
- channel={RCS}
3189
- content={{
3190
- carouselData: cardsForPreview,
3191
- carouselPreviewDimensions: {
3192
- imageWidth: carouselImgDims.width,
3193
- imageHeight: carouselImgDims.height,
3194
- videoThumbWidth: carouselVidDims.width,
3195
- videoThumbHeight: carouselVidDims.height,
3196
- },
3197
- }}
3198
- device={ANDROID}
3199
- showDeviceToggle={false}
3200
- showHeader={false}
3201
- formatMessage={formatMessage}
3202
- />
3203
- );
3204
- }
3205
1893
 
3206
1894
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
3207
- const isSlotMappingMode = isEditFlow || !isFullMode;
3208
- const titleVarCountForResolve = isMediaTypeText
3209
- ? 0
3210
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
3211
- const resolvedTitle = isMediaTypeText
3212
- ? ''
3213
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
3214
- const resolvedDesc = isSlotMappingMode
3215
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
3216
- : templateDesc;
1895
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1896
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
3217
1897
  return (
3218
1898
  <UnifiedPreview
3219
1899
  channel={RCS}
@@ -3236,65 +1916,51 @@ const onTitleAddVar = () => {
3236
1916
  );
3237
1917
  };
3238
1918
 
1919
+ const getUnmappedDesc = (str, mapping) => {
1920
+ if (!str) return '';
1921
+ if (!mapping || Object.keys(mapping).length === 0) return str;
1922
+ let result = str;
1923
+ const replacements = [];
1924
+ Object.entries(mapping).forEach(([key, value]) => {
1925
+ const raw = (value ?? '').toString();
1926
+ if (!raw || raw?.trim?.() === '') return;
1927
+ const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1928
+ replacements.push({ key, needle: raw });
1929
+ if (braced !== raw) replacements.push({ key, needle: braced });
1930
+ });
1931
+ const seen = new Set();
1932
+ const uniq = replacements
1933
+ .filter(({ key, needle }) => {
1934
+ const id = `${key}::${needle}`;
1935
+ if (seen.has(id)) return false;
1936
+ seen.add(id);
1937
+ return true;
1938
+ })
1939
+ .sort((a, b) => (b.needle.length - a.needle.length));
1940
+
1941
+ uniq.forEach(({ key, needle }) => {
1942
+ if (!needle) return;
1943
+ const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1944
+ const regex = new RegExp(escaped, 'g');
1945
+ result = result.replace(regex, `{{${key}}}`);
1946
+ });
1947
+ return result;
1948
+ };
1949
+
3239
1950
  const createPayload = () => {
3240
- const isSlotMappingMode = isEditFlow || !isFullMode;
1951
+ const base = get(dltEditData, `versions.base`, {});
1952
+ const {
1953
+ template_id: templateId = '',
1954
+ template_name = '',
1955
+ 'sms-editor': template = '',
1956
+ header: registeredSenderIds = [],
1957
+ } = base;
1958
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1959
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
3241
1960
  const alignment = isMediaTypeImage
3242
1961
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
3243
1962
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
3244
1963
 
3245
- const heightTypeForCardWidth = isMediaTypeText
3246
- ? undefined
3247
- : isMediaTypeImage
3248
- ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
3249
- : isMediaTypeVideo
3250
- ? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
3251
- : undefined;
3252
- const cardWidthFromSelection =
3253
- heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
3254
-
3255
- /** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
3256
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
3257
- const smsFallbackMerged = !isFullMode
3258
- ? (() => {
3259
- const local =
3260
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
3261
- return {
3262
- ...smsFromApiShape,
3263
- ...local,
3264
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
3265
- };
3266
- })()
3267
- : (smsFallbackData || {});
3268
- const smsFallbackForPayload = (() => {
3269
- if (isFullMode) {
3270
- return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
3271
- }
3272
- const mapped = {
3273
- templateName:
3274
- smsFallbackMerged.templateName
3275
- || smsFallbackMerged.smsTemplateName
3276
- || '',
3277
- // Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
3278
- content:
3279
- smsFallbackMerged.content
3280
- || smsFallbackMerged.smsContent
3281
- || smsFallbackMerged.smsTemplateContent
3282
- || smsFallbackMerged.message
3283
- || '',
3284
- templateContent:
3285
- pickFirstSmsFallbackTemplateString(smsFallbackMerged)
3286
- || '',
3287
- ...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
3288
- && { unicodeValidity: smsFallbackMerged.unicodeValidity }),
3289
- ...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
3290
- && Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
3291
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
3292
- smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
3293
- }),
3294
- };
3295
- return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
3296
- })();
3297
-
3298
1964
  const payload = {
3299
1965
  name: templateName,
3300
1966
  versions: {
@@ -3302,18 +1968,17 @@ const onTitleAddVar = () => {
3302
1968
  content: {
3303
1969
  RCS: {
3304
1970
  rcsContent: {
1971
+ ...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
3305
1972
  cardType: STANDALONE,
3306
1973
  cardSettings: {
3307
1974
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
3308
1975
  ...(alignment && { mediaAlignment: alignment }),
3309
- cardWidth: cardWidthFromSelection,
1976
+ cardWidth: SMALL,
3310
1977
  },
3311
1978
  cardContent: [
3312
1979
  {
3313
- // Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
3314
- // / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
3315
- title: templateTitle,
3316
- description: templateDesc,
1980
+ title: resolvedTitle,
1981
+ description: resolvedDesc,
3317
1982
  mediaType: templateMediaType,
3318
1983
  ...(!isMediaTypeText && {media: {
3319
1984
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -3322,32 +1987,23 @@ const onTitleAddVar = () => {
3322
1987
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
3323
1988
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
3324
1989
  }}),
3325
- ...(isSlotMappingMode && (() => {
3326
- const templateVarTokens = [
3327
- ...(templateTitle?.match(rcsVarRegex) ?? []),
3328
- ...(templateDesc?.match(rcsVarRegex) ?? []),
1990
+ ...(!isFullMode && (() => {
1991
+ const tokens = [
1992
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1993
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
3329
1994
  ];
3330
- const cardVarMappedForRcsCardOnly = pickRcsCardVarMappedEntries(
3331
- cardVarMapped,
3332
- );
3333
- // Persist numeric slot keys only ("1","2",…) — avoids duplicating the same value under
3334
- // semantic names (e.g. both "1" and dynamic_expiry_date_after_3_days.FORMAT_1). Hydration
3335
- // and coalesceCardVarMappedToTemplate still resolve from numeric keys + template tokens.
3336
- const persistedSlotVarMap = {};
3337
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
3338
- const varName = getVarNameFromToken(token);
3339
- if (!varName) return;
3340
- const resolvedRawValue = resolveCardVarMappedSlotValue(
3341
- cardVarMappedForRcsCardOnly,
3342
- varName,
3343
- slotIndexZeroBased,
3344
- isSlotMappingMode,
3345
- rcsSpanningSemanticVarNames.has(varName),
3346
- );
3347
- const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
3348
- persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
1995
+ const allowedKeys = tokens
1996
+ .map((t) => getVarNameFromToken(t))
1997
+ .filter(Boolean);
1998
+ const nextMap = {};
1999
+ allowedKeys.forEach((k) => {
2000
+ if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
2001
+ nextMap[k] = cardVarMapped[k];
2002
+ } else {
2003
+ nextMap[k] = '';
2004
+ }
3349
2005
  });
3350
- return { cardVarMapped: persistedSlotVarMap };
2006
+ return { cardVarMapped: nextMap };
3351
2007
  })()),
3352
2008
  ...(suggestions.length > 0 && { suggestions }),
3353
2009
  }
@@ -3355,88 +2011,17 @@ const onTitleAddVar = () => {
3355
2011
  contentType: isFullMode ? templateType : RICHCARD,
3356
2012
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
3357
2013
  },
3358
- ...(smsFallbackForPayload && (() => {
3359
- const smsBodyText =
3360
- smsFallbackForPayload.content
3361
- || smsFallbackForPayload.templateContent
3362
- || smsFallbackForPayload.message
3363
- || smsFallbackForPayload.smsContent
3364
- || '';
3365
- /**
3366
- * Campaigns `getTraiSenderIds` / Iris read `smsFallBackContent.templateConfigs.registeredSenderIds`.
3367
- * Library `smsFallbackForPayload` omits ids — use merged state (`smsFallbackMerged`) like test preview.
3368
- */
3369
- const m = smsFallbackMerged || {};
3370
- const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
3371
- ? m.templateConfigs
3372
- : {};
3373
- const smsFallbackTemplateId =
3374
- (m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
3375
- ? String(m.smsTemplateId)
3376
- : '')
3377
- || (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
3378
- ? String(tcSibling.templateId)
3379
- : '');
3380
- const smsFallbackTemplateStr =
3381
- pickFirstSmsFallbackTemplateString(m)
3382
- || (typeof m.templateContent === 'string' ? m.templateContent : '')
3383
- || (typeof tcSibling.template === 'string' ? tcSibling.template : '')
3384
- || '';
3385
- const smsFallbackTemplateName =
3386
- m.templateName
3387
- || m.smsTemplateName
3388
- || tcSibling.templateName
3389
- || tcSibling.name
3390
- || '';
3391
- const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
3392
- ? m.registeredSenderIds
3393
- : Array.isArray(tcSibling.registeredSenderIds)
3394
- ? tcSibling.registeredSenderIds
3395
- : Array.isArray(tcSibling.header)
3396
- ? tcSibling.header
3397
- : null;
3398
- const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
3399
- const smsFallbackTemplateConfigs =
3400
- smsFallbackTemplateId || hasRegisteredSenderIds
3401
- ? {
3402
- ...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
3403
- ...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
3404
- ...(smsFallbackTemplateName && {
3405
- templateName: smsFallbackTemplateName,
3406
- }),
3407
- ...(hasRegisteredSenderIds && {
3408
- registeredSenderIds: registeredSenderIdsForPayload,
3409
- }),
3410
- }
3411
- : null;
3412
- const isDltCampaign = !isFullMode && isTraiDLTEnable(isFullMode, smsRegister);
3413
- return {
3414
- smsFallBackContent: isFullMode
3415
- ? {
3416
- smsTemplateName: smsFallbackForPayload.templateName || '',
3417
- smsContent: smsBodyText,
3418
- // cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
3419
- message: smsBodyText,
3420
- ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
3421
- unicodeValidity: smsFallbackForPayload.unicodeValidity,
3422
- }),
3423
- ...(smsFallbackForPayload.rcsSmsFallbackVarMapped
3424
- && Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
3425
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
3426
- }),
3427
- ...(smsFallbackTemplateConfigs && {
3428
- templateConfigs: smsFallbackTemplateConfigs,
3429
- }),
3430
- }
3431
- : {
3432
- // campaign payload: non-DLT → message only; DLT → message + templateConfigs
3433
- message: smsBodyText,
3434
- ...(isDltCampaign && smsFallbackTemplateConfigs && {
3435
- templateConfigs: smsFallbackTemplateConfigs,
3436
- }),
3437
- },
3438
- };
3439
- })()),
2014
+ smsFallBackContent: {
2015
+ message: fallbackMessage,
2016
+ ...(isDltEnabled && {
2017
+ templateConfigs: {
2018
+ templateId,
2019
+ templateName: template_name,
2020
+ template,
2021
+ registeredSenderIds,
2022
+ },
2023
+ }),
2024
+ },
3440
2025
  },
3441
2026
  },
3442
2027
  },
@@ -3446,109 +2031,6 @@ const onTitleAddVar = () => {
3446
2031
  return payload;
3447
2032
  };
3448
2033
 
3449
- /** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
3450
- const testPreviewFormData = useMemo(() => {
3451
- const payload = createPayload();
3452
- const rcs = payload?.versions?.base?.content?.RCS;
3453
- if (!rcs) return null;
3454
- // createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
3455
- const accountIdForCreateMessageMeta =
3456
- (wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
3457
- ? String(wecrmAccountId)
3458
- : accountId;
3459
- const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
3460
- let rcsForTest = {
3461
- ...rcs,
3462
- rcsContent: {
3463
- ...rcs.rcsContent,
3464
- ...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
3465
- },
3466
- };
3467
- /** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
3468
- if (isSlotMappingModeForPreview) {
3469
- const cardContent = rcsForTest.rcsContent?.cardContent;
3470
- if (Array.isArray(cardContent) && cardContent[0]) {
3471
- const fullCardVarMapped = coalesceCardVarMappedToTemplate(
3472
- pickRcsCardVarMappedEntries(cardVarMapped),
3473
- templateTitle,
3474
- templateDesc,
3475
- rcsVarRegex,
3476
- );
3477
- rcsForTest = {
3478
- ...rcsForTest,
3479
- rcsContent: {
3480
- ...rcsForTest.rcsContent,
3481
- cardContent: [
3482
- { ...cardContent[0], cardVarMapped: fullCardVarMapped },
3483
- ...cardContent.slice(1),
3484
- ],
3485
- },
3486
- };
3487
- }
3488
- }
3489
- const out = {
3490
- versions: {
3491
- base: {
3492
- content: {
3493
- RCS: rcsForTest,
3494
- },
3495
- },
3496
- },
3497
- };
3498
- const fb = smsFallbackData;
3499
- if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
3500
- out.templateConfigs = {
3501
- templateId: fb.smsTemplateId || '',
3502
- template: fb.templateContent || fb.content || '',
3503
- traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
3504
- registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
3505
- };
3506
- }
3507
- return out;
3508
- }, [
3509
- templateName,
3510
- templateTitle,
3511
- templateDesc,
3512
- templateMediaType,
3513
- cardVarMapped,
3514
- suggestions,
3515
- rcsImageSrc,
3516
- rcsVideoSrc,
3517
- rcsThumbnailSrc,
3518
- selectedDimension,
3519
- smsFallbackData,
3520
- isFullMode,
3521
- isEditFlow,
3522
- templateType,
3523
- accountId,
3524
- wecrmAccountId,
3525
- accessToken,
3526
- accountName,
3527
- hostName,
3528
- smsRegister,
3529
- ]);
3530
-
3531
- /**
3532
- * Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
3533
- * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
3534
- * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
3535
- */
3536
- const librarySmsFallbackMergedForValidation = useMemo(() => {
3537
- if (isFullMode) {
3538
- return smsFallbackData;
3539
- }
3540
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
3541
- const local =
3542
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
3543
- return {
3544
- ...smsFromApiShape,
3545
- ...local,
3546
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
3547
- };
3548
- }, [isFullMode, templateData, smsFallbackData]);
3549
-
3550
-
3551
-
3552
2034
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
3553
2035
  // eslint-disable-next-line no-undef
3554
2036
  const error = errorMessage?.message || errorMessage;
@@ -3578,9 +2060,6 @@ const onTitleAddVar = () => {
3578
2060
  _id: params?.id,
3579
2061
  validity: true,
3580
2062
  type: RCS,
3581
- // CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
3582
- // the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
3583
- closeSlideBoxAfterSubmit: !isFullMode,
3584
2063
  };
3585
2064
  getFormData(formDataParams);
3586
2065
  };
@@ -3594,7 +2073,6 @@ const onTitleAddVar = () => {
3594
2073
  actionCallback({ resp, errorMessage });
3595
2074
  setSpin(false); // Always turn off spinner
3596
2075
  if (!errorMessage) {
3597
- setTemplateStatus(RCS_STATUSES.pending);
3598
2076
  onCreateComplete();
3599
2077
  }
3600
2078
  });
@@ -3606,86 +2084,6 @@ const onTitleAddVar = () => {
3606
2084
  }
3607
2085
  };
3608
2086
 
3609
- /** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
3610
- const smsFallbackBlocksDone = () => {
3611
- // Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
3612
- if (
3613
- !isFullMode
3614
- && !isTraiDLTEnable(isFullMode, smsRegister)
3615
- && smsFallbackData == null
3616
- && hasMeaningfulSmsFallbackShape(
3617
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
3618
- )
3619
- ) {
3620
- return true;
3621
- }
3622
- if (!smsFallbackData) return false;
3623
- // Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
3624
- // concern, not a structural requirement for approval — the registered SMS template body stands on
3625
- // its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
3626
- if (isFullMode) return false;
3627
- const merged = librarySmsFallbackMergedForValidation;
3628
- const templateText = pickFirstSmsFallbackTemplateString(merged);
3629
- if (!templateText) {
3630
- return true;
3631
- }
3632
- const rawVarMap =
3633
- merged.rcsSmsFallbackVarMapped
3634
- || merged['rcs-sms-fallback-var-mapped'];
3635
- const varMap =
3636
- rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
3637
- return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
3638
- };
3639
-
3640
- /**
3641
- * Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
3642
- * semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
3643
- * / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
3644
- */
3645
- const isLibraryCampaignCardVarMappingIncomplete = () => {
3646
- if (isFullMode) return false;
3647
- const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
3648
- rcsVarTestRegex.test(elem),
3649
- );
3650
- const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
3651
- rcsVarTestRegex.test(elem),
3652
- );
3653
- const orderedVarNames = [
3654
- ...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
3655
- ...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
3656
- ];
3657
- if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
3658
- return true;
3659
- }
3660
- return orderedVarNames.some((name, globalIdx) => {
3661
- const v = resolveCardVarMappedSlotValue(
3662
- cardVarMapped,
3663
- name,
3664
- globalIdx,
3665
- true,
3666
- rcsSpanningSemanticVarNames.has(name),
3667
- );
3668
- const s = v == null ? '' : String(v);
3669
- return s.trim() === '';
3670
- });
3671
- };
3672
-
3673
- const isCarouselLibraryIncomplete = () => {
3674
- if (!isCarouselType || isFullMode) return false;
3675
- if ((carouselErrors || []).some((err) => err?.title || err?.description)) return true;
3676
- const unfilledVar = (carouselData || []).some((card) =>
3677
- ['title', 'description'].some((field) => {
3678
- const tokens = splitTemplateVarStringRcs(card?.[field] || '').filter((t) => rcsVarTestRegex.test(t));
3679
- return tokens.some((t) => {
3680
- const name = t.replace(/^\{\{|\}\}$/g, '');
3681
- const v = cardVarMapped?.[name];
3682
- return v == null || String(v).trim() === '';
3683
- });
3684
- })
3685
- );
3686
- return unfilledVar;
3687
- };
3688
-
3689
2087
  const isDisableDone = () => {
3690
2088
  if(isEditFlow){
3691
2089
  return false;
@@ -3696,26 +2094,46 @@ const onTitleAddVar = () => {
3696
2094
  }
3697
2095
  }
3698
2096
 
3699
- if (isCarouselLibraryIncomplete()) {
3700
- return true;
3701
- }
2097
+ if(!isFullMode){
2098
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2099
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2100
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
3702
2101
 
3703
- if (isLibraryCampaignCardVarMappingIncomplete()) {
3704
- return true;
3705
- }
2102
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2103
+ return true;
2104
+ }
3706
2105
 
3707
- if (smsFallbackBlocksDone()) {
3708
- return true;
2106
+ const hasEmptyMapping =
2107
+ cardVarMapped &&
2108
+ Object.keys(cardVarMapped).length > 0 &&
2109
+ Object.entries(cardVarMapped).some(([_, v]) => {
2110
+ if (typeof v !== 'string') return !v; // null/undefined
2111
+ return v.trim() === ''; // empty string
2112
+ });
2113
+
2114
+ if (hasEmptyMapping) {
2115
+ return true;
2116
+ }
2117
+
2118
+ const anyMissing = allVars.some(name => {
2119
+ const v = cardVarMapped?.[name];
2120
+ if (typeof v !== 'string') return !v;
2121
+ return v.trim() === '';
2122
+ });
2123
+ if (anyMissing) {
2124
+ return true;
2125
+ }
3709
2126
  }
3710
2127
 
3711
- if (!isCarouselType && isMediaTypeText && templateDesc.trim() === '') {
2128
+ if (isMediaTypeText && templateDesc.trim() === '') {
3712
2129
  return true;
2130
+
3713
2131
  }
3714
- if (!isCarouselType && isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2132
+ if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
3715
2133
  return true;
3716
2134
  }
3717
2135
 
3718
- if (!isCarouselType && isMediaTypeVideo && (!rcsVideoSrc.videoSrc || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
2136
+ if (isMediaTypeVideo && (rcsVideoSrc.videoSrc === '' || rcsThumbnailSrc === '' || templateTitle === '' || templateDesc === '' )) {
3719
2137
  return true;
3720
2138
  }
3721
2139
  if (buttonType.includes(CTA)) {
@@ -3727,36 +2145,53 @@ const onTitleAddVar = () => {
3727
2145
  return true;
3728
2146
  }
3729
2147
  }
3730
- if (templateDescError || templateTitleError) {
3731
- return true;
3732
- }
3733
- if (
3734
- smsFallbackData?.content
3735
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
3736
- ) {
2148
+ if (templateDescError || templateTitleError || fallbackMessageError) {
3737
2149
  return true;
3738
2150
  }
3739
2151
  return false;
3740
2152
  };
3741
2153
 
3742
2154
  const isEditDisableDone = () => {
3743
- if (isFullMode && !isHostInfoBip && templateStatus !== RCS_STATUSES.approved) {
2155
+
2156
+ if (templateStatus !== RCS_STATUSES.approved) {
3744
2157
  return true;
3745
2158
  }
3746
2159
 
3747
- // if (!isFullMode) {
3748
- // if (templateName.trim() === '' || templateNameError) {
3749
- // return true;
3750
- // }
3751
- // }
3752
- if (isLibraryCampaignCardVarMappingIncomplete()) {
3753
- return true;
2160
+ if (!isFullMode) {
2161
+ if (templateName.trim() === '' || templateNameError) {
2162
+ return true;
2163
+ }
3754
2164
  }
2165
+ if(!isFullMode){
2166
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2167
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2168
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
3755
2169
 
3756
- if (smsFallbackBlocksDone()) {
3757
- return true;
3758
- }
2170
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2171
+ return true;
2172
+ }
2173
+
2174
+ const hasEmptyMapping =
2175
+ cardVarMapped &&
2176
+ Object.keys(cardVarMapped).length > 0 &&
2177
+ Object.entries(cardVarMapped).some(([_, v]) => {
2178
+ if (typeof v !== 'string') return !v; // null/undefined
2179
+ return v.trim() === ''; // empty string
2180
+ });
2181
+
2182
+ if (hasEmptyMapping) {
2183
+ return true;
2184
+ }
3759
2185
 
2186
+ const anyMissing = allVars.some(name => {
2187
+ const v = cardVarMapped?.[name];
2188
+ if (typeof v !== 'string') return !v;
2189
+ return v.trim() === '';
2190
+ });
2191
+ if (anyMissing) {
2192
+ return true;
2193
+ }
2194
+ }
3760
2195
  if (isMediaTypeText && templateDesc.trim() === '') {
3761
2196
  return true;
3762
2197
  }
@@ -3775,13 +2210,7 @@ const onTitleAddVar = () => {
3775
2210
  return true;
3776
2211
  }
3777
2212
  }
3778
- if (templateTitleError || templateDescError) {
3779
- return true;
3780
- }
3781
- if (
3782
- smsFallbackData?.content
3783
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
3784
- ) {
2213
+ if (templateTitleError || templateDescError || fallbackMessageError) {
3785
2214
  return true;
3786
2215
  }
3787
2216
  return false;
@@ -3831,58 +2260,54 @@ const onTitleAddVar = () => {
3831
2260
  };
3832
2261
 
3833
2262
  const getMainContent = () => {
3834
- // Slideboxes are rendered outside the page-level spinner to avoid
3835
- // stacking/blur issues during initial loads.
3836
- if (showDltContainer) return null;
2263
+ if (showDltContainer && !fallbackPreviewmode) {
2264
+ const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2265
+ const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2266
+ return (
2267
+ <CapSlideBox
2268
+ show={showDltContainer}
2269
+ header={dltHeader}
2270
+ content={dltContent}
2271
+ handleClose={closeDltContainerHandler}
2272
+ size="size-xl"
2273
+ />
2274
+ );
2275
+ }
2276
+
3837
2277
  return (
3838
2278
  <>
3839
- {templateStatus !== '' && (
3840
- <CapRow className="template-status-container">
3841
- <CapColumn span={14}>
3842
- <CapLabel type="label2">
3843
- {formatMessage(messages.templateStatusLabel)}
3844
- </CapLabel>
3845
-
3846
- {!isHostInfoBip && templateStatus && (
3847
- <CapAlert
3848
- message={getTemplateStatusMessage()}
3849
- type={getTemplateStatusType(templateStatus)}
3850
- />
3851
- )}
3852
- </CapColumn>
3853
- </CapRow>
2279
+ {templateStatus !== '' && (<CapRow className="template-status-container">
2280
+ <CapLabel type="label2">
2281
+ {formatMessage(messages.templateStatusLabel)}
2282
+ </CapLabel>
2283
+
2284
+ {templateStatus && (
2285
+ <CapAlert
2286
+ message={getTemplateStatusMessage()}
2287
+ type={getTemplateStatusType(templateStatus)}
2288
+ />
2289
+ )}
2290
+ </CapRow>
3854
2291
  )}
3855
- <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2292
+ <CapRow className="cap-rcs-creatives">
3856
2293
  <CapColumn span={14}>
3857
2294
  {/* template name */}
3858
2295
  {isFullMode && (
3859
- isEditFlow ? (
3860
- <div className="rcs-creative-name-readonly">
3861
- <CapHeading type="h4">
3862
- {formatMessage(globalMessages.creativeNameLabel)}
3863
- </CapHeading>
3864
- <CapHeading type="h5" className="rcs-creative-name-value">
3865
- {templateName || '-'}
3866
- </CapHeading>
3867
- </div>
3868
- ) : (
3869
- <CapInput
3870
- id="rcs_template_name_input"
3871
- data-testid="template_name"
3872
- onChange={onTemplateNameChange}
3873
- errorMessage={templateNameError}
3874
- placeholder={formatMessage(
3875
- globalMessages.templateNamePlaceholder,
3876
- )}
3877
- value={templateName || ''}
3878
- size="default"
3879
- label={formatMessage(globalMessages.creativeNameLabel)}
3880
- disabled={(isEditFlow || !isFullMode)}
3881
- />
3882
- )
2296
+ <CapInput
2297
+ id="rcs_template_name_input"
2298
+ data-testid="template_name"
2299
+ onChange={onTemplateNameChange}
2300
+ errorMessage={templateNameError}
2301
+ placeholder={formatMessage(
2302
+ globalMessages.templateNamePlaceholder,
2303
+ )}
2304
+ value={templateName || ''}
2305
+ size="default"
2306
+ label={formatMessage(globalMessages.creativeNameLabel)}
2307
+ disabled={(isEditFlow || !isFullMode)}
2308
+ />
3883
2309
  )}
3884
2310
  {renderLabel('templateTypeLabel')}
3885
-
3886
2311
  <CapRadioGroup
3887
2312
  id="select-rcs-template-type"
3888
2313
  options={TEMPLATE_TYPE_OPTIONS}
@@ -3891,30 +2316,24 @@ const onTitleAddVar = () => {
3891
2316
  disabled={(isEditFlow || !isFullMode)}
3892
2317
  />
3893
2318
 
3894
- {templateType === contentType.carousel ? (
3895
- renderCarouselSection()
3896
- ) : (
2319
+ {/* Show media only for rich_card or carousel */}
2320
+ {(templateType === contentType.rich_card || templateType === contentType.carousel) && (
3897
2321
  <>
3898
- {/* Show media only for rich_card */}
3899
- {templateType === contentType.rich_card && (
3900
- <>
3901
- {renderLabel('mediaLabel')}
3902
- <CapRadioGroup
3903
- options={mediaRadioOptions || []}
3904
- value={templateMediaType}
3905
- onChange={onTemplateMediaTypeChange}
3906
- disabled={(isEditFlow || !isFullMode)}
3907
- className="rcs-radio"
3908
- />
3909
- <div className="rcs-container-image">
3910
- {getMediaBasedComponent()}
3911
- </div>
3912
- </>
3913
- )}
3914
- {renderTextComponent()}
2322
+ {renderLabel('mediaLabel')}
2323
+ <CapRadioGroup
2324
+ options={mediaRadioOptions || []}
2325
+ value={templateMediaType}
2326
+ onChange={onTemplateMediaTypeChange}
2327
+ disabled={(isEditFlow || !isFullMode)}
2328
+ className="rcs-radio"
2329
+ />
2330
+ <div className="rcs-container-image">
2331
+ {getMediaBasedComponent()}
2332
+ </div>
3915
2333
  </>
3916
2334
  )}
3917
- <CapDivider className="rcs-fallback-section-divider" />
2335
+ {renderTextComponent()}
2336
+ <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
3918
2337
  {renderFallBackSmsComponent()}
3919
2338
  <div className="rcs-scroll-div" />
3920
2339
  </CapColumn>
@@ -3926,8 +2345,7 @@ const onTitleAddVar = () => {
3926
2345
 
3927
2346
 
3928
2347
  <div className="rcs-footer">
3929
- {/* 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). */}
3930
- {!isEditFlow && isFullMode && (
2348
+ {!isEditFlow && (
3931
2349
  <>
3932
2350
  <div className="button-disabled-tooltip-wrapper">
3933
2351
  <CapButton
@@ -3935,9 +2353,7 @@ const onTitleAddVar = () => {
3935
2353
  disabled={isDisableDone()}
3936
2354
  className="rcs-done-btn"
3937
2355
  >
3938
- <FormattedMessage
3939
- {...(isHostInfoBip ? messages.doneButtonLabel : messages.sendForApprovalButtonLabel)}
3940
- />
2356
+ <FormattedMessage {...messages.sendForApprovalButtonLabel} />
3941
2357
  </CapButton>
3942
2358
  </div>
3943
2359
  <CapTooltip
@@ -3950,6 +2366,7 @@ const onTitleAddVar = () => {
3950
2366
  className="rcs-test-preview-btn"
3951
2367
  type="secondary"
3952
2368
  disabled={true}
2369
+ style={{ marginLeft: "8px" }}
3953
2370
  >
3954
2371
  <FormattedMessage {...creativesMessages.testAndPreview} />
3955
2372
  </CapButton>
@@ -3970,7 +2387,7 @@ const onTitleAddVar = () => {
3970
2387
  </div>
3971
2388
  </>
3972
2389
  )}
3973
- {isEditFlow && (isHostInfoBip || templateStatus === RCS_STATUSES.approved) && (
2390
+ {isEditFlow && templateStatus === RCS_STATUSES.approved && (
3974
2391
  <>
3975
2392
  <CapButton
3976
2393
  onClick={handleTestAndPreview}
@@ -3982,6 +2399,51 @@ const onTitleAddVar = () => {
3982
2399
  </>
3983
2400
  )}
3984
2401
  </div>
2402
+
2403
+
2404
+ {fallbackPreviewmode && (
2405
+ <CapSlideBox
2406
+ className="rcs-fallback-preview"
2407
+ show={fallbackPreviewmode}
2408
+ header={(
2409
+ <CapHeading type="h7" style={{ color: CAP_G01 }}>
2410
+ {formatMessage(messages.fallbackPreviewtitle)}
2411
+ </CapHeading>
2412
+ )}
2413
+ content={(
2414
+ <>
2415
+ <UnifiedPreview
2416
+ channel={RCS}
2417
+ content={{
2418
+ rcsPreviewContent: {
2419
+ rcsDesc: tempMsg,
2420
+ },
2421
+ }}
2422
+ device={ANDROID}
2423
+ showDeviceToggle={false}
2424
+ showHeader={false}
2425
+ formatMessage={formatMessage}
2426
+ />
2427
+ <CapHeading
2428
+ type="h3"
2429
+ style={{ textAlign: 'center' }}
2430
+ className="margin-t-16"
2431
+ >
2432
+ {formatMessage(messages.totalCharacters, {
2433
+ smsCount: Math.ceil(
2434
+ tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2435
+ ),
2436
+ number: tempMsg.length,
2437
+ })}
2438
+ </CapHeading>
2439
+ </>
2440
+ )}
2441
+ handleClose={() => {
2442
+ setFallbackPreviewmode(false);
2443
+ setDltPreviewData('');
2444
+ }}
2445
+ />
2446
+ )}
3985
2447
  </>
3986
2448
  );
3987
2449
  };
@@ -3990,57 +2452,23 @@ const onTitleAddVar = () => {
3990
2452
  <CapSpin spinning={loadingTags || spin}>
3991
2453
  {getMainContent()}
3992
2454
  </CapSpin>
3993
-
3994
- {showDltContainer && (() => {
3995
- const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
3996
- return (
3997
- <CapSlideBox
3998
- show={showDltContainer}
3999
- header={dltHeader}
4000
- content={dltContent}
4001
- handleClose={closeDltContainerHandler}
4002
- size="size-xl"
4003
- />
4004
- );
4005
- })()}
4006
-
4007
2455
  <TestAndPreviewSlidebox
4008
2456
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
4009
2457
  onClose={handleCloseTestAndPreview}
4010
- formData={testPreviewFormData}
4011
- content={testAndPreviewContent}
2458
+ formData={null} // RCS doesn't use formData structure like SMS
2459
+ content={getTemplateContent()}
4012
2460
  currentChannel={RCS}
4013
- orgUnitId={orgUnitId}
4014
- rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
4015
- smsFallbackContent={
4016
- smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
4017
- ? {
4018
- templateContent:
4019
- smsFallbackData.templateContent || smsFallbackData.content || '',
4020
- templateName: smsFallbackData.templateName || '',
4021
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
4022
- ? mergeRcsSmsFallbackVarMapLayers(
4023
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
4024
- smsFallbackData,
4025
- )
4026
- : mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
4027
- }
4028
- : null
4029
- }
4030
- smsRegister={smsRegister}
4031
2461
  />
4032
2462
  </>
4033
2463
  );
4034
2464
  };
4035
2465
 
4036
-
4037
2466
  const mapStateToProps = createStructuredSelector({
4038
2467
  rcsData: makeSelectRcs(),
4039
2468
  accountData: makeSelectAccount(),
4040
2469
  metaEntities: makeSelectMetaEntities(),
4041
2470
  loadingTags: isLoadingMetaEntities(),
4042
2471
  injectedTags: setInjectedTags(),
4043
- currentOrgDetails: selectCurrentOrgDetails(),
4044
2472
  });
4045
2473
 
4046
2474
  const mapDispatchToProps = (dispatch) => ({