@capillarytech/creatives-library 8.0.329 → 8.0.330

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 (122) hide show
  1. package/constants/unified.js +0 -14
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -10
  6. package/utils/tests/commonUtil.test.js +0 -169
  7. package/v2Components/CapTagList/index.js +0 -10
  8. package/v2Components/CommonTestAndPreview/CustomValuesEditor.js +49 -70
  9. package/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +2 -8
  10. package/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +21 -207
  11. package/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +0 -16
  12. package/v2Components/CommonTestAndPreview/DeliverySettings/index.js +10 -85
  13. package/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +0 -30
  14. package/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +11 -79
  15. package/v2Components/CommonTestAndPreview/SendTestMessage.js +53 -87
  16. package/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +1 -20
  17. package/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +4 -133
  18. package/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +34 -145
  19. package/v2Components/CommonTestAndPreview/actions.js +0 -10
  20. package/v2Components/CommonTestAndPreview/constants.js +1 -53
  21. package/v2Components/CommonTestAndPreview/index.js +168 -1006
  22. package/v2Components/CommonTestAndPreview/messages.js +3 -147
  23. package/v2Components/CommonTestAndPreview/reducer.js +0 -10
  24. package/v2Components/CommonTestAndPreview/sagas.js +6 -15
  25. package/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +286 -328
  26. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +65 -231
  27. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +5 -118
  28. package/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +0 -341
  29. package/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +24 -65
  30. package/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +1 -199
  31. package/v2Components/CommonTestAndPreview/tests/constants.test.js +1 -31
  32. package/v2Components/CommonTestAndPreview/tests/index.test.js +4 -168
  33. package/v2Components/CommonTestAndPreview/tests/reducer.test.js +0 -71
  34. package/v2Components/CommonTestAndPreview/tests/sagas.test.js +2 -2
  35. package/v2Components/CommonTestAndPreview/tests/selectors.test.js +0 -17
  36. package/v2Components/FormBuilder/index.js +1 -7
  37. package/v2Components/TestAndPreviewSlidebox/index.js +1 -8
  38. package/v2Components/TestAndPreviewSlidebox/sagas.js +4 -11
  39. package/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +1 -3
  40. package/v2Containers/CreativesContainer/SlideBoxContent.js +4 -36
  41. package/v2Containers/CreativesContainer/SlideBoxFooter.js +1 -10
  42. package/v2Containers/CreativesContainer/SlideBoxHeader.js +4 -29
  43. package/v2Containers/CreativesContainer/constants.js +0 -9
  44. package/v2Containers/CreativesContainer/index.js +93 -286
  45. package/v2Containers/CreativesContainer/index.scss +1 -51
  46. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +34 -78
  47. package/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +16 -79
  48. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +0 -8
  49. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +98 -357
  50. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +10 -20
  51. package/v2Containers/CreativesContainer/tests/index.test.js +9 -71
  52. package/v2Containers/Rcs/constants.js +1 -34
  53. package/v2Containers/Rcs/index.js +884 -999
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +1 -10
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2453 -41456
  57. package/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +5 -0
  58. package/v2Containers/Rcs/tests/index.test.js +38 -41
  59. package/v2Containers/Rcs/tests/mockData.js +0 -38
  60. package/v2Containers/Rcs/tests/utils.test.js +1 -379
  61. package/v2Containers/Rcs/utils.js +10 -358
  62. package/v2Containers/Sms/Create/index.js +38 -100
  63. package/v2Containers/SmsTrai/Create/index.js +4 -9
  64. package/v2Containers/SmsTrai/Edit/constants.js +0 -2
  65. package/v2Containers/SmsTrai/Edit/index.js +128 -609
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -9
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2600 -4586
  68. package/v2Containers/SmsWrapper/index.js +8 -37
  69. package/v2Containers/TagList/index.js +0 -6
  70. package/v2Containers/Templates/_templates.scss +2 -63
  71. package/v2Containers/Templates/actions.js +0 -11
  72. package/v2Containers/Templates/constants.js +0 -2
  73. package/v2Containers/Templates/index.js +40 -90
  74. package/v2Containers/Templates/sagas.js +12 -57
  75. package/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1079 -1043
  76. package/v2Containers/Templates/tests/sagas.test.js +123 -193
  77. package/v2Containers/TemplatesV2/TemplatesV2.style.js +1 -72
  78. package/v2Containers/TemplatesV2/index.js +23 -86
  79. package/v2Containers/Whatsapp/index.js +20 -3
  80. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +4872 -5790
  81. package/utils/templateVarUtils.js +0 -172
  82. package/utils/tests/templateVarUtils.test.js +0 -160
  83. package/v2Components/CommonTestAndPreview/AddTestCustomer.js +0 -42
  84. package/v2Components/CommonTestAndPreview/CustomerCreationModal.js +0 -155
  85. package/v2Components/CommonTestAndPreview/ExistingCustomerModal.js +0 -93
  86. package/v2Components/CommonTestAndPreview/previewApiUtils.js +0 -59
  87. package/v2Components/CommonTestAndPreview/tests/AddTestCustomer.test.js +0 -66
  88. package/v2Components/CommonTestAndPreview/tests/CommonTestAndPreview.addTestCustomer.test.js +0 -648
  89. package/v2Components/CommonTestAndPreview/tests/CustomerCreationModal.test.js +0 -174
  90. package/v2Components/CommonTestAndPreview/tests/ExistingCustomerModal.test.js +0 -114
  91. package/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +0 -67
  92. package/v2Components/SmsFallback/SmsFallbackLocalSelector.js +0 -87
  93. package/v2Components/SmsFallback/constants.js +0 -73
  94. package/v2Components/SmsFallback/index.js +0 -955
  95. package/v2Components/SmsFallback/index.scss +0 -265
  96. package/v2Components/SmsFallback/messages.js +0 -78
  97. package/v2Components/SmsFallback/smsFallbackUtils.js +0 -107
  98. package/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +0 -50
  99. package/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +0 -147
  100. package/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +0 -304
  101. package/v2Components/SmsFallback/tests/smsFallbackUi.test.js +0 -197
  102. package/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +0 -261
  103. package/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +0 -422
  104. package/v2Components/SmsFallback/useLocalTemplateList.js +0 -92
  105. package/v2Components/VarSegmentMessageEditor/constants.js +0 -2
  106. package/v2Components/VarSegmentMessageEditor/index.js +0 -125
  107. package/v2Components/VarSegmentMessageEditor/index.scss +0 -46
  108. package/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +0 -43
  109. package/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +0 -67
  110. package/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +0 -90
  111. package/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +0 -258
  112. package/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +0 -125
  113. package/v2Containers/Rcs/rcsLibraryHydrationUtils.js +0 -205
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -251
  115. package/v2Containers/Sms/smsFormDataHelpers.js +0 -67
  116. package/v2Containers/Sms/tests/smsFormDataHelpers.test.js +0 -253
  117. package/v2Containers/SmsTrai/Edit/index.scss +0 -121
  118. package/v2Containers/Templates/TemplatesActionBar.js +0 -101
  119. package/v2Containers/Templates/tests/TemplatesActionBar.test.js +0 -120
  120. package/v2Containers/Templates/tests/smsTemplatesListApi.test.js +0 -180
  121. package/v2Containers/Templates/utils/smsTemplatesListApi.js +0 -79
  122. package/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +0 -131
@@ -1,12 +1,13 @@
1
1
  /* eslint-disable no-unused-expressions */
2
- import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
2
+ import React, { useState, useEffect, useCallback } from 'react';
3
3
  import { bindActionCreators } from 'redux';
4
4
  import { createStructuredSelector } from 'reselect';
5
- import { FormattedMessage } from 'react-intl';
5
+ import { injectIntl, FormattedMessage } from 'react-intl';
6
6
  import get from 'lodash/get';
7
7
  import isEmpty from 'lodash/isEmpty';
8
8
  import cloneDeep from 'lodash/cloneDeep';
9
9
  import isNil from 'lodash/isNil';
10
+ import styled from 'styled-components';
10
11
  import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
11
12
  import CapRow from '@capillarytech/cap-ui-library/CapRow';
12
13
  import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
@@ -21,16 +22,35 @@ import CapHeader from '@capillarytech/cap-ui-library/CapHeader';
21
22
  import CapDivider from '@capillarytech/cap-ui-library/CapDivider';
22
23
  import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
23
24
  import CapImage from '@capillarytech/cap-ui-library/CapImage';
25
+ import CapCard from '@capillarytech/cap-ui-library/CapCard';
24
26
  import CapSlideBox from '@capillarytech/cap-ui-library/CapSlideBox';
25
27
  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';
26
31
  import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
32
+ import CapTooltipWithInfo from '@capillarytech/cap-ui-library/CapTooltipWithInfo';
27
33
  import CapError from '@capillarytech/cap-ui-library/CapError';
34
+ import CapCheckbox from '@capillarytech/cap-ui-library/CapCheckbox';
28
35
  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';
29
48
 
30
49
  import CapVideoUpload from '../../v2Components/CapVideoUpload';
31
50
  import * as globalActions from '../Cap/actions';
32
51
  import CapActionButton from '../../v2Components/CapActionButton';
33
52
  import { makeSelectRcs, makeSelectAccount } from './selectors';
53
+ import { DATE_DISPLAY_FORMAT, TIME_DISPLAY_FORMAT } from '../App/constants';
34
54
  import {
35
55
  isLoadingMetaEntities,
36
56
  makeSelectMetaEntities,
@@ -40,15 +60,6 @@ import * as RcsActions from './actions';
40
60
  import { isAiContentBotDisabled } from '../../utils/common';
41
61
  import * as TemplatesActions from '../Templates/actions';
42
62
  import './index.scss';
43
- import {
44
- normalizeLibraryLoadedTitleDesc,
45
- mergeRcsSmsFallBackContentFromDetails,
46
- mergeRcsSmsFallbackVarMapLayers,
47
- pickFirstSmsFallbackTemplateString,
48
- syncCardVarMappedSemanticsFromSlots,
49
- hasMeaningfulSmsFallbackShape,
50
- getLibrarySmsFallbackApiBaselineFromTemplateData,
51
- } from './rcsLibraryHydrationUtils';
52
63
  import {
53
64
  RCS,
54
65
  SMS,
@@ -70,6 +81,8 @@ import {
70
81
  MESSAGE_TEXT,
71
82
  ALLOWED_EXTENSIONS_VIDEO_REGEX,
72
83
  RCS_VIDEO_SIZE,
84
+ TEMPLATE_HEADER_MAX_LENGTH,
85
+ TEMPLATE_MESSAGE_MAX_LENGTH,
73
86
  RCS_THUMBNAIL_MIN_SIZE,
74
87
  RCS_THUMBNAIL_MAX_SIZE,
75
88
  contentType,
@@ -82,46 +95,35 @@ import {
82
95
  MAX_BUTTONS,
83
96
  INITIAL_SUGGESTIONS_DATA_STOP,
84
97
  RCS_BUTTON_TYPES,
98
+ titletype,
99
+ descType,
85
100
  STANDALONE,
86
101
  VERTICAL,
87
102
  SMALL,
88
103
  MEDIUM,
89
104
  RICHCARD,
90
- RCS_NUMERIC_VAR_NAME_REGEX,
91
- RCS_NUMERIC_VAR_TOKEN_REGEX,
92
- RCS_TAG_AREA_FIELD_TITLE,
93
- RCS_TAG_AREA_FIELD_DESC,
94
105
  } from './constants';
95
106
  import globalMessages from '../Cap/messages';
96
107
  import messages from './messages';
97
108
  import creativesMessages from '../CreativesContainer/messages';
98
109
  import withCreatives from '../../hoc/withCreatives';
99
110
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
100
- import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
101
- import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
111
+ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
102
112
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
103
- import { splitTemplateVarString } from '../../utils/templateVarUtils';
104
113
  import CapImageUpload from '../../v2Components/CapImageUpload';
114
+ import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
105
115
  import Templates from '../Templates';
106
116
  import SmsTraiEdit from '../SmsTrai/Edit';
107
- import SmsFallback from '../../v2Components/SmsFallback';
108
- import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
109
117
  import TagList from '../TagList';
110
118
  import { validateTags } from '../../utils/tagValidations';
111
- import { isTraiDLTEnable } from '../../utils/common';
119
+ import { getCdnUrl } from '../../utils/cdnTransformation';
112
120
  import { isTagIncluded } from '../../utils/commonUtils';
113
121
  import injectReducer from '../../utils/injectReducer';
114
122
  import v2RcsReducer from './reducer';
115
- import {
116
- areAllRcsSmsFallbackVarSlotsFilled,
117
- buildRcsNumericMustachePlaceholderRegex,
118
- getTemplateStatusType,
119
- normalizeCardVarMapped,
120
- coalesceCardVarMappedToTemplate,
121
- resolveCardVarMappedSlotValue,
122
- sanitizeCardVarMappedValue,
123
- } from './utils';
123
+ import { getTemplateStatusType } from './utils';
124
124
 
125
+
126
+ const { Group: CapCheckboxGroup } = CapCheckbox;
125
127
  export const Rcs = (props) => {
126
128
  const {
127
129
  intl,
@@ -135,14 +137,15 @@ export const Rcs = (props) => {
135
137
  templatesActions,
136
138
  globalActions,
137
139
  location,
140
+ handleClose,
138
141
  getDefaultTags,
139
142
  supportedTags,
140
143
  metaEntities,
141
144
  injectedTags,
142
145
  loadingTags,
143
146
  getFormData,
147
+ isDltEnabled,
144
148
  smsRegister,
145
- orgUnitId,
146
149
  selectedOfferDetails,
147
150
  eventContextTags,
148
151
  accountData = {},
@@ -153,8 +156,8 @@ export const Rcs = (props) => {
153
156
  } = props || {};
154
157
  const { formatMessage } = intl;
155
158
  const { TextArea } = CapInput;
159
+ const { CapCustomCardList } = CapCustomCard;
156
160
  const [isEditFlow, setEditFlow] = useState(false);
157
- const isEditLike = isEditFlow || !isFullMode;
158
161
  const [tags, updateTags] = useState([]);
159
162
  const [spin, setSpin] = useState(false);
160
163
  //template
@@ -163,71 +166,112 @@ export const Rcs = (props) => {
163
166
  const [templateMediaType, setTemplateMediaType] = useState(
164
167
  RCS_MEDIA_TYPES.NONE,
165
168
  );
169
+ const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
166
170
  const [templateTitle, setTemplateTitle] = useState('');
167
171
  const [templateDesc, setTemplateDesc] = useState('');
168
172
  const [templateDescError, setTemplateDescError] = useState(false);
169
173
  const [templateStatus, setTemplateStatus] = useState('');
174
+ const [templateDate, setTemplateDate] = useState('');
175
+ //fallback
176
+ const [fallbackMessage, setFallbackMessage] = useState('');
177
+ const [fallbackMessageError, setFallbackMessageError] = useState(false);
170
178
  //fallback dlt
171
179
  const [showDltContainer, setShowDltContainer] = useState(false);
172
180
  const [dltMode, setDltMode] = useState('');
173
181
  const [dltEditData, setDltEditData] = useState({});
174
- /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
175
- const [smsFallbackData, setSmsFallbackData] = useState(undefined);
182
+ const [showDltCard, setShowDltCard] = useState(false);
183
+ const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
184
+ const [dltPreviewData, setDltPreviewData] = useState('');
176
185
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
177
- const buttonType = RCS_BUTTON_TYPES.NONE;
186
+ const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
178
187
  const [suggestionError, setSuggestionError] = useState(true);
179
188
  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([]);
180
196
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
181
197
  const [descVarMappedData, setDescVarMappedData] = useState({});
182
198
  const [titleTextAreaId, setTitleTextAreaId] = useState();
183
199
  const [descTextAreaId, setDescTextAreaId] = useState();
184
200
  const [assetList, setAssetList] = useState({});
201
+ const [assetListImage, setAssetListImage] = useState('');
185
202
  const [rcsImageSrc, updateRcsImageSrc] = useState('');
186
203
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
187
204
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
188
205
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
206
+ const [imageError, setImageError] = useState(null);
189
207
  const [templateTitleError, setTemplateTitleError] = useState(false);
190
208
  const [cardVarMapped, setCardVarMapped] = useState({});
191
- /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
192
- const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
193
- const lastHydratedRcsCardVarSignatureRef = useRef(null);
194
-
195
- /**
196
- * Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
197
- * without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
198
- * fallback card / content stopped appearing until re-selected.
199
- */
200
- const rcsHydrationDetails = useMemo(
201
- () => (isFullMode ? rcsData?.templateDetails : templateData),
202
- [isFullMode, rcsData?.templateDetails, templateData],
203
- );
204
-
205
- /** 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'). */
206
- const lastTagSchemaQueryKeyRef = useRef(null);
207
- /**
208
- * Library: parent often passes a new `templateData` object reference every render. Re-applying the same
209
- * SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
210
- */
211
- const lastSmsFallbackHydrationKeyRef = useRef(null);
212
-
213
- const fetchTagSchemaIfNewQuery = useCallback(
214
- (query) => {
215
- const key = JSON.stringify(query);
216
- if (lastTagSchemaQueryKeyRef.current === key) {
217
- return;
218
- }
219
- lastTagSchemaQueryKeyRef.current = key;
220
- globalActions.fetchSchemaForEntity(query);
221
- },
222
- [globalActions],
223
- );
224
209
 
225
210
  // TestAndPreviewSlidebox state
226
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;
241
+ }
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
+ };
258
+
259
+ return contentObj;
260
+ }, [
261
+ templateMediaType,
262
+ templateTitle,
263
+ templateDesc,
264
+ rcsImageSrc,
265
+ rcsVideoSrc,
266
+ rcsThumbnailSrc,
267
+ suggestions,
268
+ selectedDimension,
269
+ ]);
227
270
 
228
271
  // Handle Test and Preview button click
229
272
  const handleTestAndPreview = useCallback(() => {
230
273
  setShowTestAndPreviewSlidebox(true);
274
+ setIsTestAndPreviewMode(true);
231
275
  if (propsHandleTestAndPreview) {
232
276
  propsHandleTestAndPreview();
233
277
  }
@@ -236,28 +280,23 @@ export const Rcs = (props) => {
236
280
  // Handle close Test and Preview slidebox
237
281
  const handleCloseTestAndPreview = useCallback(() => {
238
282
  setShowTestAndPreviewSlidebox(false);
283
+ setIsTestAndPreviewMode(false);
239
284
  if (propsHandleCloseTestAndPreview) {
240
285
  propsHandleCloseTestAndPreview();
241
286
  }
242
287
  }, [propsHandleCloseTestAndPreview]);
243
288
 
244
- /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
245
- const handleSmsFallbackEditorStateChange = useCallback((patch) => {
246
- setSmsFallbackData((prev) => {
247
- if (!patch || typeof patch !== 'object') return prev;
248
- // Bail out when no template has been selected yet — SmsTraiEdit fires
249
- // onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
250
- // which would create a non-null smsFallbackData from nothing and cause
251
- // the card to appear as "Untitled creative" before any save.
252
- if (!prev) return prev;
253
- return { ...prev, ...patch };
254
- });
255
- }, []);
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
+ };
256
298
 
257
- /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
258
299
  const [accountId, setAccountId] = useState('');
259
- /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
260
- const [wecrmAccountId, setWecrmAccountId] = useState('');
261
300
  const [accessToken, setAccessToken] = useState('');
262
301
  const [hostName, setHostName] = useState('');
263
302
  const [accountName, setAccountName] = useState('');
@@ -265,23 +304,14 @@ export const Rcs = (props) => {
265
304
  const accountObj = accountData.selectedRcsAccount || {};
266
305
  if (!isEmpty(accountObj)) {
267
306
  const {
268
- id: wecrmId,
269
307
  sourceAccountIdentifier = '',
270
308
  configs = {},
271
309
  } = accountObj;
310
+
272
311
  setAccountId(sourceAccountIdentifier);
273
- setWecrmAccountId(
274
- wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
275
- );
276
312
  setAccessToken(configs.accessToken || '');
277
313
  setHostName(accountObj.hostName || '');
278
314
  setAccountName(accountObj.name || '');
279
- } else {
280
- setAccountId('');
281
- setWecrmAccountId('');
282
- setAccessToken('');
283
- setHostName('');
284
- setAccountName('');
285
315
  }
286
316
  }, [accountData.selectedRcsAccount]);
287
317
 
@@ -337,9 +367,7 @@ export const Rcs = (props) => {
337
367
  if (isFullMode) return;
338
368
  if (loadingTags || !tags || tags.length === 0) return;
339
369
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
340
- const slotOffset =
341
- type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
342
- const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
370
+ const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
343
371
  if (!resolved) {
344
372
  if (type === TITLE_TEXT) setTemplateTitleError(false);
345
373
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -384,41 +412,13 @@ export const Rcs = (props) => {
384
412
 
385
413
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
386
414
 
387
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
388
-
389
- /** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
390
- const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
391
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
392
- const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
393
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
394
- let varOrdinal = 0;
395
- for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
396
- const segmentToken = templateSegments[segmentIndexInField];
397
- if (rcsVarTestRegex.test(segmentToken)) {
398
- if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
399
- return offset + varOrdinal;
400
- }
401
- varOrdinal += 1;
402
- }
403
- }
404
- return null;
405
- };
406
-
407
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
415
+ const resolveTemplateWithMap = (str = '') => {
408
416
  if (!str) return '';
409
- const arr = splitTemplateVarStringRcs(str);
410
- let varOrdinal = 0;
417
+ const arr = splitTemplateVarString(str);
411
418
  return arr.map((elem) => {
412
419
  if (rcsVarTestRegex.test(elem)) {
413
420
  const key = getVarNameFromToken(elem);
414
- const globalSlot = slotOffset + varOrdinal;
415
- varOrdinal += 1;
416
- const v = resolveCardVarMappedSlotValue(
417
- cardVarMapped,
418
- key,
419
- globalSlot,
420
- isEditLike,
421
- );
421
+ const v = cardVarMapped?.[key];
422
422
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
423
423
  return String(v);
424
424
  }
@@ -426,154 +426,107 @@ export const Rcs = (props) => {
426
426
  }).join('');
427
427
  };
428
428
 
429
- /**
430
- * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
431
- * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
432
- * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
433
- */
434
- const getTemplateContent = useCallback(() => {
435
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
436
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
437
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
438
-
439
- const isSlotMappingMode = isEditFlow || !isFullMode;
440
- const titleVarCountForResolve = isMediaTypeText
441
- ? 0
442
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
443
- const resolvedTitle = isMediaTypeText
444
- ? ''
445
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
446
- const resolvedDesc = isSlotMappingMode
447
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
448
- : templateDesc;
449
-
450
- const mediaPreview = {};
451
- if (isMediaTypeImage && rcsImageSrc) {
452
- mediaPreview.rcsImageSrc = rcsImageSrc;
453
- }
454
- if (isMediaTypeVideo && !isMediaTypeText) {
455
- if (rcsThumbnailSrc) {
456
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
457
- } else if (rcsVideoSrc?.videoSrc) {
458
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
459
- }
460
- if (rcsThumbnailSrc) {
461
- mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
462
- }
463
- }
464
-
465
- const contentObj = {
466
- templateHeader: resolvedTitle,
467
- templateMessage: resolvedDesc,
468
- ...mediaPreview,
469
- ...(suggestions.length > 0 && {
470
- suggestions: suggestions,
471
- }),
472
- };
473
429
 
474
- return contentObj;
475
- }, [
476
- templateMediaType,
477
- templateTitle,
478
- templateDesc,
479
- rcsImageSrc,
480
- rcsVideoSrc,
481
- rcsThumbnailSrc,
482
- suggestions,
483
- selectedDimension,
484
- isFullMode,
485
- isEditFlow,
486
- cardVarMapped,
487
- ]);
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]);
488
450
 
489
- const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
490
451
 
452
+ const RcsLabel = styled.div`
453
+ display: flex;
454
+ margin-top: 20px;
455
+ `;
491
456
  const paramObj = params || {};
492
457
  useEffect(() => {
493
- const { id } = paramObj;
494
- if (id && isFullMode) {
495
- setSpin(true);
496
- actions.getTemplateDetails(id, setSpin);
497
- }
498
- return () => {
499
- actions.clearEditResponse();
500
- };
501
- }, [paramObj.id, isFullMode]);
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]);
502
467
 
503
468
  useEffect(() => {
504
- if (!(isEditFlow || !isFullMode)) return;
505
-
506
- const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
507
-
508
- const initField = (targetString, setVarMap, slotOffset) => {
509
- const arr = splitTemplateVarStringRcs(targetString);
510
- if (!arr?.length) {
511
- setVarMap({});
512
- return;
513
- }
514
- const nextVarMap = {};
515
- let varOrdinal = 0;
516
- arr.forEach((elem, idx) => {
517
- // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
518
- if (rcsVarTestRegex.test(elem)) {
519
- const id = `${elem}_${idx}`;
520
- const varName = getVarNameFromToken(elem);
521
- const globalSlot = slotOffset + varOrdinal;
522
- varOrdinal += 1;
523
- const mappedValue = resolveCardVarMappedSlotValue(
524
- cardVarMapped,
525
- varName,
526
- globalSlot,
527
- isEditLike,
528
- );
529
- nextVarMap[id] = mappedValue;
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;
530
477
  }
531
- });
532
- setVarMap(nextVarMap);
533
- };
534
-
535
- initField(templateTitle, setTitleVarMappedData, 0);
536
- initField(templateDesc, setDescVarMappedData, titleTokenCount);
537
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
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
+ };
538
497
 
539
- useEffect(() => {
540
- if (!isEditFlow && isFullMode) {
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){
541
504
  setRcsVideoSrc({});
542
505
  updateRcsImageSrc('');
543
506
  setUpdateRcsImageSrc('');
544
507
  updateRcsThumbnailSrc('');
545
508
  setAssetList({});
546
- }
547
- }, [templateMediaType]);
509
+ }
510
+ }, [templateMediaType]);
548
511
 
549
- /** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
550
- const templateStatusHelper = (cardContentFirst) => {
551
- const raw =
552
- cardContentFirst?.Status
553
- ?? cardContentFirst?.status
554
- ?? cardContentFirst?.approvalStatus
555
- ?? '';
556
- const status = typeof raw === 'string' ? raw.trim() : String(raw);
557
- const n = status.toLowerCase();
558
- switch (n) {
559
- case 'approved':
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:
560
516
  setTemplateStatus(RCS_STATUSES.approved);
561
517
  break;
562
- case 'pending':
518
+ case RCS_STATUSES.pending:
563
519
  setTemplateStatus(RCS_STATUSES.pending);
564
520
  break;
565
- case 'awaitingapproval':
521
+ case RCS_STATUSES.awaitingApproval:
566
522
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
567
523
  break;
568
- case 'unavailable':
524
+ case RCS_STATUSES.unavailable:
569
525
  setTemplateStatus(RCS_STATUSES.unavailable);
570
526
  break;
571
- case 'rejected':
527
+ case RCS_STATUSES.rejected:
572
528
  setTemplateStatus(RCS_STATUSES.rejected);
573
529
  break;
574
- case 'created':
575
- setTemplateStatus(RCS_STATUSES.created);
576
- break;
577
530
  default:
578
531
  setTemplateStatus(status);
579
532
  break;
@@ -584,6 +537,7 @@ export const Rcs = (props) => {
584
537
  if (mediaType) {
585
538
  setTemplateMediaType(mediaType);
586
539
  }
540
+ const tempOrientation = cardSettings.cardOrientation;
587
541
  const tempAlignment = cardSettings.mediaAlignment;
588
542
  const tempHeight = mediaData.height;
589
543
 
@@ -626,197 +580,44 @@ export const Rcs = (props) => {
626
580
  };
627
581
 
628
582
  useEffect(() => {
629
- const details = rcsHydrationDetails;
583
+ const details = isFullMode ? rcsData?.templateDetails : templateData;
630
584
  if (details && Object.keys(details).length > 0) {
631
- // Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
632
- const cardFromVersions = get(
633
- details,
634
- 'versions.base.content.RCS.rcsContent.cardContent[0]',
635
- );
636
- const cardFromTop = get(details, 'rcsContent.cardContent[0]');
637
- const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
638
- const cardVarMappedFromCardContent =
639
- card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
640
- ? card0.cardVarMapped
641
- : {};
642
- const cardVarMappedFromRootMirror =
643
- details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
644
- ? details.rcsCardVarMapped
645
- : {};
646
- // Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
647
- // nested versions.cardContent[0].cardVarMapped is dropped on reload.
648
- const mergedCardVarMappedFromPayload = {
649
- ...cardVarMappedFromRootMirror,
650
- ...cardVarMappedFromCardContent,
651
- };
652
- const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
653
- const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
654
- const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
655
- Object.keys(mergedCardVarMappedFromPayload)
656
- .sort()
657
- .reduce((accumulator, mapKey) => {
658
- accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
659
- return accumulator;
660
- }, {}),
661
- )}`;
662
- if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
663
- lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
664
- setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
585
+ if (!isFullMode) {
586
+ const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
587
+ setCardVarMapped(tempCardVarMapped);
665
588
  }
666
- const tokenListForMap = [
667
- ...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
668
- ...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
669
- ];
670
- const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
671
- // Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
672
- // getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
673
- const cardVarMappedBeforeCoalesce = isFullMode
674
- ? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
675
- : { ...mergedCardVarMappedFromPayload };
676
- const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
677
- cardVarMappedBeforeCoalesce,
678
- loadedTitleForMap,
679
- loadedDescForMap,
680
- rcsVarRegex,
681
- );
682
- const cardVarMappedAfterNumericSlotSync = !isFullMode
683
- ? syncCardVarMappedSemanticsFromSlots(
684
- cardVarMappedAfterCoalesce,
685
- loadedTitleForMap,
686
- loadedDescForMap,
687
- rcsVarRegex,
688
- )
689
- : cardVarMappedAfterCoalesce;
690
- const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
691
- // Pre-populate variable/tag mappings while opening an existing template in edit flows
692
- setCardVarMapped((previousVarMapState) => {
693
- const previousVarMap = previousVarMapState ?? {};
694
- if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
695
- const previousVarMapKeys = Object.keys(previousVarMap);
696
- const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
697
- if (previousVarMapKeys.length === nextVarMapKeys.length) {
698
- const allSlotValuesMatchPrevious = previousVarMapKeys.every(
699
- (key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
700
- );
701
- if (allSlotValuesMatchPrevious) return previousVarMapState;
702
- }
703
- return hydratedCardVarMappedResult;
704
- });
705
- const mediaType =
706
- card0.mediaType
707
- || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
589
+ const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
708
590
  if (mediaType === RCS_MEDIA_TYPES.NONE) {
709
591
  setTemplateType(contentType.text_message);
710
592
  } else {
711
593
  setTemplateType(contentType.rich_card);
712
594
  }
713
595
  setEditFlow(true);
714
- setTemplateName(details?.name || details?.creativeName || '');
715
- const loadedTitle = loadedTitleForMap;
716
- const loadedDesc = loadedDescForMap;
717
- const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
718
- loadedTitle,
719
- loadedDesc,
720
- isFullMode,
721
- cardVarMappedAfterHydration: hydratedCardVarMappedResult,
722
- rcsVarRegex,
723
- });
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;
724
602
  setTemplateTitle(normalizedTitle);
725
603
  setTemplateDesc(normalizedDesc);
726
- setSuggestions(
727
- Array.isArray(card0.suggestions)
728
- ? card0.suggestions
729
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
730
- );
731
- const cardForStatus = {
732
- ...card0,
733
- Status:
734
- card0.Status
735
- ?? card0.status
736
- ?? card0.approvalStatus
737
- ?? get(details, 'templateStatus')
738
- ?? get(details, 'approvalStatus')
739
- ?? get(details, 'creativeStatus')
740
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
741
- ?? '',
742
- };
743
- templateStatusHelper(cardForStatus);
744
- const mediaData =
745
- card0.media != null && card0.media !== ''
746
- ? card0.media
747
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
748
- const cardSettings =
749
- get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
750
- || get(details, 'rcsContent.cardSettings', '');
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', '');
751
608
  setMediaData(mediaData, mediaType, cardSettings);
752
-
753
- const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
754
- const base = get(smsFallbackContent, 'versions.base', {});
755
- const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
756
- const smsEditor = base['sms-editor'];
757
- const fromNested = Array.isArray(updatedEditor)
758
- ? updatedEditor.join('')
759
- : (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
760
- const fallbackMessage = smsFallbackContent.smsContent
761
- || smsFallbackContent.smsTemplateContent
762
- || smsFallbackContent.message
763
- || fromNested
764
- || '';
765
- const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
766
- const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
767
- const hasFallbackPayload =
768
- smsFallbackContent
769
- && Object.keys(smsFallbackContent).length > 0
770
- && (
771
- !!smsFallbackContent.smsTemplateName
772
- || !!fallbackMessage
773
- || hasVarMapped
774
- );
775
- if (hasFallbackPayload) {
776
- if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
777
- console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
778
- }
779
- const unicodeFromApi =
780
- typeof smsFallbackContent.unicodeValidity === 'boolean'
781
- ? smsFallbackContent.unicodeValidity
782
- : (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
783
- const nextSmsState = {
784
- templateName: smsFallbackContent.smsTemplateName || '',
785
- content: fallbackMessage,
786
- templateContent: fallbackMessage,
787
- unicodeValidity: unicodeFromApi,
788
- ...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
789
- };
790
- const hydrationKey = JSON.stringify({
791
- creativeKey: details._id || details.name || details.creativeName || '',
792
- templateName: nextSmsState.templateName,
793
- content: nextSmsState.content,
794
- unicodeValidity: nextSmsState.unicodeValidity,
795
- varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
796
- });
797
- if (
798
- isFullMode
799
- || lastSmsFallbackHydrationKeyRef.current !== hydrationKey
800
- ) {
801
- lastSmsFallbackHydrationKeyRef.current = hydrationKey;
802
- setSmsFallbackData(nextSmsState);
803
- }
804
- } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
805
- lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
806
- setSmsFallbackData(null);
807
- }
808
609
  }
809
- }, [rcsHydrationDetails, isFullMode]);
610
+ }, [rcsData, templateData, isFullMode, isEditFlow]);
611
+
810
612
 
811
613
  useEffect(() => {
812
614
  if (templateType === contentType.text_message) {
813
615
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
814
- // Full-mode create only: switching to plain text clears draft title/media. Never clear when
815
- // hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
616
+ setTemplateTitle('');
617
+ setTemplateTitleError('');
816
618
  if (!isEditFlow && isFullMode) {
817
- setTemplateTitle('');
818
- setTemplateTitleError('');
819
619
  setUpdateRcsImageSrc('');
620
+ setUpdateRcsVideoSrc({});
820
621
  setRcsVideoSrc({});
821
622
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
822
623
  }
@@ -843,8 +644,7 @@ export const Rcs = (props) => {
843
644
  if (!showDltContainer) {
844
645
  const { type, module } = location.query || {};
845
646
  const isEmbedded = type === EMBEDDED;
846
- // Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
847
- const context = isEmbedded ? module : 'outbound';
647
+ const context = isEmbedded ? module : DEFAULT;
848
648
  const embedded = isEmbedded ? type : FULL;
849
649
  const query = {
850
650
  layout: SMS,
@@ -855,9 +655,9 @@ export const Rcs = (props) => {
855
655
  if (getDefaultTags) {
856
656
  query.context = getDefaultTags;
857
657
  }
858
- fetchTagSchemaIfNewQuery(query);
658
+ globalActions.fetchSchemaForEntity(query);
859
659
  }
860
- }, [showDltContainer, fetchTagSchemaIfNewQuery]);
660
+ }, [showDltContainer]);
861
661
 
862
662
  useEffect(() => {
863
663
  let tag = get(metaEntities, `tags.standard`, []);
@@ -880,78 +680,39 @@ export const Rcs = (props) => {
880
680
  context,
881
681
  embedded,
882
682
  };
883
- if (getDefaultTags) {
884
- query.context = getDefaultTags;
885
- }
886
- fetchTagSchemaIfNewQuery(query);
683
+ globalActions.fetchSchemaForEntity(query);
887
684
  };
888
685
 
889
- const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
890
- if (!templateStr || !numericVarName || !tagName) return templateStr;
891
- const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
892
- return templateStr.replace(re, `{{${tagName}}}`);
893
- };
894
-
895
- const onTagSelect = (data, areaId, field) => {
686
+ const onTagSelect = (data, areaId) => {
896
687
  if (!areaId) return;
897
688
  const sep = areaId.lastIndexOf('_');
898
689
  if (sep === -1) return;
899
- const slotSuffix = areaId.slice(sep + 1);
900
- if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
690
+ const numId = Number(areaId.slice(sep + 1));
691
+ if (isNaN(numId)) return;
901
692
  const token = areaId.slice(0, sep);
902
693
  const variableName = getVarNameFromToken(token);
903
694
  if (!variableName) return;
904
- const isNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(variableName));
905
- const fieldStr = field === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
906
- const fieldType = field === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
907
- const globalSlotForArea = getGlobalSlotIndexForRcsFieldId(areaId, fieldStr, fieldType);
908
-
909
695
  setCardVarMapped((prev) => {
910
696
  const base = (prev?.[variableName] ?? '').toString();
911
697
  const nextVal = `${base}{{${data}}}`;
912
- const next = { ...(prev || {}) };
913
- if (isNumericSlot) {
914
- delete next[variableName];
915
- next[data] = nextVal;
916
- } else {
917
- // Use the global slot index key only — writing by semantic name (variableName)
918
- // would contaminate any other field that shares the same var name (e.g. {{gt}}
919
- // in both title and description), causing the label value to bleed across fields.
920
- if (globalSlotForArea !== null && globalSlotForArea !== undefined) {
921
- next[String(globalSlotForArea + 1)] = nextVal;
922
- // Same reasoning as handleRcsVarChange: delete the semantic key so
923
- // resolveCardVarMappedSlotValue uses only the numeric slot and the
924
- // value cannot bleed across fields that share the same var name.
925
- delete next[variableName];
926
- } else {
927
- next[variableName] = nextVal;
928
- }
929
- }
930
- return next;
698
+ return {
699
+ ...(prev || {}),
700
+ [variableName]: nextVal,
701
+ };
931
702
  });
932
-
933
- if (isNumericSlot && (field === RCS_TAG_AREA_FIELD_TITLE || field === RCS_TAG_AREA_FIELD_DESC)) {
934
- if (field === RCS_TAG_AREA_FIELD_TITLE) {
935
- setTemplateTitle((prev) => {
936
- const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
937
- if (nextStr === prev) return prev;
938
- setTemplateTitleError(variableErrorHandling(nextStr));
939
- return nextStr;
940
- });
941
- } else {
942
- setTemplateDesc((prev) => {
943
- const nextStr = replaceNumericPlaceholderWithTagInTemplate(prev || '', variableName, data);
944
- if (nextStr === prev) return prev;
945
- setTemplateDescError(variableErrorHandling(nextStr));
946
- return nextStr;
947
- });
948
- }
949
- }
950
703
  };
951
704
 
952
- const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
705
+ const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
706
+
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);
714
+ };
953
715
 
954
- const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
955
716
 
956
717
  //removing optout tag for rcs
957
718
  const getRcsTags = () => {
@@ -964,14 +725,15 @@ export const Rcs = (props) => {
964
725
  };
965
726
  // tag Code end
966
727
 
967
- const renderLabel = (value, desc) => {
728
+ const renderLabel = (value, showLabel, desc) => {
729
+ const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
968
730
  return (
969
731
  <>
970
- <div className="rcs-form-section-heading">
732
+ <RcsLabel>
971
733
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
972
- </div>
734
+ </RcsLabel>
973
735
  {desc && (
974
- <CapLabel type="label3" className="rcs-form-field-caption">
736
+ <CapLabel type="label3" style={{ marginBottom: '17px' }}>
975
737
  {formatMessage(messages[desc])}
976
738
  </CapLabel>
977
739
  )}
@@ -1057,6 +819,47 @@ export const Rcs = (props) => {
1057
819
  setTemplateDescError(error);
1058
820
  };
1059
821
 
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
+
1060
863
  // Check for forbidden characters: square brackets [] and single curly braces {}
1061
864
  const forbiddenCharactersValidation = (value) => {
1062
865
  if (!value) return false;
@@ -1107,36 +910,47 @@ export const Rcs = (props) => {
1107
910
  }
1108
911
  return false;
1109
912
  };
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
+
1110
940
 
1111
941
  const onMessageAddVar = () => {
1112
- onAddVar(templateDesc);
942
+ onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
1113
943
  };
1114
944
 
1115
- /**
1116
- * Returns the smallest positive integer not already used as a `{{N}}` variable
1117
- * in either the title or description fields, or null if the limit (19) is reached.
1118
- * Scans both fields so title and description vars never share the same number
1119
- * (duplicate numbers would share a cardVarMapped key and bleed values across fields).
1120
- */
1121
- const getNextRcsNumericVarNumber = (titleStr, descStr) => {
1122
- const allExistingVars = [
1123
- ...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
1124
- ...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
1125
- ];
1126
- const existingNumbers = allExistingVars.flatMap(v => {
1127
- const m = v.match(/\d+/);
1128
- return m ? [parseInt(m[0], 10)] : [];
1129
- });
945
+ const onAddVar = (type, messageContent, regex) => {
946
+ // Always append the next variable at the end, like WhatsApp
947
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
948
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
1130
949
  let nextNumber = 1;
1131
950
  while (existingNumbers.includes(nextNumber)) {
1132
951
  nextNumber++;
1133
952
  }
1134
- return nextNumber > 19 ? null : nextNumber;
1135
- };
1136
-
1137
- const onAddVar = (messageContent) => {
1138
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
1139
- if (nextNumber === null) {
953
+ if (nextNumber > 19) {
1140
954
  return;
1141
955
  }
1142
956
  const nextVar = `{{${nextNumber}}}`;
@@ -1148,13 +962,15 @@ const onAddVar = (messageContent) => {
1148
962
  };
1149
963
 
1150
964
  const onTitleAddVar = () => {
1151
- // Scan both title AND description so the new title var number doesn't
1152
- // duplicate a number already used in the description. Duplicate numeric
1153
- // names would share the same cardVarMapped semantic key, causing the
1154
- // description slot to reflect the title slot value and vice-versa.
965
+ // Always append the next variable at the end, like WhatsApp
1155
966
  const messageContent = templateTitle;
1156
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
1157
- if (nextNumber === null) {
967
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
968
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
969
+ let nextNumber = 1;
970
+ while (existingNumbers.includes(nextNumber)) {
971
+ nextNumber++;
972
+ }
973
+ if (nextNumber > 19) {
1158
974
  return;
1159
975
  }
1160
976
  const nextVar = `{{${nextNumber}}}`;
@@ -1166,6 +982,66 @@ const onTitleAddVar = () => {
1166
982
  setTemplateTitleError(error);
1167
983
  };
1168
984
 
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
+
1169
1045
  const renderButtonComponent = () => {
1170
1046
  return (
1171
1047
  <>
@@ -1196,69 +1072,39 @@ const onTitleAddVar = () => {
1196
1072
  );
1197
1073
  };
1198
1074
 
1199
- const getRcsValueMap = (fieldTemplateString, fieldType) => {
1200
- if (!fieldTemplateString) return {};
1201
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
1202
- const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
1203
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
1204
- const segmentIdToResolvedValue = {};
1205
- let varOrdinal = 0;
1206
- templateSegments.forEach((segmentToken, segmentIndexInField) => {
1207
- if (rcsVarTestRegex.test(segmentToken)) {
1208
- const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
1209
- const varName = getVarNameFromToken(segmentToken);
1210
- const globalSlot = slotOffset + varOrdinal;
1211
- varOrdinal += 1;
1212
- segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
1213
- cardVarMapped,
1214
- varName,
1215
- globalSlot,
1216
- isEditLike,
1217
- );
1218
- }
1219
- });
1220
- return segmentIdToResolvedValue;
1221
- };
1222
-
1223
- const titleVarSegmentValueMapById = useMemo(
1224
- () => getRcsValueMap(templateTitle, TITLE_TEXT),
1225
- [templateTitle, cardVarMapped, isEditFlow, isFullMode],
1226
- );
1227
- const descriptionVarSegmentValueMapById = useMemo(
1228
- () => getRcsValueMap(templateDesc, MESSAGE_TEXT),
1229
- [templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode],
1230
- );
1231
-
1232
- const handleRcsVarChange = (id, value, type) => {
1233
- const sep = id.lastIndexOf('_');
1234
- if (sep === -1) return;
1235
- const token = id.slice(0, sep);
1236
- const variableName = getVarNameFromToken(token);
1237
- if (variableName === undefined || variableName === null || variableName === '') return;
1238
- const isInvalidValue = value?.trim() === '';
1239
- const coercedSlotValue = isInvalidValue ? '' : value;
1240
- const fieldStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1241
- const globalSlot = getGlobalSlotIndexForRcsFieldId(id, fieldStr, type);
1242
- setCardVarMapped((previousVarMap) => {
1243
- const nextVarMap = { ...previousVarMap };
1244
- if (globalSlot !== null && globalSlot !== undefined) {
1245
- // Write by global slot index only — title and description can share the
1246
- // same var name (e.g. {{gt}}), and writing by semantic name would cause
1247
- // the description slot to resolve the title's value and vice-versa.
1248
- const numericKey = String(globalSlot + 1);
1249
- nextVarMap[numericKey] = coercedSlotValue;
1250
- // Remove any stale semantic key so resolveCardVarMappedSlotValue never
1251
- // falls back to it. Guard: when variableName is already the numeric slot
1252
- // key (e.g. {{1}} at slot 0 → both equal "1"), skip the delete or it
1253
- // would erase the value we just wrote.
1254
- if (variableName !== numericKey) {
1255
- delete nextVarMap[variableName];
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
+ );
1256
1104
  }
1257
- } else {
1258
- nextVarMap[variableName] = coercedSlotValue;
1259
- }
1260
- return nextVarMap;
1261
- });
1105
+ });
1106
+ }
1107
+ return renderArray;
1262
1108
  };
1263
1109
 
1264
1110
  const renderTextComponent = () => {
@@ -1302,19 +1148,10 @@ const onTitleAddVar = () => {
1302
1148
  </>
1303
1149
  }
1304
1150
  />
1151
+ <div className="rcs_text_area_wrapper">
1305
1152
  {(isEditFlow || !isFullMode) ? (
1306
- <VarSegmentMessageEditor
1307
- key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
1308
- templateString={templateTitle}
1309
- valueMap={titleVarSegmentValueMapById}
1310
- onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
1311
- onFocus={(id) => setTitleTextAreaId(id)}
1312
- varRegex={rcsVarRegex}
1313
- placeholderPrefix=""
1314
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1315
- />
1153
+ renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
1316
1154
  ) : (
1317
- <div className="rcs_text_area_wrapper">
1318
1155
  <CapInput
1319
1156
  className={`rcs-template-title-input ${
1320
1157
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -1327,8 +1164,8 @@ const onTitleAddVar = () => {
1327
1164
  errorMessage={templateTitleError}
1328
1165
  disabled={isEditFlow || !isFullMode}
1329
1166
  />
1330
- </div>
1331
1167
  )}
1168
+ </div>
1332
1169
  {(isEditFlow || !isFullMode) && templateTitleError && (
1333
1170
  <CapError className="rcs-template-title-error">
1334
1171
  {templateTitleError}
@@ -1376,18 +1213,7 @@ const onTitleAddVar = () => {
1376
1213
  <CapRow className="rcs-create-template-message-input">
1377
1214
  <div className="rcs_text_area_wrapper">
1378
1215
  {(isEditFlow || !isFullMode)
1379
- ? (
1380
- <VarSegmentMessageEditor
1381
- key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
1382
- templateString={templateDesc}
1383
- valueMap={descriptionVarSegmentValueMapById}
1384
- onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
1385
- onFocus={(id) => setDescTextAreaId(id)}
1386
- varRegex={rcsVarRegex}
1387
- placeholderPrefix=""
1388
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1389
- />
1390
- )
1216
+ ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
1391
1217
  : (
1392
1218
  <>
1393
1219
  <TextArea
@@ -1431,9 +1257,7 @@ const onTitleAddVar = () => {
1431
1257
  {templateDescError}
1432
1258
  </CapError>
1433
1259
  )}
1434
- {(isEditFlow || !isFullMode)
1435
- ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
1436
- : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1260
+ {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
1437
1261
  {!isFullMode && hasTag() && (
1438
1262
  <CapAlert
1439
1263
  message={
@@ -1453,6 +1277,18 @@ const onTitleAddVar = () => {
1453
1277
  );
1454
1278
  };
1455
1279
 
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
+
1456
1292
  // Get character count for title (rich card only)
1457
1293
  const getTitleCharacterCount = () => {
1458
1294
  if (templateType === contentType.text_message) return 0;
@@ -1512,7 +1348,7 @@ const onTitleAddVar = () => {
1512
1348
  const hasTag = () => {
1513
1349
  // Check cardVarMapped values for tags
1514
1350
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1515
- const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
1351
+ const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1516
1352
  isTagIncluded(value)
1517
1353
  );
1518
1354
  if (hasTagInMapped) return true;
@@ -1530,6 +1366,14 @@ const onTitleAddVar = () => {
1530
1366
  return false;
1531
1367
  };
1532
1368
 
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
+
1533
1377
  const closeDltContainerHandler = () => {
1534
1378
  setShowDltContainer(false);
1535
1379
  setDltMode('');
@@ -1552,17 +1396,68 @@ const onTitleAddVar = () => {
1552
1396
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1553
1397
  '',
1554
1398
  );
1555
- const templateNameFromDlt = get(dltEditData, 'name', '')
1556
- || get(tempData, 'versions.base.name', '')
1557
- || '';
1558
1399
  closeDltContainerHandler();
1559
1400
  setDltEditData(tempData);
1560
- const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
1561
- setSmsFallbackData({
1562
- templateName: templateNameFromDlt,
1563
- content: fallMsg,
1564
- ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
1565
- });
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
+ };
1566
1461
  };
1567
1462
 
1568
1463
  const getDltSlideBoxContent = () => {
@@ -1589,6 +1484,7 @@ const onTitleAddVar = () => {
1589
1484
  isFullMode={isFullMode}
1590
1485
  isDltFromRcs
1591
1486
  onSelectTemplate={rcsDltEditSelectHandler}
1487
+ handlePeviewTemplate={dltFallbackListingPreviewhandler}
1592
1488
  />
1593
1489
  );
1594
1490
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1609,32 +1505,147 @@ const onTitleAddVar = () => {
1609
1505
  return { dltHeader, dltContent };
1610
1506
  };
1611
1507
 
1612
- const renderFallBackSmsComponent = () => (
1613
- <SmsFallback
1614
- value={smsFallbackData}
1615
- onChange={setSmsFallbackData}
1616
- parentLocation={location}
1617
- smsRegister={smsRegister}
1618
- isFullMode={isFullMode}
1619
- selectedOfferDetails={selectedOfferDetails}
1620
- channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
1621
- sectionTitle={
1622
- smsFallbackData
1623
- ? formatMessage(messages.fallbackLabel)
1624
- : formatMessage(messages.smsFallbackOptional)
1625
- }
1626
- templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
1627
- templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
1628
- /* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
1629
- showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
1630
- disableSelectTemplate={isEditFlow}
1631
- eventContextTags={eventContextTags}
1632
- onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
1633
- isRcsEditFlow={isEditFlow}
1634
- />
1635
- );
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
+ };
1636
1646
 
1637
1647
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1648
+ setImageError(null);
1638
1649
  const isRcsThumbnail = index === 1;
1639
1650
  actions.uploadRcsAsset(file, type, {
1640
1651
  isRcsThumbnail,
@@ -1646,6 +1657,7 @@ const onTitleAddVar = () => {
1646
1657
 
1647
1658
  const setUpdateRcsImageSrc = useCallback(
1648
1659
  (val) => {
1660
+ setAssetListImage(val);
1649
1661
  updateRcsImageSrc(val);
1650
1662
  actions.clearRcsMediaAsset(0);
1651
1663
  },
@@ -1675,6 +1687,7 @@ const onTitleAddVar = () => {
1675
1687
 
1676
1688
 
1677
1689
  const uploadRcsVideo = (file, type, fileParams) => {
1690
+ setImageError(null);
1678
1691
  actions.uploadRcsAsset(file, type, {
1679
1692
  ...fileParams,
1680
1693
  type: 'video',
@@ -1683,6 +1696,9 @@ const onTitleAddVar = () => {
1683
1696
  });
1684
1697
  };
1685
1698
 
1699
+ const updateRcsVideoSrc = (val) => {
1700
+ setRcsVideoSrc(val);
1701
+ };
1686
1702
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
1687
1703
  setRcsVideoSrc(val);
1688
1704
  setAssetList(val);
@@ -1711,7 +1727,7 @@ const onTitleAddVar = () => {
1711
1727
  <>
1712
1728
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1713
1729
  <CapImageUpload
1714
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1730
+ style={{ paddingTop: '20px' }}
1715
1731
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1716
1732
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1717
1733
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1723,6 +1739,7 @@ const onTitleAddVar = () => {
1723
1739
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1724
1740
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1725
1741
  index={1}
1742
+ className="cap-custom-image-upload"
1726
1743
  key={`rcs-uploaded-image-${currentDimension}`}
1727
1744
  imageData={thumbnailData}
1728
1745
  channel={RCS}
@@ -1753,7 +1770,7 @@ const onTitleAddVar = () => {
1753
1770
  value: dim.type,
1754
1771
  label: `${dim.label}`
1755
1772
  }))}
1756
- className="rcs-dimension-select--bottom-spacing"
1773
+ style={{ marginBottom: '20px' }}
1757
1774
  />
1758
1775
  </>
1759
1776
  )}
@@ -1767,6 +1784,7 @@ const onTitleAddVar = () => {
1767
1784
  </div>
1768
1785
  ) : (
1769
1786
  <CapImageUpload
1787
+ style={{ paddingTop: '20px' }}
1770
1788
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1771
1789
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1772
1790
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1777,7 +1795,7 @@ const onTitleAddVar = () => {
1777
1795
  updateImageSrc={setUpdateRcsImageSrc}
1778
1796
  updateOnReUpload={updateOnRcsImageReUpload}
1779
1797
  index={0}
1780
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1798
+ className="cap-custom-image-upload"
1781
1799
  key={`rcs-uploaded-image-${selectedDimension}`}
1782
1800
  imageData={rcsData}
1783
1801
  channel={RCS}
@@ -1807,7 +1825,7 @@ const onTitleAddVar = () => {
1807
1825
  value: dim.type,
1808
1826
  label: `${dim.label}`
1809
1827
  }))}
1810
- className="rcs-dimension-select--bottom-spacing"
1828
+ style={{ marginBottom: '20px' }}
1811
1829
  />
1812
1830
  )}
1813
1831
  {(isEditFlow || !isFullMode) ? (
@@ -1867,16 +1885,8 @@ const onTitleAddVar = () => {
1867
1885
  const getRcsPreview = () => {
1868
1886
 
1869
1887
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
1870
- const isSlotMappingMode = isEditFlow || !isFullMode;
1871
- const titleVarCountForResolve = isMediaTypeText
1872
- ? 0
1873
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
1874
- const resolvedTitle = isMediaTypeText
1875
- ? ''
1876
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
1877
- const resolvedDesc = isSlotMappingMode
1878
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
1879
- : templateDesc;
1888
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1889
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1880
1890
  return (
1881
1891
  <UnifiedPreview
1882
1892
  channel={RCS}
@@ -1899,65 +1909,51 @@ const onTitleAddVar = () => {
1899
1909
  );
1900
1910
  };
1901
1911
 
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
+
1902
1943
  const createPayload = () => {
1903
- const isSlotMappingMode = isEditFlow || !isFullMode;
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;
1904
1953
  const alignment = isMediaTypeImage
1905
1954
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
1906
1955
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
1907
1956
 
1908
- const heightTypeForCardWidth = isMediaTypeText
1909
- ? undefined
1910
- : isMediaTypeImage
1911
- ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
1912
- : isMediaTypeVideo
1913
- ? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
1914
- : undefined;
1915
- const cardWidthFromSelection =
1916
- heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
1917
-
1918
- /** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
1919
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
1920
- const smsFallbackMerged = !isFullMode
1921
- ? (() => {
1922
- const local =
1923
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
1924
- return {
1925
- ...smsFromApiShape,
1926
- ...local,
1927
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
1928
- };
1929
- })()
1930
- : (smsFallbackData || {});
1931
- const smsFallbackForPayload = (() => {
1932
- if (isFullMode) {
1933
- return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
1934
- }
1935
- const mapped = {
1936
- templateName:
1937
- smsFallbackMerged.templateName
1938
- || smsFallbackMerged.smsTemplateName
1939
- || '',
1940
- // Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
1941
- content:
1942
- smsFallbackMerged.content
1943
- || smsFallbackMerged.smsContent
1944
- || smsFallbackMerged.smsTemplateContent
1945
- || smsFallbackMerged.message
1946
- || '',
1947
- templateContent:
1948
- pickFirstSmsFallbackTemplateString(smsFallbackMerged)
1949
- || '',
1950
- ...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
1951
- && { unicodeValidity: smsFallbackMerged.unicodeValidity }),
1952
- ...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
1953
- && Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
1954
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
1955
- smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
1956
- }),
1957
- };
1958
- return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
1959
- })();
1960
-
1961
1957
  const payload = {
1962
1958
  name: templateName,
1963
1959
  versions: {
@@ -1969,14 +1965,12 @@ const onTitleAddVar = () => {
1969
1965
  cardSettings: {
1970
1966
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
1971
1967
  ...(alignment && { mediaAlignment: alignment }),
1972
- cardWidth: cardWidthFromSelection,
1968
+ cardWidth: SMALL,
1973
1969
  },
1974
1970
  cardContent: [
1975
1971
  {
1976
- // Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
1977
- // / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
1978
- title: templateTitle,
1979
- description: templateDesc,
1972
+ title: resolvedTitle,
1973
+ description: resolvedDesc,
1980
1974
  mediaType: templateMediaType,
1981
1975
  ...(!isMediaTypeText && {media: {
1982
1976
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -1985,30 +1979,23 @@ const onTitleAddVar = () => {
1985
1979
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
1986
1980
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
1987
1981
  }}),
1988
- ...(isSlotMappingMode && (() => {
1989
- const templateVarTokens = [
1990
- ...(templateTitle?.match(rcsVarRegex) ?? []),
1991
- ...(templateDesc?.match(rcsVarRegex) ?? []),
1982
+ ...(!isFullMode && (() => {
1983
+ const tokens = [
1984
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1985
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
1992
1986
  ];
1993
- const persistedSlotVarMap = {};
1994
- const seenSemanticVarNames = new Set();
1995
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
1996
- const varName = getVarNameFromToken(token);
1997
- if (!varName) return;
1998
- const resolvedRawValue = resolveCardVarMappedSlotValue(
1999
- cardVarMapped,
2000
- varName,
2001
- slotIndexZeroBased,
2002
- isSlotMappingMode,
2003
- );
2004
- const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
2005
- persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
2006
- if (!seenSemanticVarNames.has(varName)) {
2007
- seenSemanticVarNames.add(varName);
2008
- persistedSlotVarMap[varName] = sanitizedSlotValue;
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] = '';
2009
1996
  }
2010
1997
  });
2011
- return { cardVarMapped: persistedSlotVarMap };
1998
+ return { cardVarMapped: nextMap };
2012
1999
  })()),
2013
2000
  ...(suggestions.length > 0 && { suggestions }),
2014
2001
  }
@@ -2016,29 +2003,17 @@ const onTitleAddVar = () => {
2016
2003
  contentType: isFullMode ? templateType : RICHCARD,
2017
2004
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2018
2005
  },
2019
- ...(smsFallbackForPayload && (() => {
2020
- const smsBodyText =
2021
- smsFallbackForPayload.content
2022
- || smsFallbackForPayload.templateContent
2023
- || smsFallbackForPayload.message
2024
- || smsFallbackForPayload.smsContent
2025
- || '';
2026
- return {
2027
- smsFallBackContent: {
2028
- smsTemplateName: smsFallbackForPayload.templateName || '',
2029
- smsContent: smsBodyText,
2030
- // cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
2031
- message: smsBodyText,
2032
- ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
2033
- unicodeValidity: smsFallbackForPayload.unicodeValidity,
2034
- }),
2035
- ...(smsFallbackForPayload.rcsSmsFallbackVarMapped
2036
- && Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
2037
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
2038
- }),
2006
+ smsFallBackContent: {
2007
+ message: fallbackMessage,
2008
+ ...(isDltEnabled && {
2009
+ templateConfigs: {
2010
+ templateId,
2011
+ templateName: template_name,
2012
+ template,
2013
+ registeredSenderIds,
2039
2014
  },
2040
- };
2041
- })()),
2015
+ }),
2016
+ },
2042
2017
  },
2043
2018
  },
2044
2019
  },
@@ -2048,84 +2023,6 @@ const onTitleAddVar = () => {
2048
2023
  return payload;
2049
2024
  };
2050
2025
 
2051
- /** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
2052
- const testPreviewFormData = useMemo(() => {
2053
- const payload = createPayload();
2054
- const rcs = payload?.versions?.base?.content?.RCS;
2055
- if (!rcs) return null;
2056
- // createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
2057
- const accountIdForCreateMessageMeta =
2058
- (wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
2059
- ? String(wecrmAccountId)
2060
- : accountId;
2061
- const rcsForTest = {
2062
- ...rcs,
2063
- rcsContent: {
2064
- ...rcs.rcsContent,
2065
- ...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
2066
- },
2067
- };
2068
- const out = {
2069
- versions: {
2070
- base: {
2071
- content: {
2072
- RCS: rcsForTest,
2073
- },
2074
- },
2075
- },
2076
- };
2077
- const fb = smsFallbackData;
2078
- if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
2079
- out.templateConfigs = {
2080
- templateId: fb.smsTemplateId || '',
2081
- template: fb.templateContent || fb.content || '',
2082
- traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
2083
- registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
2084
- };
2085
- }
2086
- return out;
2087
- }, [
2088
- templateName,
2089
- templateTitle,
2090
- templateDesc,
2091
- templateMediaType,
2092
- cardVarMapped,
2093
- suggestions,
2094
- rcsImageSrc,
2095
- rcsVideoSrc,
2096
- rcsThumbnailSrc,
2097
- selectedDimension,
2098
- smsFallbackData,
2099
- isFullMode,
2100
- isEditFlow,
2101
- templateType,
2102
- accountId,
2103
- wecrmAccountId,
2104
- accessToken,
2105
- accountName,
2106
- hostName,
2107
- smsRegister,
2108
- ]);
2109
-
2110
- /**
2111
- * Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
2112
- * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
2113
- * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
2114
- */
2115
- const librarySmsFallbackMergedForValidation = useMemo(() => {
2116
- if (isFullMode) {
2117
- return smsFallbackData;
2118
- }
2119
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
2120
- const local =
2121
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
2122
- return {
2123
- ...smsFromApiShape,
2124
- ...local,
2125
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
2126
- };
2127
- }, [isFullMode, templateData, smsFallbackData]);
2128
-
2129
2026
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2130
2027
  // eslint-disable-next-line no-undef
2131
2028
  const error = errorMessage?.message || errorMessage;
@@ -2155,9 +2052,6 @@ const onTitleAddVar = () => {
2155
2052
  _id: params?.id,
2156
2053
  validity: true,
2157
2054
  type: RCS,
2158
- // CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
2159
- // the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
2160
- closeSlideBoxAfterSubmit: !isFullMode,
2161
2055
  };
2162
2056
  getFormData(formDataParams);
2163
2057
  };
@@ -2171,7 +2065,6 @@ const onTitleAddVar = () => {
2171
2065
  actionCallback({ resp, errorMessage });
2172
2066
  setSpin(false); // Always turn off spinner
2173
2067
  if (!errorMessage) {
2174
- setTemplateStatus(RCS_STATUSES.pending);
2175
2068
  onCreateComplete();
2176
2069
  }
2177
2070
  });
@@ -2183,64 +2076,6 @@ const onTitleAddVar = () => {
2183
2076
  }
2184
2077
  };
2185
2078
 
2186
- /** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
2187
- const smsFallbackBlocksDone = () => {
2188
- // Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
2189
- if (
2190
- !isFullMode
2191
- && !isTraiDLTEnable(isFullMode, smsRegister)
2192
- && smsFallbackData == null
2193
- && hasMeaningfulSmsFallbackShape(
2194
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
2195
- )
2196
- ) {
2197
- return true;
2198
- }
2199
- if (!smsFallbackData) return false;
2200
- // Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
2201
- // concern, not a structural requirement for approval — the registered SMS template body stands on
2202
- // its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
2203
- if (isFullMode) return false;
2204
- const merged = librarySmsFallbackMergedForValidation;
2205
- const templateText = pickFirstSmsFallbackTemplateString(merged);
2206
- if (!templateText) {
2207
- return true;
2208
- }
2209
- const rawVarMap =
2210
- merged.rcsSmsFallbackVarMapped
2211
- || merged['rcs-sms-fallback-var-mapped'];
2212
- const varMap =
2213
- rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
2214
- return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
2215
- };
2216
-
2217
- /**
2218
- * Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
2219
- * semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
2220
- * / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
2221
- */
2222
- const isLibraryCampaignCardVarMappingIncomplete = () => {
2223
- if (isFullMode) return false;
2224
- const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
2225
- rcsVarTestRegex.test(elem),
2226
- );
2227
- const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
2228
- rcsVarTestRegex.test(elem),
2229
- );
2230
- const orderedVarNames = [
2231
- ...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2232
- ...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2233
- ];
2234
- if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
2235
- return true;
2236
- }
2237
- return orderedVarNames.some((name, globalIdx) => {
2238
- const v = resolveCardVarMappedSlotValue(cardVarMapped, name, globalIdx, true);
2239
- const s = v == null ? '' : String(v);
2240
- return s.trim() === '';
2241
- });
2242
- };
2243
-
2244
2079
  const isDisableDone = () => {
2245
2080
  if(isEditFlow){
2246
2081
  return false;
@@ -2251,16 +2086,40 @@ const onTitleAddVar = () => {
2251
2086
  }
2252
2087
  }
2253
2088
 
2254
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2255
- return true;
2256
- }
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 ]));
2257
2093
 
2258
- if (smsFallbackBlocksDone()) {
2259
- return true;
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
+ }
2260
2118
  }
2261
2119
 
2262
- if (isMediaTypeText && templateDesc.trim() === '') {
2120
+ if (isMediaTypeText && templateDesc.trim() === '') {
2263
2121
  return true;
2122
+
2264
2123
  }
2265
2124
  if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2266
2125
  return true;
@@ -2278,36 +2137,53 @@ const onTitleAddVar = () => {
2278
2137
  return true;
2279
2138
  }
2280
2139
  }
2281
- if (templateDescError || templateTitleError) {
2282
- return true;
2283
- }
2284
- if (
2285
- smsFallbackData?.content
2286
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2287
- ) {
2140
+ if (templateDescError || templateTitleError || fallbackMessageError) {
2288
2141
  return true;
2289
2142
  }
2290
2143
  return false;
2291
2144
  };
2292
2145
 
2293
2146
  const isEditDisableDone = () => {
2147
+
2294
2148
  if (templateStatus !== RCS_STATUSES.approved) {
2295
2149
  return true;
2296
2150
  }
2297
2151
 
2298
- // if (!isFullMode) {
2299
- // if (templateName.trim() === '' || templateNameError) {
2300
- // return true;
2301
- // }
2302
- // }
2303
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2304
- return true;
2152
+ if (!isFullMode) {
2153
+ if (templateName.trim() === '' || templateNameError) {
2154
+ return true;
2155
+ }
2305
2156
  }
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 ]));
2306
2161
 
2307
- if (smsFallbackBlocksDone()) {
2308
- return true;
2309
- }
2162
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2163
+ return true;
2164
+ }
2310
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
+ }
2186
+ }
2311
2187
  if (isMediaTypeText && templateDesc.trim() === '') {
2312
2188
  return true;
2313
2189
  }
@@ -2326,13 +2202,7 @@ const onTitleAddVar = () => {
2326
2202
  return true;
2327
2203
  }
2328
2204
  }
2329
- if (templateTitleError || templateDescError) {
2330
- return true;
2331
- }
2332
- if (
2333
- smsFallbackData?.content
2334
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2335
- ) {
2205
+ if (templateTitleError || templateDescError || fallbackMessageError) {
2336
2206
  return true;
2337
2207
  }
2338
2208
  return false;
@@ -2382,56 +2252,52 @@ const onTitleAddVar = () => {
2382
2252
  };
2383
2253
 
2384
2254
  const getMainContent = () => {
2385
- // Slideboxes are rendered outside the page-level spinner to avoid
2386
- // stacking/blur issues during initial loads.
2387
- if (showDltContainer) return null;
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
+ }
2388
2268
 
2389
2269
  return (
2390
2270
  <>
2391
- {templateStatus !== '' && (
2392
- <CapRow className="template-status-container">
2393
- <CapColumn span={14}>
2394
- <CapLabel type="label2">
2395
- {formatMessage(messages.templateStatusLabel)}
2396
- </CapLabel>
2397
-
2398
- {templateStatus && (
2399
- <CapAlert
2400
- message={getTemplateStatusMessage()}
2401
- type={getTemplateStatusType(templateStatus)}
2402
- />
2403
- )}
2404
- </CapColumn>
2405
- </CapRow>
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>
2406
2283
  )}
2407
- <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2284
+ <CapRow className="cap-rcs-creatives">
2408
2285
  <CapColumn span={14}>
2409
2286
  {/* template name */}
2410
2287
  {isFullMode && (
2411
- isEditFlow ? (
2412
- <div className="rcs-creative-name-readonly">
2413
- <CapHeading type="h4">
2414
- {formatMessage(globalMessages.creativeNameLabel)}
2415
- </CapHeading>
2416
- <CapHeading type="h5" className="rcs-creative-name-value">
2417
- {templateName || '-'}
2418
- </CapHeading>
2419
- </div>
2420
- ) : (
2421
- <CapInput
2422
- id="rcs_template_name_input"
2423
- data-testid="template_name"
2424
- onChange={onTemplateNameChange}
2425
- errorMessage={templateNameError}
2426
- placeholder={formatMessage(
2427
- globalMessages.templateNamePlaceholder,
2428
- )}
2429
- value={templateName || ''}
2430
- size="default"
2431
- label={formatMessage(globalMessages.creativeNameLabel)}
2432
- disabled={(isEditFlow || !isFullMode)}
2433
- />
2434
- )
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
+ />
2435
2301
  )}
2436
2302
  {renderLabel('templateTypeLabel')}
2437
2303
  <CapRadioGroup
@@ -2459,7 +2325,7 @@ const onTitleAddVar = () => {
2459
2325
  </>
2460
2326
  )}
2461
2327
  {renderTextComponent()}
2462
- <CapDivider className="rcs-fallback-section-divider" />
2328
+ <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2463
2329
  {renderFallBackSmsComponent()}
2464
2330
  <div className="rcs-scroll-div" />
2465
2331
  </CapColumn>
@@ -2471,8 +2337,7 @@ const onTitleAddVar = () => {
2471
2337
 
2472
2338
 
2473
2339
  <div className="rcs-footer">
2474
- {/* 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). */}
2475
- {!isEditFlow && isFullMode && (
2340
+ {!isEditFlow && (
2476
2341
  <>
2477
2342
  <div className="button-disabled-tooltip-wrapper">
2478
2343
  <CapButton
@@ -2493,6 +2358,7 @@ const onTitleAddVar = () => {
2493
2358
  className="rcs-test-preview-btn"
2494
2359
  type="secondary"
2495
2360
  disabled={true}
2361
+ style={{ marginLeft: "8px" }}
2496
2362
  >
2497
2363
  <FormattedMessage {...creativesMessages.testAndPreview} />
2498
2364
  </CapButton>
@@ -2525,6 +2391,51 @@ const onTitleAddVar = () => {
2525
2391
  </>
2526
2392
  )}
2527
2393
  </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
+ )}
2528
2439
  </>
2529
2440
  );
2530
2441
  };
@@ -2533,38 +2444,12 @@ const onTitleAddVar = () => {
2533
2444
  <CapSpin spinning={loadingTags || spin}>
2534
2445
  {getMainContent()}
2535
2446
  </CapSpin>
2536
-
2537
- {showDltContainer && (() => {
2538
- const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
2539
- return (
2540
- <CapSlideBox
2541
- show={showDltContainer}
2542
- header={dltHeader}
2543
- content={dltContent}
2544
- handleClose={closeDltContainerHandler}
2545
- size="size-xl"
2546
- />
2547
- );
2548
- })()}
2549
-
2550
2447
  <TestAndPreviewSlidebox
2551
2448
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
2552
2449
  onClose={handleCloseTestAndPreview}
2553
- formData={testPreviewFormData}
2554
- content={testAndPreviewContent}
2450
+ formData={null} // RCS doesn't use formData structure like SMS
2451
+ content={getTemplateContent()}
2555
2452
  currentChannel={RCS}
2556
- orgUnitId={orgUnitId}
2557
- smsFallbackContent={
2558
- smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
2559
- ? {
2560
- templateContent:
2561
- smsFallbackData.templateContent || smsFallbackData.content || '',
2562
- templateName: smsFallbackData.templateName || '',
2563
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackData?.rcsSmsFallbackVarMapped ?? {},
2564
- }
2565
- : null
2566
- }
2567
- smsRegister={smsRegister}
2568
2453
  />
2569
2454
  </>
2570
2455
  );