@capillarytech/creatives-library 8.0.316-alpha.3 → 8.0.316-alpha.4

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 (105) hide show
  1. package/constants/unified.js +14 -0
  2. package/package.json +1 -1
  3. package/utils/templateVarUtils.js +172 -0
  4. package/utils/tests/templateVarUtils.test.js +160 -0
  5. package/v2Components/CapTagList/index.js +10 -0
  6. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  7. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +8 -2
  8. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +207 -21
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +16 -0
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +85 -10
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +30 -0
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +79 -11
  13. package/v2Components/CommonTestAndPreview/SendTestMessage.js +11 -5
  14. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  15. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  16. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +12 -0
  17. package/v2Components/CommonTestAndPreview/constants.js +38 -0
  18. package/v2Components/CommonTestAndPreview/index.js +693 -155
  19. package/v2Components/CommonTestAndPreview/messages.js +41 -3
  20. package/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  21. package/v2Components/CommonTestAndPreview/sagas.js +15 -6
  22. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +172 -0
  23. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +269 -1
  24. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +118 -5
  25. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +245 -0
  26. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +25 -4
  27. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +100 -1
  28. package/v2Components/CommonTestAndPreview/tests/index.test.js +19 -1
  29. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  30. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  31. package/v2Components/FormBuilder/index.js +7 -1
  32. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  33. package/v2Components/SmsFallback/constants.js +73 -0
  34. package/v2Components/SmsFallback/index.js +956 -0
  35. package/v2Components/SmsFallback/index.scss +265 -0
  36. package/v2Components/SmsFallback/messages.js +78 -0
  37. package/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  38. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  39. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  40. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  41. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  42. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  43. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +327 -0
  44. package/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  45. package/v2Components/TestAndPreviewSlidebox/index.js +8 -1
  46. package/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  47. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  48. package/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  49. package/v2Components/VarSegmentMessageEditor/index.js +125 -0
  50. package/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  51. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  52. package/v2Containers/CreativesContainer/SlideBoxContent.js +36 -4
  53. package/v2Containers/CreativesContainer/SlideBoxFooter.js +10 -1
  54. package/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  55. package/v2Containers/CreativesContainer/constants.js +9 -0
  56. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  57. package/v2Containers/CreativesContainer/index.js +286 -93
  58. package/v2Containers/CreativesContainer/index.scss +51 -1
  59. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  60. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +8 -0
  61. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +20 -10
  62. package/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  63. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  64. package/v2Containers/Rcs/constants.js +32 -1
  65. package/v2Containers/Rcs/index.js +950 -873
  66. package/v2Containers/Rcs/index.scss +85 -6
  67. package/v2Containers/Rcs/messages.js +10 -1
  68. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  69. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +40834 -1963
  70. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  71. package/v2Containers/Rcs/tests/index.test.js +41 -38
  72. package/v2Containers/Rcs/tests/mockData.js +38 -0
  73. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  74. package/v2Containers/Rcs/tests/utils.test.js +379 -1
  75. package/v2Containers/Rcs/utils.js +358 -10
  76. package/v2Containers/Sms/Create/index.js +81 -36
  77. package/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  78. package/v2Containers/SmsTrai/Create/index.js +9 -4
  79. package/v2Containers/SmsTrai/Edit/constants.js +2 -0
  80. package/v2Containers/SmsTrai/Edit/index.js +609 -128
  81. package/v2Containers/SmsTrai/Edit/index.scss +121 -0
  82. package/v2Containers/SmsTrai/Edit/messages.js +9 -4
  83. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4327 -2374
  84. package/v2Containers/SmsWrapper/index.js +37 -8
  85. package/v2Containers/TagList/index.js +6 -0
  86. package/v2Containers/Templates/TemplatesActionBar.js +101 -0
  87. package/v2Containers/Templates/_templates.scss +61 -2
  88. package/v2Containers/Templates/actions.js +11 -0
  89. package/v2Containers/Templates/constants.js +2 -0
  90. package/v2Containers/Templates/index.js +90 -40
  91. package/v2Containers/Templates/sagas.js +57 -12
  92. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  93. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  94. package/v2Containers/Templates/tests/sagas.test.js +110 -12
  95. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  96. package/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  97. package/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  98. package/v2Containers/TemplatesV2/index.js +86 -23
  99. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  100. package/v2Containers/WebPush/Create/components/MessageSection.js +54 -18
  101. package/v2Containers/WebPush/Create/components/MessageSection.test.js +28 -0
  102. package/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +7 -3
  103. package/v2Containers/Whatsapp/index.js +7 -23
  104. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +578 -34
  105. package/v2Containers/Whatsapp/tests/index.test.js +172 -0
@@ -1,13 +1,12 @@
1
1
  /* eslint-disable no-unused-expressions */
2
- import React, { useState, useEffect, useCallback } from 'react';
2
+ import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
3
3
  import { bindActionCreators } from 'redux';
4
4
  import { createStructuredSelector } from 'reselect';
5
- import { injectIntl, FormattedMessage } from 'react-intl';
5
+ import { 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';
11
10
  import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
12
11
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
13
12
  import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
@@ -22,35 +21,16 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
22
21
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
23
22
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
24
23
  import CapImage from '@capillarytech/cap-ui-library/CapImage';
25
- import CapCard from '@capillarytech/cap-ui-library/CapCard';
26
24
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
27
25
  import CapSelect from '@capillarytech/cap-ui-library/CapSelect';
28
- import CapCustomCard from '@capillarytech/cap-ui-library/CapCustomCard';
29
- import CapDropdown from '@capillarytech/cap-ui-library/CapDropdown';
30
- import CapMenu from '@capillarytech/cap-ui-library/CapMenu';
31
26
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
32
- import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
33
27
  import CapError from '@capillarytech/cap-ui-library/CapError';
34
- import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
35
28
  import CapAskAira from '@capillarytech/cap-ui-library/CapAskAira';
36
- import CapLink from '@capillarytech/cap-ui-library/CapLink';
37
-
38
- import {
39
- CAP_G01,
40
- CAP_SPACE_04,
41
- CAP_SPACE_16,
42
- CAP_SPACE_24,
43
- CAP_SPACE_28,
44
- CAP_SPACE_32,
45
- CAP_WHITE,
46
- CAP_SECONDARY,
47
- } from '@capillarytech/cap-ui-library/styled/variables';
48
29
 
49
30
  import CapVideoUpload from '../../v2Components/CapVideoUpload';
50
31
  import * as globalActions from '../Cap/actions';
51
32
  import CapActionButton from '../../v2Components/CapActionButton';
52
33
  import { makeSelectRcs, makeSelectAccount } from './selectors';
53
- import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
54
34
  import {
55
35
  isLoadingMetaEntities,
56
36
  makeSelectMetaEntities,
@@ -60,6 +40,15 @@ import * as RcsActions from './actions';
60
40
  import { isAiContentBotDisabled } from '../../utils/common';
61
41
  import * as TemplatesActions from '../Templates/actions';
62
42
  import './index.scss';
43
+ import {
44
+ normalizeLibraryLoadedTitleDesc,
45
+ mergeRcsSmsFallBackContentFromDetails,
46
+ mergeRcsSmsFallbackVarMapLayers,
47
+ pickFirstSmsFallbackTemplateString,
48
+ syncCardVarMappedSemanticsFromSlots,
49
+ hasMeaningfulSmsFallbackShape,
50
+ getLibrarySmsFallbackApiBaselineFromTemplateData,
51
+ } from './rcsLibraryHydrationUtils';
63
52
  import {
64
53
  RCS,
65
54
  SMS,
@@ -81,8 +70,6 @@ import {
81
70
  MESSAGE_TEXT,
82
71
  ALLOWED_EXTENSIONS_VIDEO_REGEX,
83
72
  RCS_VIDEO_SIZE,
84
- TEMPLATE_HEADER_MAX_LENGTH,
85
- TEMPLATE_MESSAGE_MAX_LENGTH,
86
73
  RCS_THUMBNAIL_MIN_SIZE,
87
74
  RCS_THUMBNAIL_MAX_SIZE,
88
75
  contentType,
@@ -95,35 +82,45 @@ import {
95
82
  MAX_BUTTONS,
96
83
  INITIAL_SUGGESTIONS_DATA_STOP,
97
84
  RCS_BUTTON_TYPES,
98
- titletype,
99
- descType,
100
85
  STANDALONE,
101
86
  VERTICAL,
102
87
  SMALL,
103
88
  MEDIUM,
104
89
  RICHCARD,
90
+ RCS_NUMERIC_VAR_NAME_REGEX,
91
+ RCS_TAG_AREA_FIELD_TITLE,
92
+ RCS_TAG_AREA_FIELD_DESC,
105
93
  } from './constants';
106
94
  import globalMessages from '../Cap/messages';
107
95
  import messages from './messages';
108
96
  import creativesMessages from '../CreativesContainer/messages';
109
97
  import withCreatives from '../../hoc/withCreatives';
110
98
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
111
- import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
99
+ import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
100
+ import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
112
101
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
102
+ import { splitTemplateVarString } from '../../utils/templateVarUtils';
113
103
  import CapImageUpload from '../../v2Components/CapImageUpload';
114
- import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
115
104
  import Templates from '../Templates';
116
105
  import SmsTraiEdit from '../SmsTrai/Edit';
106
+ import SmsFallback from '../../v2Components/SmsFallback';
107
+ import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
117
108
  import TagList from '../TagList';
118
109
  import { validateTags } from '../../utils/tagValidations';
119
- import { getCdnUrl } from '../../utils/cdnTransformation';
110
+ import { isTraiDLTEnable } from '../../utils/common';
120
111
  import { isTagIncluded } from '../../utils/commonUtils';
121
112
  import injectReducer from '../../utils/injectReducer';
122
113
  import v2RcsReducer from './reducer';
123
- import { getTemplateStatusType } from './utils';
124
-
114
+ import {
115
+ areAllRcsSmsFallbackVarSlotsFilled,
116
+ buildRcsNumericMustachePlaceholderRegex,
117
+ getTemplateStatusType,
118
+ normalizeCardVarMapped,
119
+ coalesceCardVarMappedToTemplate,
120
+ resolveCardVarMappedSlotValue,
121
+ sanitizeCardVarMappedValue,
122
+ } from './utils';
125
123
 
126
- const { Group: CapCheckboxGroup } = CapCheckbox;
127
124
  export const Rcs = (props) => {
128
125
  const {
129
126
  intl,
@@ -137,15 +134,14 @@ export const Rcs = (props) => {
137
134
  templatesActions,
138
135
  globalActions,
139
136
  location,
140
- handleClose,
141
137
  getDefaultTags,
142
138
  supportedTags,
143
139
  metaEntities,
144
140
  injectedTags,
145
141
  loadingTags,
146
142
  getFormData,
147
- isDltEnabled,
148
143
  smsRegister,
144
+ orgUnitId,
149
145
  selectedOfferDetails,
150
146
  eventContextTags,
151
147
  accountData = {},
@@ -156,8 +152,8 @@ export const Rcs = (props) => {
156
152
  } = props || {};
157
153
  const { formatMessage } = intl;
158
154
  const { TextArea } = CapInput;
159
- const { CapCustomCardList } = CapCustomCard;
160
155
  const [isEditFlow, setEditFlow] = useState(false);
156
+ const isEditLike = isEditFlow || !isFullMode;
161
157
  const [tags, updateTags] = useState([]);
162
158
  const [spin, setSpin] = useState(false);
163
159
  //template
@@ -166,112 +162,71 @@ export const Rcs = (props) => {
166
162
  const [templateMediaType, setTemplateMediaType] = useState(
167
163
  RCS_MEDIA_TYPES.NONE,
168
164
  );
169
- const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
170
165
  const [templateTitle, setTemplateTitle] = useState('');
171
166
  const [templateDesc, setTemplateDesc] = useState('');
172
167
  const [templateDescError, setTemplateDescError] = useState(false);
173
168
  const [templateStatus, setTemplateStatus] = useState('');
174
- const [templateDate, setTemplateDate] = useState('');
175
- //fallback
176
- const [fallbackMessage, setFallbackMessage] = useState('');
177
- const [fallbackMessageError, setFallbackMessageError] = useState(false);
178
169
  //fallback dlt
179
170
  const [showDltContainer, setShowDltContainer] = useState(false);
180
171
  const [dltMode, setDltMode] = useState('');
181
172
  const [dltEditData, setDltEditData] = useState({});
182
- const [showDltCard, setShowDltCard] = useState(false);
183
- const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
184
- const [dltPreviewData, setDltPreviewData] = useState('');
173
+ /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
174
+ const [smsFallbackData, setSmsFallbackData] = useState(undefined);
185
175
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
186
- const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
176
+ const buttonType = RCS_BUTTON_TYPES.NONE;
187
177
  const [suggestionError, setSuggestionError] = useState(true);
188
178
  const [templateType, setTemplateType] = useState('text_message');
189
- const [templateHeader, setTemplateHeader] = useState('');
190
- const [templateMessage, setTemplateMessage] = useState('');
191
- const [templateHeaderError, setTemplateHeaderError] = useState('');
192
- const [templateMessageError, setTemplateMessageError] = useState('');
193
- const validVarRegex = /\{\{(\d+)\}\}/g;
194
- const [updatedTitleData, setUpdatedTitleData] = useState([]);
195
- const [updatedDescData, setUpdatedDescData] = useState([]);
196
179
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
197
180
  const [descVarMappedData, setDescVarMappedData] = useState({});
198
181
  const [titleTextAreaId, setTitleTextAreaId] = useState();
199
182
  const [descTextAreaId, setDescTextAreaId] = useState();
200
183
  const [assetList, setAssetList] = useState({});
201
- const [assetListImage, setAssetListImage] = useState('');
202
184
  const [rcsImageSrc, updateRcsImageSrc] = useState('');
203
185
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
204
186
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
205
187
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
206
- const [imageError, setImageError] = useState(null);
207
188
  const [templateTitleError, setTemplateTitleError] = useState(false);
208
189
  const [cardVarMapped, setCardVarMapped] = useState({});
190
+ /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
191
+ const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
192
+ const lastHydratedRcsCardVarSignatureRef = useRef(null);
193
+
194
+ /**
195
+ * Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
196
+ * without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
197
+ * fallback card / content stopped appearing until re-selected.
198
+ */
199
+ const rcsHydrationDetails = useMemo(
200
+ () => (isFullMode ? rcsData?.templateDetails : templateData),
201
+ [isFullMode, rcsData?.templateDetails, templateData],
202
+ );
209
203
 
210
- // TestAndPreviewSlidebox state
211
- const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
212
- const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
213
-
214
- const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
215
-
216
- // Get template content for TestAndPreviewSlidebox
217
- // Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
218
- // getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
219
- // renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
220
- // Note: templateHeader and templateMessage are defined but NOT used in the component
221
- const getTemplateContent = useCallback(() => {
222
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
223
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
224
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
225
-
226
- // Build media preview object (same pattern as getRcsPreview)
227
- const mediaPreview = {};
228
- if (isMediaTypeImage && rcsImageSrc) {
229
- mediaPreview.rcsImageSrc = rcsImageSrc;
230
- }
231
- if (isMediaTypeVideo && !isMediaTypeText) {
232
- // For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
233
- if (rcsThumbnailSrc) {
234
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
235
- } else if (rcsVideoSrc?.videoSrc) {
236
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
237
- }
238
- // Also include thumbnailSrc separately if available
239
- if (rcsThumbnailSrc) {
240
- mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
204
+ /** 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'). */
205
+ const lastTagSchemaQueryKeyRef = useRef(null);
206
+ /**
207
+ * Library: parent often passes a new `templateData` object reference every render. Re-applying the same
208
+ * SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
209
+ */
210
+ const lastSmsFallbackHydrationKeyRef = useRef(null);
211
+
212
+ const fetchTagSchemaIfNewQuery = useCallback(
213
+ (query) => {
214
+ const key = JSON.stringify(query);
215
+ if (lastTagSchemaQueryKeyRef.current === key) {
216
+ return;
241
217
  }
242
- }
243
-
244
- // Build content object
245
- // Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
246
- // templateTitle is used for rich_card/carousel title, empty for text_message
247
- // templateDesc is used for ALL types (text message body or rich card description)
248
- // For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
249
- const contentObj = {
250
- // Map templateTitle to templateHeader and templateDesc to templateMessage
251
- templateHeader: templateTitle,
252
- templateMessage: templateDesc,
253
- ...mediaPreview,
254
- ...(suggestions.length > 0 && {
255
- suggestions: suggestions,
256
- }),
257
- };
218
+ lastTagSchemaQueryKeyRef.current = key;
219
+ globalActions.fetchSchemaForEntity(query);
220
+ },
221
+ [globalActions],
222
+ );
258
223
 
259
- return contentObj;
260
- }, [
261
- templateMediaType,
262
- templateTitle,
263
- templateDesc,
264
- rcsImageSrc,
265
- rcsVideoSrc,
266
- rcsThumbnailSrc,
267
- suggestions,
268
- selectedDimension,
269
- ]);
224
+ // TestAndPreviewSlidebox state
225
+ const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
270
226
 
271
227
  // Handle Test and Preview button click
272
228
  const handleTestAndPreview = useCallback(() => {
273
229
  setShowTestAndPreviewSlidebox(true);
274
- setIsTestAndPreviewMode(true);
275
230
  if (propsHandleTestAndPreview) {
276
231
  propsHandleTestAndPreview();
277
232
  }
@@ -280,23 +235,24 @@ export const Rcs = (props) => {
280
235
  // Handle close Test and Preview slidebox
281
236
  const handleCloseTestAndPreview = useCallback(() => {
282
237
  setShowTestAndPreviewSlidebox(false);
283
- setIsTestAndPreviewMode(false);
284
238
  if (propsHandleCloseTestAndPreview) {
285
239
  propsHandleCloseTestAndPreview();
286
240
  }
287
241
  }, [propsHandleCloseTestAndPreview]);
288
242
 
289
- // Helper to get RCS orientation from selectedDimension
290
- const getRcsOrientation = () => {
291
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
292
- if (isMediaTypeImage) {
293
- return RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
294
- }
295
- // For video
296
- return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
297
- };
243
+ /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
244
+ const handleSmsFallbackEditorStateChange = useCallback((patch) => {
245
+ setSmsFallbackData((prev) => {
246
+ if (!patch || typeof patch !== 'object') return prev;
247
+ // Merge even when `prev` is null so tag/slot updates apply on top of `smsFromApiShape` in createPayload.
248
+ return { ...(prev || {}), ...patch };
249
+ });
250
+ }, []);
298
251
 
252
+ /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
299
253
  const [accountId, setAccountId] = useState('');
254
+ /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
255
+ const [wecrmAccountId, setWecrmAccountId] = useState('');
300
256
  const [accessToken, setAccessToken] = useState('');
301
257
  const [hostName, setHostName] = useState('');
302
258
  const [accountName, setAccountName] = useState('');
@@ -304,14 +260,23 @@ export const Rcs = (props) => {
304
260
  const accountObj = accountData.selectedRcsAccount || {};
305
261
  if (!isEmpty(accountObj)) {
306
262
  const {
263
+ id: wecrmId,
307
264
  sourceAccountIdentifier = '',
308
265
  configs = {},
309
266
  } = accountObj;
310
-
311
267
  setAccountId(sourceAccountIdentifier);
268
+ setWecrmAccountId(
269
+ wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
270
+ );
312
271
  setAccessToken(configs.accessToken || '');
313
272
  setHostName(accountObj.hostName || '');
314
273
  setAccountName(accountObj.name || '');
274
+ } else {
275
+ setAccountId('');
276
+ setWecrmAccountId('');
277
+ setAccessToken('');
278
+ setHostName('');
279
+ setAccountName('');
315
280
  }
316
281
  }, [accountData.selectedRcsAccount]);
317
282
 
@@ -367,7 +332,9 @@ export const Rcs = (props) => {
367
332
  if (isFullMode) return;
368
333
  if (loadingTags || !tags || tags.length === 0) return;
369
334
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
370
- const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
335
+ const slotOffset =
336
+ type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
337
+ const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
371
338
  if (!resolved) {
372
339
  if (type === TITLE_TEXT) setTemplateTitleError(false);
373
340
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -412,13 +379,41 @@ export const Rcs = (props) => {
412
379
 
413
380
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
414
381
 
415
- const resolveTemplateWithMap = (str = '') => {
382
+ const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
383
+
384
+ /** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
385
+ const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
386
+ const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
387
+ const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
388
+ const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
389
+ let varOrdinal = 0;
390
+ for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
391
+ const segmentToken = templateSegments[segmentIndexInField];
392
+ if (rcsVarTestRegex.test(segmentToken)) {
393
+ if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
394
+ return offset + varOrdinal;
395
+ }
396
+ varOrdinal += 1;
397
+ }
398
+ }
399
+ return null;
400
+ };
401
+
402
+ const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
416
403
  if (!str) return '';
417
- const arr = splitTemplateVarString(str);
404
+ const arr = splitTemplateVarStringRcs(str);
405
+ let varOrdinal = 0;
418
406
  return arr.map((elem) => {
419
407
  if (rcsVarTestRegex.test(elem)) {
420
408
  const key = getVarNameFromToken(elem);
421
- const v = cardVarMapped?.[key];
409
+ const globalSlot = slotOffset + varOrdinal;
410
+ varOrdinal += 1;
411
+ const v = resolveCardVarMappedSlotValue(
412
+ cardVarMapped,
413
+ key,
414
+ globalSlot,
415
+ isEditLike,
416
+ );
422
417
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
423
418
  return String(v);
424
419
  }
@@ -426,107 +421,154 @@ export const Rcs = (props) => {
426
421
  }).join('');
427
422
  };
428
423
 
424
+ /**
425
+ * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
426
+ * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
427
+ * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
428
+ */
429
+ const getTemplateContent = useCallback(() => {
430
+ const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
431
+ const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
432
+ const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
429
433
 
430
- useEffect(() => {
431
- if (isFullMode || isEditFlow) return;
432
- const tokens = [
433
- ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
434
- ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
435
- ];
436
- if (!tokens.length) return;
437
- setCardVarMapped((prev) => {
438
- const next = { ...(prev || {}) };
439
- let changed = false;
440
- tokens.forEach((t) => {
441
- const name = getVarNameFromToken(t);
442
- if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
443
- next[name] = '';
444
- changed = true;
445
- }
446
- });
447
- return changed ? next : prev;
448
- });
449
- }, [isFullMode, templateTitle, templateDesc]);
434
+ const isSlotMappingMode = isEditFlow || !isFullMode;
435
+ const titleVarCountForResolve = isMediaTypeText
436
+ ? 0
437
+ : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
438
+ const resolvedTitle = isMediaTypeText
439
+ ? ''
440
+ : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
441
+ const resolvedDesc = isSlotMappingMode
442
+ ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
443
+ : templateDesc;
444
+
445
+ const mediaPreview = {};
446
+ if (isMediaTypeImage && rcsImageSrc) {
447
+ mediaPreview.rcsImageSrc = rcsImageSrc;
448
+ }
449
+ if (isMediaTypeVideo && !isMediaTypeText) {
450
+ if (rcsThumbnailSrc) {
451
+ mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
452
+ } else if (rcsVideoSrc?.videoSrc) {
453
+ mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
454
+ }
455
+ if (rcsThumbnailSrc) {
456
+ mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
457
+ }
458
+ }
459
+
460
+ const contentObj = {
461
+ templateHeader: resolvedTitle,
462
+ templateMessage: resolvedDesc,
463
+ ...mediaPreview,
464
+ ...(suggestions.length > 0 && {
465
+ suggestions: suggestions,
466
+ }),
467
+ };
468
+
469
+ return contentObj;
470
+ }, [
471
+ templateMediaType,
472
+ templateTitle,
473
+ templateDesc,
474
+ rcsImageSrc,
475
+ rcsVideoSrc,
476
+ rcsThumbnailSrc,
477
+ suggestions,
478
+ selectedDimension,
479
+ isFullMode,
480
+ isEditFlow,
481
+ cardVarMapped,
482
+ ]);
450
483
 
484
+ const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
451
485
 
452
- const RcsLabel = styled.div`
453
- display: flex;
454
- margin-top: 20px;
455
- `;
456
486
  const paramObj = params || {};
457
487
  useEffect(() => {
458
- const { id } = paramObj;
459
- if (id && isFullMode) {
460
- setSpin(true);
461
- actions.getTemplateDetails(id, setSpin);
462
- }
463
- return () => {
464
- actions.clearEditResponse();
465
- };
466
- }, [paramObj.id]);
488
+ const { id } = paramObj;
489
+ if (id && isFullMode) {
490
+ setSpin(true);
491
+ actions.getTemplateDetails(id, setSpin);
492
+ }
493
+ return () => {
494
+ actions.clearEditResponse();
495
+ };
496
+ }, [paramObj.id, isFullMode]);
467
497
 
468
498
  useEffect(() => {
469
- if (!(isEditFlow || !isFullMode)) return;
470
-
471
- const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
472
- const arr = splitTemplateVarString(targetString);
473
- if (!arr?.length) {
474
- setVarMap({});
475
- setUpdated([]);
476
- return;
499
+ if (!(isEditFlow || !isFullMode)) return;
500
+
501
+ const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
502
+
503
+ const initField = (targetString, setVarMap, slotOffset) => {
504
+ const arr = splitTemplateVarStringRcs(targetString);
505
+ if (!arr?.length) {
506
+ setVarMap({});
507
+ return;
508
+ }
509
+ const nextVarMap = {};
510
+ let varOrdinal = 0;
511
+ arr.forEach((elem, idx) => {
512
+ // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
513
+ if (rcsVarTestRegex.test(elem)) {
514
+ const id = `${elem}_${idx}`;
515
+ const varName = getVarNameFromToken(elem);
516
+ const globalSlot = slotOffset + varOrdinal;
517
+ varOrdinal += 1;
518
+ const mappedValue = resolveCardVarMappedSlotValue(
519
+ cardVarMapped,
520
+ varName,
521
+ globalSlot,
522
+ isEditLike,
523
+ );
524
+ nextVarMap[id] = mappedValue;
477
525
  }
478
- const nextVarMap = {};
479
- const nextUpdated = [...arr];
480
- arr.forEach((elem, idx) => {
481
- // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
482
- if (rcsVarTestRegex.test(elem)) {
483
- const id = `${elem}_${idx}`;
484
- const varName = getVarNameFromToken(elem);
485
- const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
486
- nextVarMap[id] = mappedValue;
487
- if (mappedValue !== '') {
488
- nextUpdated[idx] = mappedValue;
489
- } else {
490
- nextUpdated[idx] = elem;
491
- }
492
- }
493
- });
494
- setVarMap(nextVarMap);
495
- setUpdated(nextUpdated);
496
- };
526
+ });
527
+ setVarMap(nextVarMap);
528
+ };
497
529
 
498
- initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
499
- initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
500
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
501
-
502
- useEffect(() => {
503
- if(!isEditFlow && isFullMode){
530
+ initField(templateTitle, setTitleVarMappedData, 0);
531
+ initField(templateDesc, setDescVarMappedData, titleTokenCount);
532
+ }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
533
+
534
+ useEffect(() => {
535
+ if (!isEditFlow && isFullMode) {
504
536
  setRcsVideoSrc({});
505
537
  updateRcsImageSrc('');
506
538
  setUpdateRcsImageSrc('');
507
539
  updateRcsThumbnailSrc('');
508
540
  setAssetList({});
509
- }
510
- }, [templateMediaType]);
541
+ }
542
+ }, [templateMediaType]);
511
543
 
512
- const templateStatusHelper = (details) => {
513
- const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
514
- switch (status) {
515
- case RCS_STATUSES.approved:
544
+ /** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
545
+ const templateStatusHelper = (cardContentFirst) => {
546
+ const raw =
547
+ cardContentFirst?.Status
548
+ ?? cardContentFirst?.status
549
+ ?? cardContentFirst?.approvalStatus
550
+ ?? '';
551
+ const status = typeof raw === 'string' ? raw.trim() : String(raw);
552
+ const n = status.toLowerCase();
553
+ switch (n) {
554
+ case 'approved':
516
555
  setTemplateStatus(RCS_STATUSES.approved);
517
556
  break;
518
- case RCS_STATUSES.pending:
557
+ case 'pending':
519
558
  setTemplateStatus(RCS_STATUSES.pending);
520
559
  break;
521
- case RCS_STATUSES.awaitingApproval:
560
+ case 'awaitingapproval':
522
561
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
523
562
  break;
524
- case RCS_STATUSES.unavailable:
563
+ case 'unavailable':
525
564
  setTemplateStatus(RCS_STATUSES.unavailable);
526
565
  break;
527
- case RCS_STATUSES.rejected:
566
+ case 'rejected':
528
567
  setTemplateStatus(RCS_STATUSES.rejected);
529
568
  break;
569
+ case 'created':
570
+ setTemplateStatus(RCS_STATUSES.created);
571
+ break;
530
572
  default:
531
573
  setTemplateStatus(status);
532
574
  break;
@@ -537,7 +579,6 @@ export const Rcs = (props) => {
537
579
  if (mediaType) {
538
580
  setTemplateMediaType(mediaType);
539
581
  }
540
- const tempOrientation = cardSettings.cardOrientation;
541
582
  const tempAlignment = cardSettings.mediaAlignment;
542
583
  const tempHeight = mediaData.height;
543
584
 
@@ -580,44 +621,197 @@ export const Rcs = (props) => {
580
621
  };
581
622
 
582
623
  useEffect(() => {
583
- const details = isFullMode ? rcsData?.templateDetails : templateData;
624
+ const details = rcsHydrationDetails;
584
625
  if (details && Object.keys(details).length > 0) {
585
- if (!isFullMode) {
586
- const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
587
- setCardVarMapped(tempCardVarMapped);
626
+ // Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
627
+ const cardFromVersions = get(
628
+ details,
629
+ 'versions.base.content.RCS.rcsContent.cardContent[0]',
630
+ );
631
+ const cardFromTop = get(details, 'rcsContent.cardContent[0]');
632
+ const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
633
+ const cardVarMappedFromCardContent =
634
+ card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
635
+ ? card0.cardVarMapped
636
+ : {};
637
+ const cardVarMappedFromRootMirror =
638
+ details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
639
+ ? details.rcsCardVarMapped
640
+ : {};
641
+ // Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
642
+ // nested versions.cardContent[0].cardVarMapped is dropped on reload.
643
+ const mergedCardVarMappedFromPayload = {
644
+ ...cardVarMappedFromRootMirror,
645
+ ...cardVarMappedFromCardContent,
646
+ };
647
+ const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
648
+ const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
649
+ const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
650
+ Object.keys(mergedCardVarMappedFromPayload)
651
+ .sort()
652
+ .reduce((accumulator, mapKey) => {
653
+ accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
654
+ return accumulator;
655
+ }, {}),
656
+ )}`;
657
+ if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
658
+ lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
659
+ setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
588
660
  }
589
- const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
661
+ const tokenListForMap = [
662
+ ...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
663
+ ...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
664
+ ];
665
+ const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
666
+ // Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
667
+ // getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
668
+ const cardVarMappedBeforeCoalesce = isFullMode
669
+ ? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
670
+ : { ...mergedCardVarMappedFromPayload };
671
+ const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
672
+ cardVarMappedBeforeCoalesce,
673
+ loadedTitleForMap,
674
+ loadedDescForMap,
675
+ rcsVarRegex,
676
+ );
677
+ const cardVarMappedAfterNumericSlotSync = !isFullMode
678
+ ? syncCardVarMappedSemanticsFromSlots(
679
+ cardVarMappedAfterCoalesce,
680
+ loadedTitleForMap,
681
+ loadedDescForMap,
682
+ rcsVarRegex,
683
+ )
684
+ : cardVarMappedAfterCoalesce;
685
+ const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
686
+ // Pre-populate variable/tag mappings while opening an existing template in edit flows
687
+ setCardVarMapped((previousVarMapState) => {
688
+ const previousVarMap = previousVarMapState ?? {};
689
+ if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
690
+ const previousVarMapKeys = Object.keys(previousVarMap);
691
+ const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
692
+ if (previousVarMapKeys.length === nextVarMapKeys.length) {
693
+ const allSlotValuesMatchPrevious = previousVarMapKeys.every(
694
+ (key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
695
+ );
696
+ if (allSlotValuesMatchPrevious) return previousVarMapState;
697
+ }
698
+ return hydratedCardVarMappedResult;
699
+ });
700
+ const mediaType =
701
+ card0.mediaType
702
+ || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
590
703
  if (mediaType === RCS_MEDIA_TYPES.NONE) {
591
704
  setTemplateType(contentType.text_message);
592
705
  } else {
593
706
  setTemplateType(contentType.rich_card);
594
707
  }
595
708
  setEditFlow(true);
596
- setTemplateName(details.name || '');
597
- const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
598
- const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
599
- const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
600
- const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
601
- const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
709
+ setTemplateName(details?.name || details?.creativeName || '');
710
+ const loadedTitle = loadedTitleForMap;
711
+ const loadedDesc = loadedDescForMap;
712
+ const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
713
+ loadedTitle,
714
+ loadedDesc,
715
+ isFullMode,
716
+ cardVarMappedAfterHydration: hydratedCardVarMappedResult,
717
+ rcsVarRegex,
718
+ });
602
719
  setTemplateTitle(normalizedTitle);
603
720
  setTemplateDesc(normalizedDesc);
604
- setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
605
- templateStatusHelper(details);
606
- const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
607
- const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
721
+ setSuggestions(
722
+ Array.isArray(card0.suggestions)
723
+ ? card0.suggestions
724
+ : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
725
+ );
726
+ const cardForStatus = {
727
+ ...card0,
728
+ Status:
729
+ card0.Status
730
+ ?? card0.status
731
+ ?? card0.approvalStatus
732
+ ?? get(details, 'templateStatus')
733
+ ?? get(details, 'approvalStatus')
734
+ ?? get(details, 'creativeStatus')
735
+ ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
736
+ ?? '',
737
+ };
738
+ templateStatusHelper(cardForStatus);
739
+ const mediaData =
740
+ card0.media != null && card0.media !== ''
741
+ ? card0.media
742
+ : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
743
+ const cardSettings =
744
+ get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
745
+ || get(details, 'rcsContent.cardSettings', '');
608
746
  setMediaData(mediaData, mediaType, cardSettings);
747
+
748
+ const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
749
+ const base = get(smsFallbackContent, 'versions.base', {});
750
+ const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
751
+ const smsEditor = base['sms-editor'];
752
+ const fromNested = Array.isArray(updatedEditor)
753
+ ? updatedEditor.join('')
754
+ : (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
755
+ const fallbackMessage = smsFallbackContent.smsContent
756
+ || smsFallbackContent.smsTemplateContent
757
+ || smsFallbackContent.message
758
+ || fromNested
759
+ || '';
760
+ const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
761
+ const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
762
+ const hasFallbackPayload =
763
+ smsFallbackContent
764
+ && Object.keys(smsFallbackContent).length > 0
765
+ && (
766
+ !!smsFallbackContent.smsTemplateName
767
+ || !!fallbackMessage
768
+ || hasVarMapped
769
+ );
770
+ if (hasFallbackPayload) {
771
+ if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
772
+ console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
773
+ }
774
+ const unicodeFromApi =
775
+ typeof smsFallbackContent.unicodeValidity === 'boolean'
776
+ ? smsFallbackContent.unicodeValidity
777
+ : (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
778
+ const nextSmsState = {
779
+ templateName: smsFallbackContent.smsTemplateName || '',
780
+ content: fallbackMessage,
781
+ templateContent: fallbackMessage,
782
+ unicodeValidity: unicodeFromApi,
783
+ ...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
784
+ };
785
+ const hydrationKey = JSON.stringify({
786
+ creativeKey: details._id || details.name || details.creativeName || '',
787
+ templateName: nextSmsState.templateName,
788
+ content: nextSmsState.content,
789
+ unicodeValidity: nextSmsState.unicodeValidity,
790
+ varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
791
+ });
792
+ if (
793
+ isFullMode
794
+ || lastSmsFallbackHydrationKeyRef.current !== hydrationKey
795
+ ) {
796
+ lastSmsFallbackHydrationKeyRef.current = hydrationKey;
797
+ setSmsFallbackData(nextSmsState);
798
+ }
799
+ } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
800
+ lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
801
+ setSmsFallbackData(null);
802
+ }
609
803
  }
610
- }, [rcsData, templateData, isFullMode, isEditFlow]);
611
-
804
+ }, [rcsHydrationDetails, isFullMode]);
612
805
 
613
806
  useEffect(() => {
614
807
  if (templateType === contentType.text_message) {
615
808
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
616
- setTemplateTitle('');
617
- setTemplateTitleError('');
809
+ // Full-mode create only: switching to plain text clears draft title/media. Never clear when
810
+ // hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
618
811
  if (!isEditFlow && isFullMode) {
812
+ setTemplateTitle('');
813
+ setTemplateTitleError('');
619
814
  setUpdateRcsImageSrc('');
620
- setUpdateRcsVideoSrc({});
621
815
  setRcsVideoSrc({});
622
816
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
623
817
  }
@@ -644,7 +838,8 @@ export const Rcs = (props) => {
644
838
  if (!showDltContainer) {
645
839
  const { type, module } = location.query || {};
646
840
  const isEmbedded = type === EMBEDDED;
647
- const context = isEmbedded ? module : DEFAULT;
841
+ // Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
842
+ const context = isEmbedded ? module : 'outbound';
648
843
  const embedded = isEmbedded ? type : FULL;
649
844
  const query = {
650
845
  layout: SMS,
@@ -655,9 +850,9 @@ export const Rcs = (props) => {
655
850
  if (getDefaultTags) {
656
851
  query.context = getDefaultTags;
657
852
  }
658
- globalActions.fetchSchemaForEntity(query);
853
+ fetchTagSchemaIfNewQuery(query);
659
854
  }
660
- }, [showDltContainer]);
855
+ }, [showDltContainer, fetchTagSchemaIfNewQuery]);
661
856
 
662
857
  useEffect(() => {
663
858
  let tag = get(metaEntities, `tags.standard`, []);
@@ -680,39 +875,72 @@ export const Rcs = (props) => {
680
875
  context,
681
876
  embedded,
682
877
  };
683
- globalActions.fetchSchemaForEntity(query);
878
+ if (getDefaultTags) {
879
+ query.context = getDefaultTags;
880
+ }
881
+ fetchTagSchemaIfNewQuery(query);
684
882
  };
685
883
 
686
- const onTagSelect = (data, areaId) => {
884
+ const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
885
+ if (!templateStr || !numericVarName || !tagName) return templateStr;
886
+ const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
887
+ return templateStr.replace(re, `{{${tagName}}}`);
888
+ };
889
+
890
+ const onTagSelect = (data, areaId, field) => {
687
891
  if (!areaId) return;
688
892
  const sep = areaId.lastIndexOf('_');
689
893
  if (sep === -1) return;
690
- const numId = Number(areaId.slice(sep + 1));
691
- if (isNaN(numId)) return;
894
+ const slotSuffix = areaId.slice(sep + 1);
895
+ if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
692
896
  const token = areaId.slice(0, sep);
693
897
  const variableName = getVarNameFromToken(token);
694
898
  if (!variableName) return;
899
+ const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
900
+ const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
901
+ const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
902
+ const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
903
+
695
904
  setCardVarMapped((prev) => {
696
905
  const base = (prev?.[variableName] ?? '').toString();
697
906
  const nextVal = `${base}{{${data}}}`;
698
- return {
699
- ...(prev || {}),
700
- [variableName]: nextVal,
701
- };
907
+ const next = { ...(prev || {}) };
908
+ if (isNumericSlot) {
909
+ delete next[variableName];
910
+ next[data] = nextVal;
911
+ } else {
912
+ next[variableName] = nextVal;
913
+ // resolveCardVarMappedSlotValue prefers numeric slot keys ("1","2",…) over semantic names;
914
+ // hydration may set "1": ''. Use global slot index — suffix is segment index (includes static text), not var ordinal.
915
+ if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
916
+ next[String(globalSlotForArea + 1)] = nextVal;
917
+ }
918
+ }
919
+ return next;
702
920
  });
703
- };
704
-
705
- const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
706
921
 
707
- const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
708
-
709
- const onTagSelectFallback = (data) => {
710
- const tempMsg = `${fallbackMessage}{{${data}}}`;
711
- const error = fallbackMessageErrorHandler(tempMsg);
712
- setFallbackMessage(tempMsg);
713
- setFallbackMessageError(error);
922
+ if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
923
+ if (field === RCS_TAG_AREA_FIELD_TITLE) {
924
+ setTemplateTitle((prev) => {
925
+ const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
926
+ if (nextStr === prev) return prev;
927
+ setTemplateTitleError(variableErrorHandling(nextStr));
928
+ return nextStr;
929
+ });
930
+ } else {
931
+ setTemplateDesc((prev) => {
932
+ const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
933
+ if (nextStr === prev) return prev;
934
+ setTemplateDescError(variableErrorHandling(nextStr));
935
+ return nextStr;
936
+ });
937
+ }
938
+ }
714
939
  };
715
940
 
941
+ const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
942
+
943
+ const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
716
944
 
717
945
  //removing optout tag for rcs
718
946
  const getRcsTags = () => {
@@ -725,15 +953,14 @@ export const Rcs = (props) => {
725
953
  };
726
954
  // tag Code end
727
955
 
728
- const renderLabel = (value, showLabel, desc) => {
729
- const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
956
+ const renderLabel = (value, desc) => {
730
957
  return (
731
958
  <>
732
- <RcsLabel>
959
+ <div className="rcs-form-section-heading">
733
960
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
734
- </RcsLabel>
961
+ </div>
735
962
  {desc && (
736
- <CapLabel type="label3" style={{ marginBottom: '17px' }}>
963
+ <CapLabel type="label3" className="rcs-form-field-caption">
737
964
  {formatMessage(messages[desc])}
738
965
  </CapLabel>
739
966
  )}
@@ -819,47 +1046,6 @@ export const Rcs = (props) => {
819
1046
  setTemplateDescError(error);
820
1047
  };
821
1048
 
822
-
823
- const templateDescErrorHandler = (value) => {
824
- let errorMessage = false;
825
- const { isBraceError } = validateTags({
826
- content: value,
827
- tagsParam: tags,
828
- location,
829
- tagModule: getDefaultTags,
830
- isFullMode,
831
- }) || {};
832
-
833
- const maxLength = templateType === contentType.text_message
834
- ? RCS_TEXT_MESSAGE_MAX_LENGTH
835
- : RCS_RICH_CARD_MAX_LENGTH;
836
-
837
- if (value === '' && isMediaTypeText) {
838
- errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
839
- } else if (value?.length > maxLength) {
840
- errorMessage = formatMessage(messages.templateMessageLengthError);
841
- }
842
-
843
- if (isBraceError) {
844
- errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
845
- }
846
- return errorMessage;
847
- };
848
-
849
-
850
- const onFallbackMessageChange = ({ target: { value } }) => {
851
- const error = fallbackMessageErrorHandler(value);
852
- setFallbackMessage(value);
853
- setFallbackMessageError(error);
854
- };
855
-
856
- const fallbackMessageErrorHandler = (value) => {
857
- if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
858
- return formatMessage(messages.fallbackMsgLenError);
859
- }
860
- return false;
861
- };
862
-
863
1049
  // Check for forbidden characters: square brackets [] and single curly braces {}
864
1050
  const forbiddenCharactersValidation = (value) => {
865
1051
  if (!value) return false;
@@ -910,39 +1096,12 @@ export const Rcs = (props) => {
910
1096
  }
911
1097
  return false;
912
1098
  };
913
-
914
- const templateHeaderErrorHandler = (value) => {
915
- let errorMessage = false;
916
- if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
917
- errorMessage = formatMessage(messages.templateHeaderLengthError);
918
- } else {
919
- errorMessage = variableErrorHandling(value);
920
- }
921
- return errorMessage;
922
- };
923
-
924
-
925
- const templateMessageErrorHandler = (value) => {
926
- let errorMessage = false;
927
- if (value === '') {
928
- errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
929
- } else if (
930
- value?.length
931
- > TEMPLATE_MESSAGE_MAX_LENGTH
932
- ) {
933
- errorMessage = formatMessage(messages.templateMessageLengthError);
934
- } else {
935
- errorMessage = variableErrorHandling(value);
936
- }
937
- return errorMessage;
938
- };
939
-
940
1099
 
941
1100
  const onMessageAddVar = () => {
942
- onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
1101
+ onAddVar(templateDesc);
943
1102
  };
944
1103
 
945
- const onAddVar = (type, messageContent, regex) => {
1104
+ const onAddVar = (messageContent) => {
946
1105
  // Always append the next variable at the end, like WhatsApp
947
1106
  const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
948
1107
  const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
@@ -982,66 +1141,6 @@ const onTitleAddVar = () => {
982
1141
  setTemplateTitleError(error);
983
1142
  };
984
1143
 
985
-
986
- const splitTemplateVarString = (str) => {
987
- if (!str) return [];
988
- const validVarArr = str.match(rcsVarRegex) || [];
989
- const templateVarArray = [];
990
- let content = str;
991
- while (content?.length !== 0) {
992
- const index = content.indexOf(validVarArr?.[0]);
993
- if (index !== -1) {
994
- templateVarArray.push(content.substring(0, index));
995
- templateVarArray.push(validVarArr?.[0]);
996
- content = content.substring(index + validVarArr?.[0]?.length, content?.length);
997
- validVarArr?.shift();
998
- } else {
999
- templateVarArray.push(content);
1000
- break;
1001
- }
1002
- }
1003
- return templateVarArray.filter(Boolean);
1004
- };
1005
-
1006
- const textAreaValue = (idValue, type) => {
1007
- if (idValue >= 0) {
1008
- const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1009
- const templateArr = splitTemplateVarString(templateStr);
1010
- const token = templateArr?.[idValue] || "";
1011
- if (token && rcsVarTestRegex.test(token)) {
1012
- const varName = getVarNameFromToken(token);
1013
- return (cardVarMapped?.[varName] ?? '').toString();
1014
- }
1015
- return "";
1016
- }
1017
- return "";
1018
- };
1019
-
1020
- const textAreaValueChange = (e, type) => {
1021
- const value = e?.target?.value ?? '';
1022
- const id = e?.target?.id || e?.currentTarget?.id || '';
1023
- if (!id) return;
1024
- const sep = id.lastIndexOf('_');
1025
- if (sep === -1) return;
1026
- const isInvalidValue = value?.trim() === "";
1027
- const token = id.slice(0, sep);
1028
- const variableName = getVarNameFromToken(token);
1029
-
1030
- if (variableName) {
1031
- setCardVarMapped((prev) => ({
1032
- ...prev,
1033
- [variableName]: isInvalidValue ? "" : value,
1034
- }));
1035
- }
1036
- };
1037
-
1038
- const setTextAreaId = (e, type) => {
1039
- const id = e?.target?.id || e?.currentTarget?.id || '';
1040
- if (!id) return;
1041
- if (type === TITLE_TEXT) setTitleTextAreaId(id);
1042
- else setDescTextAreaId(id);
1043
- };
1044
-
1045
1144
  const renderButtonComponent = () => {
1046
1145
  return (
1047
1146
  <>
@@ -1072,39 +1171,56 @@ const splitTemplateVarString = (str) => {
1072
1171
  );
1073
1172
  };
1074
1173
 
1075
- const renderedRCSEditMessage = (descArray, type) => {
1076
- const renderArray = [];
1077
- if (descArray?.length) {
1078
- descArray.forEach((elem, index) => {
1079
- if (rcsVarTestRegex.test(elem)) {
1080
- // Variable input
1081
- renderArray.push(
1082
- <TextArea
1083
- id={`${elem}_${index}`}
1084
- key={`${elem}_${index}`}
1085
- placeholder={`enter the value for ${elem}`}
1086
- autosize={{ minRows: 1, maxRows: 3 }}
1087
- onChange={e => textAreaValueChange(e, type)}
1088
- value={textAreaValue(index, type)}
1089
- onFocus={(e) => setTextAreaId(e, type)}
1090
- />
1091
- );
1092
- } else if (elem) {
1093
- // Static text
1094
- renderArray.push(
1095
- <TextArea
1096
- key={`static_${index}`}
1097
- value={elem}
1098
- autosize={{ minRows: 1, maxRows: 3 }}
1099
- disabled
1100
- className="rcs-edit-template-message-static-textarea"
1101
- style={{ background: '#fafafa', color: '#888' }}
1102
- />
1103
- );
1104
- }
1105
- });
1106
- }
1107
- return renderArray;
1174
+ const getRcsValueMap = (fieldTemplateString, fieldType) => {
1175
+ if (!fieldTemplateString) return {};
1176
+ const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
1177
+ const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
1178
+ const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
1179
+ const segmentIdToResolvedValue = {};
1180
+ let varOrdinal = 0;
1181
+ templateSegments.forEach((segmentToken, segmentIndexInField) => {
1182
+ if (rcsVarTestRegex.test(segmentToken)) {
1183
+ const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
1184
+ const varName = getVarNameFromToken(segmentToken);
1185
+ const globalSlot = slotOffset + varOrdinal;
1186
+ varOrdinal += 1;
1187
+ segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
1188
+ cardVarMapped,
1189
+ varName,
1190
+ globalSlot,
1191
+ isEditLike,
1192
+ );
1193
+ }
1194
+ });
1195
+ return segmentIdToResolvedValue;
1196
+ };
1197
+
1198
+ const titleVarSegmentValueMapById = useMemo(
1199
+ () => getRcsValueMap(templateTitle, TITLE_TEXT),
1200
+ [templateTitle, cardVarMapped, isEditFlow, isFullMode],
1201
+ );
1202
+ const descriptionVarSegmentValueMapById = useMemo(
1203
+ () => getRcsValueMap(templateDesc, MESSAGE_TEXT),
1204
+ [templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
1205
+ );
1206
+
1207
+ const handleRcsVarChange = (id, value, type) => {
1208
+ const sep = id.lastIndexOf('_');
1209
+ if (sep === -1) return;
1210
+ const token = id.slice(0, sep);
1211
+ const variableName = getVarNameFromToken(token);
1212
+ if (variableName === undefined || variableName === null || variableName === '') return;
1213
+ const isInvalidValue = value?.trim() === '';
1214
+ const coercedSlotValue = isInvalidValue ? '' : value;
1215
+ const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1216
+ const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
1217
+ setCardVarMapped((previousVarMap) => {
1218
+ const nextVarMap = { ...previousVarMap, [variableName]: coercedSlotValue };
1219
+ if (globalSlot !== null && globalSlot !== undefined) {
1220
+ nextVarMap[String(globalSlot + 1)] = coercedSlotValue;
1221
+ }
1222
+ return nextVarMap;
1223
+ });
1108
1224
  };
1109
1225
 
1110
1226
  const renderTextComponent = () => {
@@ -1148,10 +1264,19 @@ const splitTemplateVarString = (str) => {
1148
1264
  </>
1149
1265
  }
1150
1266
  />
1151
- <div className="rcs_text_area_wrapper">
1152
1267
  {(isEditFlow || !isFullMode) ? (
1153
- renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
1268
+ <VarSegmentMessageEditor
1269
+ key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
1270
+ templateString={templateTitle}
1271
+ valueMap={titleVarSegmentValueMapById}
1272
+ onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
1273
+ onFocus={(id) => setTitleTextAreaId(id)}
1274
+ varRegex={rcsVarRegex}
1275
+ placeholderPrefix=""
1276
+ getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1277
+ />
1154
1278
  ) : (
1279
+ <div className="rcs_text_area_wrapper">
1155
1280
  <CapInput
1156
1281
  className={`rcs-template-title-input ${
1157
1282
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -1164,8 +1289,8 @@ const splitTemplateVarString = (str) => {
1164
1289
  errorMessage={templateTitleError}
1165
1290
  disabled={isEditFlow || !isFullMode}
1166
1291
  />
1292
+ </div>
1167
1293
  )}
1168
- </div>
1169
1294
  {(isEditFlow || !isFullMode) && templateTitleError && (
1170
1295
  <CapError className="rcs-template-title-error">
1171
1296
  {templateTitleError}
@@ -1213,7 +1338,18 @@ const splitTemplateVarString = (str) => {
1213
1338
  <CapRow className="rcs-create-template-message-input">
1214
1339
  <div className="rcs_text_area_wrapper">
1215
1340
  {(isEditFlow || !isFullMode)
1216
- ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
1341
+ ? (
1342
+ <VarSegmentMessageEditor
1343
+ key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
1344
+ templateString={templateDesc}
1345
+ valueMap={descriptionVarSegmentValueMapById}
1346
+ onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
1347
+ onFocus={(id) => setDescTextAreaId(id)}
1348
+ varRegex={rcsVarRegex}
1349
+ placeholderPrefix=""
1350
+ getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1351
+ />
1352
+ )
1217
1353
  : (
1218
1354
  <>
1219
1355
  <TextArea
@@ -1257,7 +1393,9 @@ const splitTemplateVarString = (str) => {
1257
1393
  {templateDescError}
1258
1394
  </CapError>
1259
1395
  )}
1260
- {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
1396
+ {(isEditFlow || !isFullMode)
1397
+ ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
1398
+ : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1261
1399
  {!isFullMode && hasTag() && (
1262
1400
  <CapAlert
1263
1401
  message={
@@ -1277,18 +1415,6 @@ const splitTemplateVarString = (str) => {
1277
1415
  );
1278
1416
  };
1279
1417
 
1280
-
1281
- const fallbackSmsLength = () => (
1282
- <CapLabel type="label1" className="fallback-sms-length">
1283
- {formatMessage(messages.totalCharacters, {
1284
- smsCount: Math.ceil(
1285
- fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1286
- ),
1287
- number: fallbackMessage?.length,
1288
- })}
1289
- </CapLabel>
1290
- );
1291
-
1292
1418
  // Get character count for title (rich card only)
1293
1419
  const getTitleCharacterCount = () => {
1294
1420
  if (templateType === contentType.text_message) return 0;
@@ -1348,7 +1474,7 @@ const splitTemplateVarString = (str) => {
1348
1474
  const hasTag = () => {
1349
1475
  // Check cardVarMapped values for tags
1350
1476
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1351
- const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1477
+ const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
1352
1478
  isTagIncluded(value)
1353
1479
  );
1354
1480
  if (hasTagInMapped) return true;
@@ -1366,14 +1492,6 @@ const splitTemplateVarString = (str) => {
1366
1492
  return false;
1367
1493
  };
1368
1494
 
1369
- //adding creative dlt fallback sms handlers
1370
- const addDltMsgHandler = () => {
1371
- setShowDltContainer(true);
1372
- setDltMode(RCS_DLT_MODE.TEMPLATES);
1373
- setDltEditData({});
1374
- setFallbackMessage('');
1375
- };
1376
-
1377
1495
  const closeDltContainerHandler = () => {
1378
1496
  setShowDltContainer(false);
1379
1497
  setDltMode('');
@@ -1396,68 +1514,17 @@ const splitTemplateVarString = (str) => {
1396
1514
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1397
1515
  '',
1398
1516
  );
1517
+ const templateNameFromDlt = get(dltEditData, 'name', '')
1518
+ || get(tempData, 'versions.base.name', '')
1519
+ || '';
1399
1520
  closeDltContainerHandler();
1400
1521
  setDltEditData(tempData);
1401
- setFallbackMessage(fallMsg);
1402
- setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1403
- setShowDltCard(true);
1404
- };
1405
-
1406
- const rcsDltCardDeleteHandler = () => {
1407
- closeDltContainerHandler();
1408
- setDltEditData({});
1409
- setFallbackMessage('');
1410
- setFallbackMessageError(false);
1411
- setShowDltCard(false);
1412
- };
1413
-
1414
- const dltFallbackListingPreviewhandler = (data) => {
1415
- const {
1416
- 'updated-sms-editor': updatedSmsEditor = [],
1417
- 'sms-editor': smsEditor = '',
1418
- } = data.versions.base || {};
1419
- setFallbackPreviewmode(true);
1420
- setDltPreviewData(
1421
- updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1422
- );
1423
- };
1424
-
1425
- const getDltContentCardList = (content, channel) => {
1426
- const extra = [
1427
- <CapIcon
1428
- type="edit"
1429
- style={{ marginRight: '8px' }}
1430
- onClick={() => rcsDltEditSelectHandler(dltEditData)}
1431
- />,
1432
- <CapDropdown
1433
- overlay={(
1434
- <CapMenu>
1435
- <>
1436
- <CapMenu.Item
1437
- className="ant-dropdown-menu-item"
1438
- onClick={() => setFallbackPreviewmode(true)}
1439
- >
1440
- {formatMessage(globalMessages.preview)}
1441
- </CapMenu.Item>
1442
- <CapMenu.Item
1443
- className="ant-dropdown-menu-item"
1444
- onClick={rcsDltCardDeleteHandler}
1445
- >
1446
- {formatMessage(globalMessages.delete)}
1447
- </CapMenu.Item>
1448
- </>
1449
- </CapMenu>
1450
- )}
1451
- >
1452
- <CapIcon type="more" />
1453
- </CapDropdown>,
1454
- ];
1455
- return {
1456
- title: channel,
1457
- content,
1458
- cardType: channel,
1459
- extra,
1460
- };
1522
+ const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
1523
+ setSmsFallbackData({
1524
+ templateName: templateNameFromDlt,
1525
+ content: fallMsg,
1526
+ ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
1527
+ });
1461
1528
  };
1462
1529
 
1463
1530
  const getDltSlideBoxContent = () => {
@@ -1484,7 +1551,6 @@ const splitTemplateVarString = (str) => {
1484
1551
  isFullMode={isFullMode}
1485
1552
  isDltFromRcs
1486
1553
  onSelectTemplate={rcsDltEditSelectHandler}
1487
- handlePeviewTemplate={dltFallbackListingPreviewhandler}
1488
1554
  />
1489
1555
  );
1490
1556
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1505,147 +1571,32 @@ const splitTemplateVarString = (str) => {
1505
1571
  return { dltHeader, dltContent };
1506
1572
  };
1507
1573
 
1508
- const renderFallBackSmsComponent = () => {
1509
- // Completely disable fallback functionality when DLT is disabled
1510
- return null;
1511
-
1512
- // const contentArr = [];
1513
- // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1514
- // const showCardForDlt = isDltEnabled && showDltCard;
1515
- // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1516
- // //pushing common fallback sms headings
1517
- // contentArr.push(
1518
- // <CapRow
1519
- // style={{
1520
- // marginBottom: isDltEnabled ? '20px' : '10px',
1521
- // }}
1522
- // >
1523
- // <CapHeader
1524
- // title={(
1525
- // <CapRow type="flex">
1526
- // <CapHeading type="h4">
1527
- // {formatMessage(messages.fallbackLabel)}
1528
- // </CapHeading>
1529
- // <CapTooltipWithInfo
1530
- // placement="right"
1531
- // infoIconProps={{
1532
- // style: { marginLeft: '4px', marginTop: '3px' },
1533
- // }}
1534
- // title={formatMessage(messages.fallbackToolTip)}
1535
- // />
1536
- // </CapRow>
1537
- // )}
1538
- // description={formatMessage(messages.fallbackDesc)}
1539
- // suffix={
1540
- // isDltEnabled ? null : (
1541
- // <CapButton
1542
- // type="flat"
1543
- // className="fallback-preview-btn"
1544
- // prefix={<CapIcon type="eye" />}
1545
- // style={{ color: CAP_SECONDARY.base }}
1546
- // onClick={() => setFallbackPreviewmode(true)}
1547
- // disabled={fallbackMessage === '' || fallbackMessageError}
1548
- // >
1549
- // {formatMessage(globalMessages.preview)}
1550
- // </CapButton>
1551
- // )
1552
- // }
1553
- // />
1554
- // </CapRow>,
1555
- // );
1556
-
1557
- //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1558
- // showAddCreativeBtnForDlt
1559
- // && contentArr.push(
1560
- // <CapCard className="rcs-dlt-fallback-card">
1561
- // <CapRow type="flex" justify="center" align="middle">
1562
- // <CapColumn span={10}>
1563
- // <CapImage src={addCreativesIcon} />
1564
- // </CapColumn>
1565
- // <CapColumn span={14}>
1566
- // <CapButton
1567
- // className="add-dlt-btn"
1568
- // type="secondary"
1569
- // onClick={addDltMsgHandler}
1570
- // >
1571
- // {formatMessage(messages.addSmsCreative)}
1572
- // </CapButton>
1573
- // </CapColumn>
1574
- // </CapRow>
1575
- // </CapCard>,
1576
- // );
1577
-
1578
- // //dlt is enabled and dlt content is added, show it in a card
1579
- // showCardForDlt
1580
- // && contentArr.push(
1581
- // <CapCustomCardList
1582
- // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1583
- // className="rcs-dlt-card"
1584
- // />,
1585
- // fallbackMessageError && (
1586
- // <CapError className="rcs-fallback-len-error">
1587
- // {formatMessage(messages.fallbackMsgLenError)}
1588
- // </CapError>
1589
- // ),
1590
- // );
1591
-
1592
- // //dlt is not enabled, show non dlt text area
1593
- // showNonDltFallbackComp
1594
- // && contentArr.push(
1595
- // <>
1596
- // <CapRow>
1597
- // <CapHeader
1598
- // title={(
1599
- // <CapHeading type="h4">
1600
- // {formatMessage(messages.fallbackTextAreaLabel)}
1601
- // </CapHeading>
1602
- // )}
1603
- // suffix={(
1604
- // <TagList
1605
- // label={formatMessage(globalMessages.addLabels)}
1606
- // onTagSelect={onTagSelectFallback}
1607
- // location={location}
1608
- // tags={tags || []}
1609
- // onContextChange={handleOnTagsContextChange}
1610
- // injectedTags={injectedTags || {}}
1611
- // selectedOfferDetails={selectedOfferDetails}
1612
- // />
1613
- // )}
1614
- // />
1615
- // </CapRow>
1616
- // <div className="rcs_fallback_msg_textarea_wrapper">
1617
- // <TextArea
1618
- // id="rcs_fallback_message_textarea"
1619
- // autosize={{ minRows: 3, maxRows: 5 }}
1620
- // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1621
- // onChange={onFallbackMessageChange}
1622
- // errorMessage={fallbackMessageError}
1623
- // value={fallbackMessage || ""}
1624
- // />
1625
- // {!aiContentBotDisabled && (
1626
- // <CapAskAira.ContentGenerationBot
1627
- // text={fallbackMessage || ""}
1628
- // setText={(text) => {
1629
- // onFallbackMessageChange({ target: { value: text } });
1630
- // }}
1631
- // iconPlacement="float-br"
1632
- // rootStyle={{
1633
- // bottom: "0.5rem",
1634
- // right: "0.5rem",
1635
- // position: "absolute",
1636
- // }}
1637
- // />
1638
- // )}
1639
- // </div>
1640
- // <CapRow>{fallbackSmsLength()}</CapRow>
1641
- // </>
1642
- // );
1643
-
1644
- // return <>{contentArr}</>;
1645
- };
1574
+ const renderFallBackSmsComponent = () => (
1575
+ <SmsFallback
1576
+ value={smsFallbackData}
1577
+ onChange={setSmsFallbackData}
1578
+ parentLocation={location}
1579
+ smsRegister={smsRegister}
1580
+ isFullMode={isFullMode}
1581
+ selectedOfferDetails={selectedOfferDetails}
1582
+ channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
1583
+ sectionTitle={
1584
+ smsFallbackData
1585
+ ? formatMessage(messages.fallbackLabel)
1586
+ : formatMessage(messages.smsFallbackOptional)
1587
+ }
1588
+ templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
1589
+ templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
1590
+ /* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
1591
+ showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
1592
+ disableSelectTemplate={isEditFlow}
1593
+ eventContextTags={eventContextTags}
1594
+ onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
1595
+ isRcsEditFlow={isEditFlow}
1596
+ />
1597
+ );
1646
1598
 
1647
1599
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1648
- setImageError(null);
1649
1600
  const isRcsThumbnail = index === 1;
1650
1601
  actions.uploadRcsAsset(file, type, {
1651
1602
  isRcsThumbnail,
@@ -1657,7 +1608,6 @@ const splitTemplateVarString = (str) => {
1657
1608
 
1658
1609
  const setUpdateRcsImageSrc = useCallback(
1659
1610
  (val) => {
1660
- setAssetListImage(val);
1661
1611
  updateRcsImageSrc(val);
1662
1612
  actions.clearRcsMediaAsset(0);
1663
1613
  },
@@ -1687,7 +1637,6 @@ const splitTemplateVarString = (str) => {
1687
1637
 
1688
1638
 
1689
1639
  const uploadRcsVideo = (file, type, fileParams) => {
1690
- setImageError(null);
1691
1640
  actions.uploadRcsAsset(file, type, {
1692
1641
  ...fileParams,
1693
1642
  type: 'video',
@@ -1696,9 +1645,6 @@ const splitTemplateVarString = (str) => {
1696
1645
  });
1697
1646
  };
1698
1647
 
1699
- const updateRcsVideoSrc = (val) => {
1700
- setRcsVideoSrc(val);
1701
- };
1702
1648
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
1703
1649
  setRcsVideoSrc(val);
1704
1650
  setAssetList(val);
@@ -1727,7 +1673,7 @@ const splitTemplateVarString = (str) => {
1727
1673
  <>
1728
1674
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1729
1675
  <CapImageUpload
1730
- style={{ paddingTop: '20px' }}
1676
+ className="cap-custom-image-upload rcs-image-upload--top-spacing"
1731
1677
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1732
1678
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1733
1679
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1739,7 +1685,6 @@ const splitTemplateVarString = (str) => {
1739
1685
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1740
1686
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1741
1687
  index={1}
1742
- className="cap-custom-image-upload"
1743
1688
  key={`rcs-uploaded-image-${currentDimension}`}
1744
1689
  imageData={thumbnailData}
1745
1690
  channel={RCS}
@@ -1770,7 +1715,7 @@ const splitTemplateVarString = (str) => {
1770
1715
  value: dim.type,
1771
1716
  label: `${dim.label}`
1772
1717
  }))}
1773
- style={{ marginBottom: '20px' }}
1718
+ className="rcs-dimension-select--bottom-spacing"
1774
1719
  />
1775
1720
  </>
1776
1721
  )}
@@ -1784,7 +1729,6 @@ const splitTemplateVarString = (str) => {
1784
1729
  </div>
1785
1730
  ) : (
1786
1731
  <CapImageUpload
1787
- style={{ paddingTop: '20px' }}
1788
1732
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1789
1733
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1790
1734
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1795,7 +1739,7 @@ const splitTemplateVarString = (str) => {
1795
1739
  updateImageSrc={setUpdateRcsImageSrc}
1796
1740
  updateOnReUpload={updateOnRcsImageReUpload}
1797
1741
  index={0}
1798
- className="cap-custom-image-upload"
1742
+ className="cap-custom-image-upload rcs-image-upload--top-spacing"
1799
1743
  key={`rcs-uploaded-image-${selectedDimension}`}
1800
1744
  imageData={rcsData}
1801
1745
  channel={RCS}
@@ -1825,7 +1769,7 @@ const splitTemplateVarString = (str) => {
1825
1769
  value: dim.type,
1826
1770
  label: `${dim.label}`
1827
1771
  }))}
1828
- style={{ marginBottom: '20px' }}
1772
+ className="rcs-dimension-select--bottom-spacing"
1829
1773
  />
1830
1774
  )}
1831
1775
  {(isEditFlow || !isFullMode) ? (
@@ -1885,8 +1829,16 @@ const splitTemplateVarString = (str) => {
1885
1829
  const getRcsPreview = () => {
1886
1830
 
1887
1831
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
1888
- const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1889
- const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1832
+ const isSlotMappingMode = isEditFlow || !isFullMode;
1833
+ const titleVarCountForResolve = isMediaTypeText
1834
+ ? 0
1835
+ : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
1836
+ const resolvedTitle = isMediaTypeText
1837
+ ? ''
1838
+ : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
1839
+ const resolvedDesc = isSlotMappingMode
1840
+ ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
1841
+ : templateDesc;
1890
1842
  return (
1891
1843
  <UnifiedPreview
1892
1844
  channel={RCS}
@@ -1909,51 +1861,65 @@ const splitTemplateVarString = (str) => {
1909
1861
  );
1910
1862
  };
1911
1863
 
1912
- const getUnmappedDesc = (str, mapping) => {
1913
- if (!str) return '';
1914
- if (!mapping || Object.keys(mapping).length === 0) return str;
1915
- let result = str;
1916
- const replacements = [];
1917
- Object.entries(mapping).forEach(([key, value]) => {
1918
- const raw = (value ?? '').toString();
1919
- if (!raw || raw?.trim?.() === '') return;
1920
- const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1921
- replacements.push({ key, needle: raw });
1922
- if (braced !== raw) replacements.push({ key, needle: braced });
1923
- });
1924
- const seen = new Set();
1925
- const uniq = replacements
1926
- .filter(({ key, needle }) => {
1927
- const id = `${key}::${needle}`;
1928
- if (seen.has(id)) return false;
1929
- seen.add(id);
1930
- return true;
1931
- })
1932
- .sort((a, b) => (b.needle.length - a.needle.length));
1933
-
1934
- uniq.forEach(({ key, needle }) => {
1935
- if (!needle) return;
1936
- const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1937
- const regex = new RegExp(escaped, 'g');
1938
- result = result.replace(regex, `{{${key}}}`);
1939
- });
1940
- return result;
1941
- };
1942
-
1943
1864
  const createPayload = () => {
1944
- const base = get(dltEditData, `versions.base`, {});
1945
- const {
1946
- template_id: templateId = '',
1947
- template_name = '',
1948
- 'sms-editor': template = '',
1949
- header: registeredSenderIds = [],
1950
- } = base;
1951
- const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1952
- const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1865
+ const isSlotMappingMode = isEditFlow || !isFullMode;
1953
1866
  const alignment = isMediaTypeImage
1954
1867
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
1955
1868
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
1956
1869
 
1870
+ const heightTypeForCardWidth = isMediaTypeText
1871
+ ? undefined
1872
+ : isMediaTypeImage
1873
+ ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
1874
+ : isMediaTypeVideo
1875
+ ? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
1876
+ : undefined;
1877
+ const cardWidthFromSelection =
1878
+ heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
1879
+
1880
+ /** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
1881
+ const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
1882
+ const smsFallbackMerged = !isFullMode
1883
+ ? (() => {
1884
+ const local =
1885
+ smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
1886
+ return {
1887
+ ...smsFromApiShape,
1888
+ ...local,
1889
+ rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
1890
+ };
1891
+ })()
1892
+ : (smsFallbackData || {});
1893
+ const smsFallbackForPayload = (() => {
1894
+ if (isFullMode) {
1895
+ return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
1896
+ }
1897
+ const mapped = {
1898
+ templateName:
1899
+ smsFallbackMerged.templateName
1900
+ || smsFallbackMerged.smsTemplateName
1901
+ || '',
1902
+ // Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
1903
+ content:
1904
+ smsFallbackMerged.content
1905
+ || smsFallbackMerged.smsContent
1906
+ || smsFallbackMerged.smsTemplateContent
1907
+ || smsFallbackMerged.message
1908
+ || '',
1909
+ templateContent:
1910
+ pickFirstSmsFallbackTemplateString(smsFallbackMerged)
1911
+ || '',
1912
+ ...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
1913
+ && { unicodeValidity: smsFallbackMerged.unicodeValidity }),
1914
+ ...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
1915
+ && Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
1916
+ [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
1917
+ smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
1918
+ }),
1919
+ };
1920
+ return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
1921
+ })();
1922
+
1957
1923
  const payload = {
1958
1924
  name: templateName,
1959
1925
  versions: {
@@ -1965,12 +1931,14 @@ const splitTemplateVarString = (str) => {
1965
1931
  cardSettings: {
1966
1932
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
1967
1933
  ...(alignment && { mediaAlignment: alignment }),
1968
- cardWidth: SMALL,
1934
+ cardWidth: cardWidthFromSelection,
1969
1935
  },
1970
1936
  cardContent: [
1971
1937
  {
1972
- title: resolvedTitle,
1973
- description: resolvedDesc,
1938
+ // Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
1939
+ // / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
1940
+ title: templateTitle,
1941
+ description: templateDesc,
1974
1942
  mediaType: templateMediaType,
1975
1943
  ...(!isMediaTypeText && {media: {
1976
1944
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -1979,23 +1947,30 @@ const splitTemplateVarString = (str) => {
1979
1947
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
1980
1948
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
1981
1949
  }}),
1982
- ...(!isFullMode && (() => {
1983
- const tokens = [
1984
- ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1985
- ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
1950
+ ...(isSlotMappingMode && (() => {
1951
+ const templateVarTokens = [
1952
+ ...(templateTitle?.match(rcsVarRegex) ?? []),
1953
+ ...(templateDesc?.match(rcsVarRegex) ?? []),
1986
1954
  ];
1987
- const allowedKeys = tokens
1988
- .map((t) => getVarNameFromToken(t))
1989
- .filter(Boolean);
1990
- const nextMap = {};
1991
- allowedKeys.forEach((k) => {
1992
- if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
1993
- nextMap[k] = cardVarMapped[k];
1994
- } else {
1995
- nextMap[k] = '';
1955
+ const persistedSlotVarMap = {};
1956
+ const seenSemanticVarNames = new Set();
1957
+ templateVarTokens.forEach((token, slotIndexZeroBased) => {
1958
+ const varName = getVarNameFromToken(token);
1959
+ if (!varName) return;
1960
+ const resolvedRawValue = resolveCardVarMappedSlotValue(
1961
+ cardVarMapped,
1962
+ varName,
1963
+ slotIndexZeroBased,
1964
+ isSlotMappingMode,
1965
+ );
1966
+ const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
1967
+ persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
1968
+ if (!seenSemanticVarNames.has(varName)) {
1969
+ seenSemanticVarNames.add(varName);
1970
+ persistedSlotVarMap[varName] = sanitizedSlotValue;
1996
1971
  }
1997
1972
  });
1998
- return { cardVarMapped: nextMap };
1973
+ return { cardVarMapped: persistedSlotVarMap };
1999
1974
  })()),
2000
1975
  ...(suggestions.length > 0 && { suggestions }),
2001
1976
  }
@@ -2003,17 +1978,29 @@ const splitTemplateVarString = (str) => {
2003
1978
  contentType: isFullMode ? templateType : RICHCARD,
2004
1979
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2005
1980
  },
2006
- smsFallBackContent: {
2007
- message: fallbackMessage,
2008
- ...(isDltEnabled && {
2009
- templateConfigs: {
2010
- templateId,
2011
- templateName: template_name,
2012
- template,
2013
- registeredSenderIds,
1981
+ ...(smsFallbackForPayload && (() => {
1982
+ const smsBodyText =
1983
+ smsFallbackForPayload.content
1984
+ || smsFallbackForPayload.templateContent
1985
+ || smsFallbackForPayload.message
1986
+ || smsFallbackForPayload.smsContent
1987
+ || '';
1988
+ return {
1989
+ smsFallBackContent: {
1990
+ smsTemplateName: smsFallbackForPayload.templateName || '',
1991
+ smsContent: smsBodyText,
1992
+ // cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
1993
+ message: smsBodyText,
1994
+ ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
1995
+ unicodeValidity: smsFallbackForPayload.unicodeValidity,
1996
+ }),
1997
+ ...(smsFallbackForPayload.rcsSmsFallbackVarMapped
1998
+ && Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
1999
+ [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
2000
+ }),
2014
2001
  },
2015
- }),
2016
- },
2002
+ };
2003
+ })()),
2017
2004
  },
2018
2005
  },
2019
2006
  },
@@ -2023,6 +2010,84 @@ const splitTemplateVarString = (str) => {
2023
2010
  return payload;
2024
2011
  };
2025
2012
 
2013
+ /** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
2014
+ const testPreviewFormData = useMemo(() => {
2015
+ const payload = createPayload();
2016
+ const rcs = payload?.versions?.base?.content?.RCS;
2017
+ if (!rcs) return null;
2018
+ // createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
2019
+ const accountIdForCreateMessageMeta =
2020
+ (wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
2021
+ ? String(wecrmAccountId)
2022
+ : accountId;
2023
+ const rcsForTest = {
2024
+ ...rcs,
2025
+ rcsContent: {
2026
+ ...rcs.rcsContent,
2027
+ ...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
2028
+ },
2029
+ };
2030
+ const out = {
2031
+ versions: {
2032
+ base: {
2033
+ content: {
2034
+ RCS: rcsForTest,
2035
+ },
2036
+ },
2037
+ },
2038
+ };
2039
+ const fb = smsFallbackData;
2040
+ if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
2041
+ out.templateConfigs = {
2042
+ templateId: fb.smsTemplateId || '',
2043
+ template: fb.templateContent || fb.content || '',
2044
+ traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
2045
+ registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
2046
+ };
2047
+ }
2048
+ return out;
2049
+ }, [
2050
+ templateName,
2051
+ templateTitle,
2052
+ templateDesc,
2053
+ templateMediaType,
2054
+ cardVarMapped,
2055
+ suggestions,
2056
+ rcsImageSrc,
2057
+ rcsVideoSrc,
2058
+ rcsThumbnailSrc,
2059
+ selectedDimension,
2060
+ smsFallbackData,
2061
+ isFullMode,
2062
+ isEditFlow,
2063
+ templateType,
2064
+ accountId,
2065
+ wecrmAccountId,
2066
+ accessToken,
2067
+ accountName,
2068
+ hostName,
2069
+ smsRegister,
2070
+ ]);
2071
+
2072
+ /**
2073
+ * Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
2074
+ * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
2075
+ * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
2076
+ */
2077
+ const librarySmsFallbackMergedForValidation = useMemo(() => {
2078
+ if (isFullMode) {
2079
+ return smsFallbackData;
2080
+ }
2081
+ const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
2082
+ const local =
2083
+ smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
2084
+ return {
2085
+ ...smsFromApiShape,
2086
+ ...local,
2087
+ rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
2088
+ };
2089
+ }, [isFullMode, templateData, smsFallbackData]);
2090
+
2026
2091
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2027
2092
  // eslint-disable-next-line no-undef
2028
2093
  const error = errorMessage?.message || errorMessage;
@@ -2052,6 +2117,9 @@ const splitTemplateVarString = (str) => {
2052
2117
  _id: params?.id,
2053
2118
  validity: true,
2054
2119
  type: RCS,
2120
+ // CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
2121
+ // the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
2122
+ closeSlideBoxAfterSubmit: !isFullMode,
2055
2123
  };
2056
2124
  getFormData(formDataParams);
2057
2125
  };
@@ -2065,6 +2133,7 @@ const splitTemplateVarString = (str) => {
2065
2133
  actionCallback({ resp, errorMessage });
2066
2134
  setSpin(false); // Always turn off spinner
2067
2135
  if (!errorMessage) {
2136
+ setTemplateStatus(RCS_STATUSES.pending);
2068
2137
  onCreateComplete();
2069
2138
  }
2070
2139
  });
@@ -2076,6 +2145,64 @@ const splitTemplateVarString = (str) => {
2076
2145
  }
2077
2146
  };
2078
2147
 
2148
+ /** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
2149
+ const smsFallbackBlocksDone = () => {
2150
+ // Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
2151
+ if (
2152
+ !isFullMode
2153
+ && !isTraiDLTEnable(isFullMode, smsRegister)
2154
+ && smsFallbackData == null
2155
+ && hasMeaningfulSmsFallbackShape(
2156
+ getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
2157
+ )
2158
+ ) {
2159
+ return true;
2160
+ }
2161
+ if (!smsFallbackData) return false;
2162
+ // Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
2163
+ // concern, not a structural requirement for approval — the registered SMS template body stands on
2164
+ // its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
2165
+ if (isFullMode) return false;
2166
+ const merged = librarySmsFallbackMergedForValidation;
2167
+ const templateText = pickFirstSmsFallbackTemplateString(merged);
2168
+ if (!templateText) {
2169
+ return true;
2170
+ }
2171
+ const rawVarMap =
2172
+ merged.rcsSmsFallbackVarMapped
2173
+ || merged['rcs-sms-fallback-var-mapped'];
2174
+ const varMap =
2175
+ rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
2176
+ return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
2177
+ };
2178
+
2179
+ /**
2180
+ * Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
2181
+ * semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
2182
+ * / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
2183
+ */
2184
+ const isLibraryCampaignCardVarMappingIncomplete = () => {
2185
+ if (isFullMode) return false;
2186
+ const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
2187
+ rcsVarTestRegex.test(elem),
2188
+ );
2189
+ const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
2190
+ rcsVarTestRegex.test(elem),
2191
+ );
2192
+ const orderedVarNames = [
2193
+ ...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2194
+ ...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2195
+ ];
2196
+ if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
2197
+ return true;
2198
+ }
2199
+ return orderedVarNames.some((name, globalIdx) => {
2200
+ const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
2201
+ const s = v == null ? '' : String(v);
2202
+ return s.trim() === '';
2203
+ });
2204
+ };
2205
+
2079
2206
  const isDisableDone = () => {
2080
2207
  if(isEditFlow){
2081
2208
  return false;
@@ -2086,40 +2213,16 @@ const splitTemplateVarString = (str) => {
2086
2213
  }
2087
2214
  }
2088
2215
 
2089
- if(!isFullMode){
2090
- const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2091
- const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2092
- const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2093
-
2094
- if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2095
- return true;
2096
- }
2097
-
2098
- const hasEmptyMapping =
2099
- cardVarMapped &&
2100
- Object.keys(cardVarMapped).length > 0 &&
2101
- Object.entries(cardVarMapped).some(([_, v]) => {
2102
- if (typeof v !== 'string') return !v; // null/undefined
2103
- return v.trim() === ''; // empty string
2104
- });
2105
-
2106
- if (hasEmptyMapping) {
2107
- return true;
2108
- }
2109
-
2110
- const anyMissing = allVars.some(name => {
2111
- const v = cardVarMapped?.[name];
2112
- if (typeof v !== 'string') return !v;
2113
- return v.trim() === '';
2114
- });
2115
- if (anyMissing) {
2116
- return true;
2117
- }
2216
+ if (isLibraryCampaignCardVarMappingIncomplete()) {
2217
+ return true;
2118
2218
  }
2119
2219
 
2120
- if (isMediaTypeText && templateDesc.trim() === '') {
2220
+ if (smsFallbackBlocksDone()) {
2121
2221
  return true;
2222
+ }
2122
2223
 
2224
+ if (isMediaTypeText && templateDesc.trim() === '') {
2225
+ return true;
2123
2226
  }
2124
2227
  if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2125
2228
  return true;
@@ -2137,53 +2240,36 @@ const splitTemplateVarString = (str) => {
2137
2240
  return true;
2138
2241
  }
2139
2242
  }
2140
- if (templateDescError || templateTitleError || fallbackMessageError) {
2243
+ if (templateDescError || templateTitleError) {
2244
+ return true;
2245
+ }
2246
+ if (
2247
+ smsFallbackData?.content
2248
+ && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2249
+ ) {
2141
2250
  return true;
2142
2251
  }
2143
2252
  return false;
2144
2253
  };
2145
2254
 
2146
2255
  const isEditDisableDone = () => {
2147
-
2148
2256
  if (templateStatus !== RCS_STATUSES.approved) {
2149
2257
  return true;
2150
2258
  }
2151
2259
 
2152
- if (!isFullMode) {
2153
- if (templateName.trim() === '' || templateNameError) {
2154
- return true;
2155
- }
2260
+ // if (!isFullMode) {
2261
+ // if (templateName.trim() === '' || templateNameError) {
2262
+ // return true;
2263
+ // }
2264
+ // }
2265
+ if (isLibraryCampaignCardVarMappingIncomplete()) {
2266
+ return true;
2156
2267
  }
2157
- if(!isFullMode){
2158
- const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2159
- const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2160
- const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2161
2268
 
2162
- if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2163
- return true;
2164
- }
2165
-
2166
- const hasEmptyMapping =
2167
- cardVarMapped &&
2168
- Object.keys(cardVarMapped).length > 0 &&
2169
- Object.entries(cardVarMapped).some(([_, v]) => {
2170
- if (typeof v !== 'string') return !v; // null/undefined
2171
- return v.trim() === ''; // empty string
2172
- });
2173
-
2174
- if (hasEmptyMapping) {
2175
- return true;
2176
- }
2177
-
2178
- const anyMissing = allVars.some(name => {
2179
- const v = cardVarMapped?.[name];
2180
- if (typeof v !== 'string') return !v;
2181
- return v.trim() === '';
2182
- });
2183
- if (anyMissing) {
2184
- return true;
2185
- }
2269
+ if (smsFallbackBlocksDone()) {
2270
+ return true;
2186
2271
  }
2272
+
2187
2273
  if (isMediaTypeText && templateDesc.trim() === '') {
2188
2274
  return true;
2189
2275
  }
@@ -2202,7 +2288,13 @@ const splitTemplateVarString = (str) => {
2202
2288
  return true;
2203
2289
  }
2204
2290
  }
2205
- if (templateTitleError || templateDescError || fallbackMessageError) {
2291
+ if (templateTitleError || templateDescError) {
2292
+ return true;
2293
+ }
2294
+ if (
2295
+ smsFallbackData?.content
2296
+ && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2297
+ ) {
2206
2298
  return true;
2207
2299
  }
2208
2300
  return false;
@@ -2252,52 +2344,56 @@ const splitTemplateVarString = (str) => {
2252
2344
  };
2253
2345
 
2254
2346
  const getMainContent = () => {
2255
- if (showDltContainer && !fallbackPreviewmode) {
2256
- const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2257
- const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2258
- return (
2259
- <CapSlideBox
2260
- show={showDltContainer}
2261
- header={dltHeader}
2262
- content={dltContent}
2263
- handleClose={closeDltContainerHandler}
2264
- size="size-xl"
2265
- />
2266
- );
2267
- }
2347
+ // Slideboxes are rendered outside the page-level spinner to avoid
2348
+ // stacking/blur issues during initial loads.
2349
+ if (showDltContainer) return null;
2268
2350
 
2269
2351
  return (
2270
2352
  <>
2271
- {templateStatus !== '' && (<CapRow className="template-status-container">
2272
- <CapLabel type="label2">
2273
- {formatMessage(messages.templateStatusLabel)}
2274
- </CapLabel>
2275
-
2276
- {templateStatus && (
2277
- <CapAlert
2278
- message={getTemplateStatusMessage()}
2279
- type={getTemplateStatusType(templateStatus)}
2280
- />
2281
- )}
2282
- </CapRow>
2353
+ {templateStatus !== '' && (
2354
+ <CapRow className="template-status-container">
2355
+ <CapColumn span={14}>
2356
+ <CapLabel type="label2">
2357
+ {formatMessage(messages.templateStatusLabel)}
2358
+ </CapLabel>
2359
+
2360
+ {templateStatus && (
2361
+ <CapAlert
2362
+ message={getTemplateStatusMessage()}
2363
+ type={getTemplateStatusType(templateStatus)}
2364
+ />
2365
+ )}
2366
+ </CapColumn>
2367
+ </CapRow>
2283
2368
  )}
2284
- <CapRow className="cap-rcs-creatives">
2369
+ <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2285
2370
  <CapColumn span={14}>
2286
2371
  {/* template name */}
2287
2372
  {isFullMode && (
2288
- <CapInput
2289
- id="rcs_template_name_input"
2290
- data-testid="template_name"
2291
- onChange={onTemplateNameChange}
2292
- errorMessage={templateNameError}
2293
- placeholder={formatMessage(
2294
- globalMessages.templateNamePlaceholder,
2295
- )}
2296
- value={templateName || ''}
2297
- size="default"
2298
- label={formatMessage(globalMessages.creativeNameLabel)}
2299
- disabled={(isEditFlow || !isFullMode)}
2300
- />
2373
+ isEditFlow ? (
2374
+ <div className="rcs-creative-name-readonly">
2375
+ <CapHeading type="h4">
2376
+ {formatMessage(globalMessages.creativeNameLabel)}
2377
+ </CapHeading>
2378
+ <CapHeading type="h5" className="rcs-creative-name-value">
2379
+ {templateName || '-'}
2380
+ </CapHeading>
2381
+ </div>
2382
+ ) : (
2383
+ <CapInput
2384
+ id="rcs_template_name_input"
2385
+ data-testid="template_name"
2386
+ onChange={onTemplateNameChange}
2387
+ errorMessage={templateNameError}
2388
+ placeholder={formatMessage(
2389
+ globalMessages.templateNamePlaceholder,
2390
+ )}
2391
+ value={templateName || ''}
2392
+ size="default"
2393
+ label={formatMessage(globalMessages.creativeNameLabel)}
2394
+ disabled={(isEditFlow || !isFullMode)}
2395
+ />
2396
+ )
2301
2397
  )}
2302
2398
  {renderLabel('templateTypeLabel')}
2303
2399
  <CapRadioGroup
@@ -2325,7 +2421,7 @@ const splitTemplateVarString = (str) => {
2325
2421
  </>
2326
2422
  )}
2327
2423
  {renderTextComponent()}
2328
- <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2424
+ <CapDivider className="rcs-fallback-section-divider" />
2329
2425
  {renderFallBackSmsComponent()}
2330
2426
  <div className="rcs-scroll-div" />
2331
2427
  </CapColumn>
@@ -2337,7 +2433,8 @@ const splitTemplateVarString = (str) => {
2337
2433
 
2338
2434
 
2339
2435
  <div className="rcs-footer">
2340
- {!isEditFlow && (
2436
+ {/* 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). */}
2437
+ {!isEditFlow && isFullMode && (
2341
2438
  <>
2342
2439
  <div className="button-disabled-tooltip-wrapper">
2343
2440
  <CapButton
@@ -2358,7 +2455,6 @@ const splitTemplateVarString = (str) => {
2358
2455
  className="rcs-test-preview-btn"
2359
2456
  type="secondary"
2360
2457
  disabled={true}
2361
- style={{ marginLeft: "8px" }}
2362
2458
  >
2363
2459
  <FormattedMessage {...creativesMessages.testAndPreview} />
2364
2460
  </CapButton>
@@ -2391,51 +2487,6 @@ const splitTemplateVarString = (str) => {
2391
2487
  </>
2392
2488
  )}
2393
2489
  </div>
2394
-
2395
-
2396
- {fallbackPreviewmode && (
2397
- <CapSlideBox
2398
- className="rcs-fallback-preview"
2399
- show={fallbackPreviewmode}
2400
- header={(
2401
- <CapHeading type="h7" style={{ color: CAP_G01 }}>
2402
- {formatMessage(messages.fallbackPreviewtitle)}
2403
- </CapHeading>
2404
- )}
2405
- content={(
2406
- <>
2407
- <UnifiedPreview
2408
- channel={RCS}
2409
- content={{
2410
- rcsPreviewContent: {
2411
- rcsDesc: tempMsg,
2412
- },
2413
- }}
2414
- device={ANDROID}
2415
- showDeviceToggle={false}
2416
- showHeader={false}
2417
- formatMessage={formatMessage}
2418
- />
2419
- <CapHeading
2420
- type="h3"
2421
- style={{ textAlign: 'center' }}
2422
- className="margin-t-16"
2423
- >
2424
- {formatMessage(messages.totalCharacters, {
2425
- smsCount: Math.ceil(
2426
- tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2427
- ),
2428
- number: tempMsg.length,
2429
- })}
2430
- </CapHeading>
2431
- </>
2432
- )}
2433
- handleClose={() => {
2434
- setFallbackPreviewmode(false);
2435
- setDltPreviewData('');
2436
- }}
2437
- />
2438
- )}
2439
2490
  </>
2440
2491
  );
2441
2492
  };
@@ -2444,12 +2495,38 @@ const splitTemplateVarString = (str) => {
2444
2495
  <CapSpin spinning={loadingTags || spin}>
2445
2496
  {getMainContent()}
2446
2497
  </CapSpin>
2498
+
2499
+ {showDltContainer && (() => {
2500
+ const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
2501
+ return (
2502
+ <CapSlideBox
2503
+ show={showDltContainer}
2504
+ header={dltHeader}
2505
+ content={dltContent}
2506
+ handleClose={closeDltContainerHandler}
2507
+ size="size-xl"
2508
+ />
2509
+ );
2510
+ })()}
2511
+
2447
2512
  <TestAndPreviewSlidebox
2448
2513
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
2449
2514
  onClose={handleCloseTestAndPreview}
2450
- formData={null} // RCS doesn't use formData structure like SMS
2451
- content={getTemplateContent()}
2515
+ formData={testPreviewFormData}
2516
+ content={testAndPreviewContent}
2452
2517
  currentChannel={RCS}
2518
+ orgUnitId={orgUnitId}
2519
+ smsFallbackContent={
2520
+ smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
2521
+ ? {
2522
+ templateContent:
2523
+ smsFallbackData.templateContent || smsFallbackData.content || '',
2524
+ templateName: smsFallbackData.templateName || '',
2525
+ [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
2526
+ }
2527
+ : null
2528
+ }
2529
+ smsRegister={smsRegister}
2453
2530
  />
2454
2531
  </>
2455
2532
  );