@capillarytech/creatives-library 8.0.330-alpha.0 → 8.0.331

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 -18
  2. package/package.json +2 -2
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -28
  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 -998
  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 -13
  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 +100 -298
  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 +3 -40
  53. package/v2Containers/Rcs/index.js +901 -1144
  54. package/v2Containers/Rcs/index.scss +6 -85
  55. package/v2Containers/Rcs/messages.js +2 -12
  56. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +2236 -41719
  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 -435
  61. package/v2Containers/Rcs/utils.js +10 -405
  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 -636
  66. package/v2Containers/SmsTrai/Edit/messages.js +4 -14
  67. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +2604 -4590
  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 -201
  82. package/utils/tests/templateVarUtils.test.js +0 -204
  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 -118
  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 -277
  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 -225
  114. package/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +0 -318
  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,17 +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
- extractRegisteredSenderIdsFromSmsFallbackRecord,
48
- pickFirstSmsFallbackTemplateString,
49
- syncCardVarMappedSemanticsFromSlots,
50
- hasMeaningfulSmsFallbackShape,
51
- getLibrarySmsFallbackApiBaselineFromTemplateData,
52
- pickRcsCardVarMappedEntries,
53
- } from './rcsLibraryHydrationUtils';
54
63
  import {
55
64
  RCS,
56
65
  SMS,
@@ -72,6 +81,8 @@ import {
72
81
  MESSAGE_TEXT,
73
82
  ALLOWED_EXTENSIONS_VIDEO_REGEX,
74
83
  RCS_VIDEO_SIZE,
84
+ TEMPLATE_HEADER_MAX_LENGTH,
85
+ TEMPLATE_MESSAGE_MAX_LENGTH,
75
86
  RCS_THUMBNAIL_MIN_SIZE,
76
87
  RCS_THUMBNAIL_MAX_SIZE,
77
88
  contentType,
@@ -84,47 +95,35 @@ import {
84
95
  MAX_BUTTONS,
85
96
  INITIAL_SUGGESTIONS_DATA_STOP,
86
97
  RCS_BUTTON_TYPES,
98
+ titletype,
99
+ descType,
87
100
  STANDALONE,
88
101
  VERTICAL,
89
102
  SMALL,
90
103
  MEDIUM,
91
104
  RICHCARD,
92
- RCS_NUMERIC_VAR_NAME_REGEX,
93
- RCS_NUMERIC_VAR_TOKEN_REGEX,
94
- RCS_TAG_AREA_FIELD_TITLE,
95
- RCS_TAG_AREA_FIELD_DESC,
96
105
  } from './constants';
97
106
  import globalMessages from '../Cap/messages';
98
107
  import messages from './messages';
99
108
  import creativesMessages from '../CreativesContainer/messages';
100
109
  import withCreatives from '../../hoc/withCreatives';
101
110
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
102
- import VarSegmentMessageEditor from '../../v2Components/VarSegmentMessageEditor';
103
- import { ANDROID, RCS_SMS_FALLBACK_VAR_MAPPED_PROP } from '../../v2Components/CommonTestAndPreview/constants';
111
+ import { ANDROID } from '../../v2Components/CommonTestAndPreview/constants';
104
112
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
105
- import { splitTemplateVarString } from '../../utils/templateVarUtils';
106
113
  import CapImageUpload from '../../v2Components/CapImageUpload';
114
+ import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
107
115
  import Templates from '../Templates';
108
116
  import SmsTraiEdit from '../SmsTrai/Edit';
109
- import SmsFallback from '../../v2Components/SmsFallback';
110
- import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
111
117
  import TagList from '../TagList';
112
118
  import { validateTags } from '../../utils/tagValidations';
113
- import { isTraiDLTEnable } from '../../utils/common';
119
+ import { getCdnUrl } from '../../utils/cdnTransformation';
114
120
  import { isTagIncluded } from '../../utils/commonUtils';
115
121
  import injectReducer from '../../utils/injectReducer';
116
122
  import v2RcsReducer from './reducer';
117
- import {
118
- areAllRcsSmsFallbackVarSlotsFilled,
119
- buildRcsNumericMustachePlaceholderRegex,
120
- getTemplateStatusType,
121
- normalizeCardVarMapped,
122
- coalesceCardVarMappedToTemplate,
123
- getRcsSemanticVarNamesSpanningTitleAndDesc,
124
- resolveCardVarMappedSlotValue,
125
- sanitizeCardVarMappedValue,
126
- } from './utils';
123
+ import { getTemplateStatusType } from './utils';
127
124
 
125
+
126
+ const { Group: CapCheckboxGroup } = CapCheckbox;
128
127
  export const Rcs = (props) => {
129
128
  const {
130
129
  intl,
@@ -138,14 +137,15 @@ export const Rcs = (props) => {
138
137
  templatesActions,
139
138
  globalActions,
140
139
  location,
140
+ handleClose,
141
141
  getDefaultTags,
142
142
  supportedTags,
143
143
  metaEntities,
144
144
  injectedTags,
145
145
  loadingTags,
146
146
  getFormData,
147
+ isDltEnabled,
147
148
  smsRegister,
148
- orgUnitId,
149
149
  selectedOfferDetails,
150
150
  eventContextTags,
151
151
  accountData = {},
@@ -156,8 +156,8 @@ export const Rcs = (props) => {
156
156
  } = props || {};
157
157
  const { formatMessage } = intl;
158
158
  const { TextArea } = CapInput;
159
+ const { CapCustomCardList } = CapCustomCard;
159
160
  const [isEditFlow, setEditFlow] = useState(false);
160
- const isEditLike = isEditFlow || !isFullMode;
161
161
  const [tags, updateTags] = useState([]);
162
162
  const [spin, setSpin] = useState(false);
163
163
  //template
@@ -166,71 +166,112 @@ export const Rcs = (props) => {
166
166
  const [templateMediaType, setTemplateMediaType] = useState(
167
167
  RCS_MEDIA_TYPES.NONE,
168
168
  );
169
+ const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
169
170
  const [templateTitle, setTemplateTitle] = useState('');
170
171
  const [templateDesc, setTemplateDesc] = useState('');
171
172
  const [templateDescError, setTemplateDescError] = useState(false);
172
173
  const [templateStatus, setTemplateStatus] = useState('');
174
+ const [templateDate, setTemplateDate] = useState('');
175
+ //fallback
176
+ const [fallbackMessage, setFallbackMessage] = useState('');
177
+ const [fallbackMessageError, setFallbackMessageError] = useState(false);
173
178
  //fallback dlt
174
179
  const [showDltContainer, setShowDltContainer] = useState(false);
175
180
  const [dltMode, setDltMode] = useState('');
176
181
  const [dltEditData, setDltEditData] = useState({});
177
- /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
178
- const [smsFallbackData, setSmsFallbackData] = useState(undefined);
182
+ const [showDltCard, setShowDltCard] = useState(false);
183
+ const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
184
+ const [dltPreviewData, setDltPreviewData] = useState('');
179
185
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
180
- const buttonType = RCS_BUTTON_TYPES.NONE;
186
+ const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
181
187
  const [suggestionError, setSuggestionError] = useState(true);
182
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([]);
183
196
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
184
197
  const [descVarMappedData, setDescVarMappedData] = useState({});
185
198
  const [titleTextAreaId, setTitleTextAreaId] = useState();
186
199
  const [descTextAreaId, setDescTextAreaId] = useState();
187
200
  const [assetList, setAssetList] = useState({});
201
+ const [assetListImage, setAssetListImage] = useState('');
188
202
  const [rcsImageSrc, updateRcsImageSrc] = useState('');
189
203
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
190
204
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
191
205
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
206
+ const [imageError, setImageError] = useState(null);
192
207
  const [templateTitleError, setTemplateTitleError] = useState(false);
193
208
  const [cardVarMapped, setCardVarMapped] = useState({});
194
- /** Bump when hydrated cardVarMapped payload changes so VarSegment TextAreas remount with fresh valueMap (controlled-input sync). */
195
- const [rcsVarSegmentEditorRemountKey, setRcsVarSegmentEditorRemountKey] = useState(0);
196
- const lastHydratedRcsCardVarSignatureRef = useRef(null);
197
-
198
- /**
199
- * Hydrate only from template payload — not from full `rcsData`. Uploads/asset updates change `rcsData`
200
- * without changing `templateDetails`; re-running hydration then cleared `smsFallbackData`, so the SMS
201
- * fallback card / content stopped appearing until re-selected.
202
- */
203
- const rcsHydrationDetails = useMemo(
204
- () => (isFullMode ? rcsData?.templateDetails : templateData),
205
- [isFullMode, rcsData?.templateDetails, templateData],
206
- );
207
-
208
- /** 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'). */
209
- const lastTagSchemaQueryKeyRef = useRef(null);
210
- /**
211
- * Library: parent often passes a new `templateData` object reference every render. Re-applying the same
212
- * SMS fallback snapshot was resetting `smsFallbackData` and caused VarSegment inputs to flicker old/new.
213
- */
214
- const lastSmsFallbackHydrationKeyRef = useRef(null);
215
-
216
- const fetchTagSchemaIfNewQuery = useCallback(
217
- (query) => {
218
- const key = JSON.stringify(query);
219
- if (lastTagSchemaQueryKeyRef.current === key) {
220
- return;
221
- }
222
- lastTagSchemaQueryKeyRef.current = key;
223
- globalActions.fetchSchemaForEntity(query);
224
- },
225
- [globalActions],
226
- );
227
209
 
228
210
  // TestAndPreviewSlidebox state
229
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
+ ]);
230
270
 
231
271
  // Handle Test and Preview button click
232
272
  const handleTestAndPreview = useCallback(() => {
233
273
  setShowTestAndPreviewSlidebox(true);
274
+ setIsTestAndPreviewMode(true);
234
275
  if (propsHandleTestAndPreview) {
235
276
  propsHandleTestAndPreview();
236
277
  }
@@ -239,52 +280,40 @@ export const Rcs = (props) => {
239
280
  // Handle close Test and Preview slidebox
240
281
  const handleCloseTestAndPreview = useCallback(() => {
241
282
  setShowTestAndPreviewSlidebox(false);
283
+ setIsTestAndPreviewMode(false);
242
284
  if (propsHandleCloseTestAndPreview) {
243
285
  propsHandleCloseTestAndPreview();
244
286
  }
245
287
  }, [propsHandleCloseTestAndPreview]);
246
288
 
247
- /** Merge editor slot map into `smsFallbackData` (like `cardVarMapped` for title/desc). */
248
- const handleSmsFallbackEditorStateChange = useCallback((patch) => {
249
- setSmsFallbackData((prev) => {
250
- if (!patch || typeof patch !== 'object') return prev;
251
- // Bail out when no template has been selected yet — SmsTraiEdit fires
252
- // onRcsFallbackEditorStateChange on mount (unicodeValidity, varMapped),
253
- // which would create a non-null smsFallbackData from nothing and cause
254
- // the card to appear as "Untitled creative" before any save.
255
- if (!prev) return prev;
256
- return { ...prev, ...patch };
257
- });
258
- }, []);
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
+ };
259
298
 
260
- /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
261
299
  const [accountId, setAccountId] = useState('');
262
- /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
263
- const [wecrmAccountId, setWecrmAccountId] = useState('');
264
300
  const [accessToken, setAccessToken] = useState('');
265
301
  const [hostName, setHostName] = useState('');
266
302
  const [accountName, setAccountName] = useState('');
303
+ const [rcsAccount, setRcsAccount] = useState('');
267
304
  useEffect(() => {
268
305
  const accountObj = accountData.selectedRcsAccount || {};
269
306
  if (!isEmpty(accountObj)) {
270
307
  const {
271
- id: wecrmId,
272
308
  sourceAccountIdentifier = '',
273
309
  configs = {},
274
310
  } = accountObj;
311
+
275
312
  setAccountId(sourceAccountIdentifier);
276
- setWecrmAccountId(
277
- wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
278
- );
279
313
  setAccessToken(configs.accessToken || '');
280
314
  setHostName(accountObj.hostName || '');
281
315
  setAccountName(accountObj.name || '');
282
- } else {
283
- setAccountId('');
284
- setWecrmAccountId('');
285
- setAccessToken('');
286
- setHostName('');
287
- setAccountName('');
316
+ setRcsAccount(accountObj.id || '');
288
317
  }
289
318
  }, [accountData.selectedRcsAccount]);
290
319
 
@@ -340,9 +369,7 @@ export const Rcs = (props) => {
340
369
  if (isFullMode) return;
341
370
  if (loadingTags || !tags || tags.length === 0) return;
342
371
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
343
- const slotOffset =
344
- type === TITLE_TEXT ? 0 : (templateTitle ? templateTitle.match(rcsVarRegex) || [] : []).length;
345
- const resolved = resolveTemplateWithMap(templateStr, slotOffset); // placeholders -> mapped value (or '')
372
+ const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
346
373
  if (!resolved) {
347
374
  if (type === TITLE_TEXT) setTemplateTitleError(false);
348
375
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -387,48 +414,13 @@ export const Rcs = (props) => {
387
414
 
388
415
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
389
416
 
390
- const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
391
-
392
- /** Same `{{tag}}` in both title and description must not share one semantic map entry. */
393
- const rcsSpanningSemanticVarNames = useMemo(
394
- () => getRcsSemanticVarNamesSpanningTitleAndDesc(templateTitle, templateDesc, rcsVarRegex),
395
- [templateTitle, templateDesc],
396
- );
397
-
398
- /** Global slot index (0-based, title vars then desc) for a VarSegmentMessageEditor `id` (`{{tok}}_segIdx`), or null if not found. */
399
- const getGlobalSlotIndexForRcsFieldId = (varSegmentCompositeId, fieldTemplateStr, fieldType) => {
400
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
401
- const offset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
402
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateStr ?? '');
403
- let varOrdinal = 0;
404
- for (let segmentIndexInField = 0; segmentIndexInField < templateSegments.length; segmentIndexInField += 1) {
405
- const segmentToken = templateSegments[segmentIndexInField];
406
- if (rcsVarTestRegex.test(segmentToken)) {
407
- if (`${segmentToken}_${segmentIndexInField}` === varSegmentCompositeId) {
408
- return offset + varOrdinal;
409
- }
410
- varOrdinal += 1;
411
- }
412
- }
413
- return null;
414
- };
415
-
416
- const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
417
+ const resolveTemplateWithMap = (str = '') => {
417
418
  if (!str) return '';
418
- const arr = splitTemplateVarStringRcs(str);
419
- let varOrdinal = 0;
419
+ const arr = splitTemplateVarString(str);
420
420
  return arr.map((elem) => {
421
421
  if (rcsVarTestRegex.test(elem)) {
422
422
  const key = getVarNameFromToken(elem);
423
- const globalSlot = slotOffset + varOrdinal;
424
- varOrdinal += 1;
425
- const v = resolveCardVarMappedSlotValue(
426
- cardVarMapped,
427
- key,
428
- globalSlot,
429
- isEditLike,
430
- rcsSpanningSemanticVarNames.has(key),
431
- );
423
+ const v = cardVarMapped?.[key];
432
424
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
433
425
  return String(v);
434
426
  }
@@ -436,156 +428,107 @@ export const Rcs = (props) => {
436
428
  }).join('');
437
429
  };
438
430
 
439
- /**
440
- * Content for TestAndPreviewSlidebox — apply cardVarMapped whenever the slot editor is shown
441
- * (VarSegmentMessageEditor: isEditFlow || !isFullMode). Full-mode create without edit uses plain
442
- * TextArea only — no mapping. Full-mode + edit uses slots; !isFullMode alone is not enough.
443
- */
444
- const getTemplateContent = useCallback(() => {
445
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
446
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
447
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
448
-
449
- const isSlotMappingMode = isEditFlow || !isFullMode;
450
- const titleVarCountForResolve = isMediaTypeText
451
- ? 0
452
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
453
- const resolvedTitle = isMediaTypeText
454
- ? ''
455
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
456
- const resolvedDesc = isSlotMappingMode
457
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
458
- : templateDesc;
459
-
460
- const mediaPreview = {};
461
- if (isMediaTypeImage && rcsImageSrc) {
462
- mediaPreview.rcsImageSrc = rcsImageSrc;
463
- }
464
- if (isMediaTypeVideo && !isMediaTypeText) {
465
- if (rcsThumbnailSrc) {
466
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
467
- } else if (rcsVideoSrc?.videoSrc) {
468
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
469
- }
470
- if (rcsThumbnailSrc) {
471
- mediaPreview.rcsThumbnailSrc = rcsThumbnailSrc;
472
- }
473
- }
474
-
475
- const contentObj = {
476
- templateHeader: resolvedTitle,
477
- templateMessage: resolvedDesc,
478
- ...mediaPreview,
479
- ...(suggestions.length > 0 && {
480
- suggestions: suggestions,
481
- }),
482
- };
483
431
 
484
- return contentObj;
485
- }, [
486
- templateMediaType,
487
- templateTitle,
488
- templateDesc,
489
- rcsImageSrc,
490
- rcsVideoSrc,
491
- rcsThumbnailSrc,
492
- suggestions,
493
- selectedDimension,
494
- isFullMode,
495
- isEditFlow,
496
- cardVarMapped,
497
- rcsSpanningSemanticVarNames,
498
- ]);
432
+ useEffect(() => {
433
+ if (isFullMode || isEditFlow) return;
434
+ const tokens = [
435
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
436
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
437
+ ];
438
+ if (!tokens.length) return;
439
+ setCardVarMapped((prev) => {
440
+ const next = { ...(prev || {}) };
441
+ let changed = false;
442
+ tokens.forEach((t) => {
443
+ const name = getVarNameFromToken(t);
444
+ if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
445
+ next[name] = '';
446
+ changed = true;
447
+ }
448
+ });
449
+ return changed ? next : prev;
450
+ });
451
+ }, [isFullMode, templateTitle, templateDesc]);
499
452
 
500
- const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
501
453
 
454
+ const RcsLabel = styled.div`
455
+ display: flex;
456
+ margin-top: 20px;
457
+ `;
502
458
  const paramObj = params || {};
503
459
  useEffect(() => {
504
- const { id } = paramObj;
505
- if (id && isFullMode) {
506
- setSpin(true);
507
- actions.getTemplateDetails(id, setSpin);
508
- }
509
- return () => {
510
- actions.clearEditResponse();
511
- };
512
- }, [paramObj.id, isFullMode]);
460
+ const { id } = paramObj;
461
+ if (id && isFullMode) {
462
+ setSpin(true);
463
+ actions.getTemplateDetails(id, setSpin);
464
+ }
465
+ return () => {
466
+ actions.clearEditResponse();
467
+ };
468
+ }, [paramObj.id]);
513
469
 
514
470
  useEffect(() => {
515
- if (!(isEditFlow || !isFullMode)) return;
516
-
517
- const titleTokenCount = (templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length;
518
-
519
- const initField = (targetString, setVarMap, slotOffset) => {
520
- const arr = splitTemplateVarStringRcs(targetString);
521
- if (!arr?.length) {
522
- setVarMap({});
523
- return;
524
- }
525
- const nextVarMap = {};
526
- let varOrdinal = 0;
527
- arr.forEach((elem, idx) => {
528
- // Mustache tokens: {{1}}, {{user_name}}, {{tag.FORMAT_1}}, etc.
529
- if (rcsVarTestRegex.test(elem)) {
530
- const id = `${elem}_${idx}`;
531
- const varName = getVarNameFromToken(elem);
532
- const globalSlot = slotOffset + varOrdinal;
533
- varOrdinal += 1;
534
- const mappedValue = resolveCardVarMappedSlotValue(
535
- cardVarMapped,
536
- varName,
537
- globalSlot,
538
- isEditLike,
539
- rcsSpanningSemanticVarNames.has(varName),
540
- );
541
- nextVarMap[id] = mappedValue;
471
+ if (!(isEditFlow || !isFullMode)) return;
472
+
473
+ const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
474
+ const arr = splitTemplateVarString(targetString);
475
+ if (!arr?.length) {
476
+ setVarMap({});
477
+ setUpdated([]);
478
+ return;
542
479
  }
543
- });
544
- setVarMap(nextVarMap);
545
- };
546
-
547
- initField(templateTitle, setTitleVarMappedData, 0);
548
- initField(templateDesc, setDescVarMappedData, titleTokenCount);
549
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
480
+ const nextVarMap = {};
481
+ const nextUpdated = [...arr];
482
+ arr.forEach((elem, idx) => {
483
+ // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
484
+ if (rcsVarTestRegex.test(elem)) {
485
+ const id = `${elem}_${idx}`;
486
+ const varName = getVarNameFromToken(elem);
487
+ const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
488
+ nextVarMap[id] = mappedValue;
489
+ if (mappedValue !== '') {
490
+ nextUpdated[idx] = mappedValue;
491
+ } else {
492
+ nextUpdated[idx] = elem;
493
+ }
494
+ }
495
+ });
496
+ setVarMap(nextVarMap);
497
+ setUpdated(nextUpdated);
498
+ };
550
499
 
551
- useEffect(() => {
552
- if (!isEditFlow && isFullMode) {
500
+ initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
501
+ initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
502
+ }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
503
+
504
+ useEffect(() => {
505
+ if(!isEditFlow && isFullMode){
553
506
  setRcsVideoSrc({});
554
507
  updateRcsImageSrc('');
555
508
  setUpdateRcsImageSrc('');
556
509
  updateRcsThumbnailSrc('');
557
510
  setAssetList({});
558
- }
559
- }, [templateMediaType]);
511
+ }
512
+ }, [templateMediaType]);
560
513
 
561
- /** Status on first card — same merged card as title/description/cardVarMapped (library may only set rcsContent at root). */
562
- const templateStatusHelper = (cardContentFirst) => {
563
- const raw =
564
- cardContentFirst?.Status
565
- ?? cardContentFirst?.status
566
- ?? cardContentFirst?.approvalStatus
567
- ?? '';
568
- const status = typeof raw === 'string' ? raw.trim() : String(raw);
569
- const n = status.toLowerCase();
570
- switch (n) {
571
- case 'approved':
514
+ const templateStatusHelper = (details) => {
515
+ const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
516
+ switch (status) {
517
+ case RCS_STATUSES.approved:
572
518
  setTemplateStatus(RCS_STATUSES.approved);
573
519
  break;
574
- case 'pending':
520
+ case RCS_STATUSES.pending:
575
521
  setTemplateStatus(RCS_STATUSES.pending);
576
522
  break;
577
- case 'awaitingapproval':
523
+ case RCS_STATUSES.awaitingApproval:
578
524
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
579
525
  break;
580
- case 'unavailable':
526
+ case RCS_STATUSES.unavailable:
581
527
  setTemplateStatus(RCS_STATUSES.unavailable);
582
528
  break;
583
- case 'rejected':
529
+ case RCS_STATUSES.rejected:
584
530
  setTemplateStatus(RCS_STATUSES.rejected);
585
531
  break;
586
- case 'created':
587
- setTemplateStatus(RCS_STATUSES.created);
588
- break;
589
532
  default:
590
533
  setTemplateStatus(status);
591
534
  break;
@@ -596,6 +539,7 @@ export const Rcs = (props) => {
596
539
  if (mediaType) {
597
540
  setTemplateMediaType(mediaType);
598
541
  }
542
+ const tempOrientation = cardSettings.cardOrientation;
599
543
  const tempAlignment = cardSettings.mediaAlignment;
600
544
  const tempHeight = mediaData.height;
601
545
 
@@ -638,206 +582,48 @@ export const Rcs = (props) => {
638
582
  };
639
583
 
640
584
  useEffect(() => {
641
- const details = rcsHydrationDetails;
585
+ const details = isFullMode ? rcsData?.templateDetails : templateData;
642
586
  if (details && Object.keys(details).length > 0) {
643
- // Library/campaign: match SMS fallback — read from versions… and from top-level rcsContent (getCreativesData / parent shape).
644
- const cardFromVersions = get(
645
- details,
646
- 'versions.base.content.RCS.rcsContent.cardContent[0]',
647
- );
648
- const cardFromTop = get(details, 'rcsContent.cardContent[0]');
649
- const card0 = { ...(cardFromTop || {}), ...(cardFromVersions || {}) };
650
- const cardVarMappedFromCardContent =
651
- card0?.cardVarMapped != null && typeof card0.cardVarMapped === 'object'
652
- ? card0.cardVarMapped
653
- : {};
654
- const cardVarMappedFromRootMirror =
655
- details?.rcsCardVarMapped != null && typeof details.rcsCardVarMapped === 'object'
656
- ? details.rcsCardVarMapped
657
- : {};
658
- // Root mirror from getCreativesData / getFormData — campaigns often preserve flat fields when
659
- // nested versions.cardContent[0].cardVarMapped is dropped on reload.
660
- const mergedCardVarMappedFromPayload = {
661
- ...cardVarMappedFromRootMirror,
662
- ...cardVarMappedFromCardContent,
663
- };
664
- const loadedTitleForMap = card0?.title != null ? String(card0.title) : '';
665
- const loadedDescForMap = card0?.description != null ? String(card0.description) : '';
666
- const hydratedCardVarPayloadSignature = `${loadedTitleForMap}\u0000${loadedDescForMap}\u0000${JSON.stringify(
667
- Object.keys(mergedCardVarMappedFromPayload)
668
- .sort()
669
- .reduce((accumulator, mapKey) => {
670
- accumulator[mapKey] = mergedCardVarMappedFromPayload[mapKey];
671
- return accumulator;
672
- }, {}),
673
- )}`;
674
- if (lastHydratedRcsCardVarSignatureRef.current !== hydratedCardVarPayloadSignature) {
675
- lastHydratedRcsCardVarSignatureRef.current = hydratedCardVarPayloadSignature;
676
- setRcsVarSegmentEditorRemountKey((previousKey) => previousKey + 1);
587
+ if (!isFullMode) {
588
+ const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
589
+ setCardVarMapped(tempCardVarMapped);
677
590
  }
678
- const tokenListForMap = [
679
- ...(loadedTitleForMap ? loadedTitleForMap.match(rcsVarRegex) ?? [] : []),
680
- ...(loadedDescForMap ? loadedDescForMap.match(rcsVarRegex) ?? [] : []),
681
- ];
682
- const orderedTagNamesForMap = tokenListForMap.map((token) => getVarNameFromToken(token)).filter(Boolean);
683
- // Full-mode library/API payloads need normalize for legacy slot shapes. Campaign round-trip from
684
- // getFormData already stores TagList values as {{TagName}}; normalize can strip or remap them.
685
- const cardVarMappedBeforeCoalesce = isFullMode
686
- ? normalizeCardVarMapped(mergedCardVarMappedFromPayload, orderedTagNamesForMap)
687
- : { ...mergedCardVarMappedFromPayload };
688
- const cardVarMappedAfterCoalesce = coalesceCardVarMappedToTemplate(
689
- cardVarMappedBeforeCoalesce,
690
- loadedTitleForMap,
691
- loadedDescForMap,
692
- rcsVarRegex,
693
- );
694
- const cardVarMappedAfterNumericSlotSync = !isFullMode
695
- ? syncCardVarMappedSemanticsFromSlots(
696
- cardVarMappedAfterCoalesce,
697
- loadedTitleForMap,
698
- loadedDescForMap,
699
- rcsVarRegex,
700
- )
701
- : cardVarMappedAfterCoalesce;
702
- const hydratedCardVarMappedResult = { ...cardVarMappedAfterNumericSlotSync };
703
- // Pre-populate variable/tag mappings while opening an existing template in edit flows
704
- setCardVarMapped((previousVarMapState) => {
705
- const previousVarMap = previousVarMapState ?? {};
706
- if (previousVarMap === hydratedCardVarMappedResult) return previousVarMapState;
707
- const previousVarMapKeys = Object.keys(previousVarMap);
708
- const nextVarMapKeys = Object.keys(hydratedCardVarMappedResult);
709
- if (previousVarMapKeys.length === nextVarMapKeys.length) {
710
- const allSlotValuesMatchPrevious = previousVarMapKeys.every(
711
- (key) => previousVarMap[key] === hydratedCardVarMappedResult[key],
712
- );
713
- if (allSlotValuesMatchPrevious) return previousVarMapState;
714
- }
715
- return hydratedCardVarMappedResult;
716
- });
717
- const mediaType =
718
- card0.mediaType
719
- || get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
591
+ const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
720
592
  if (mediaType === RCS_MEDIA_TYPES.NONE) {
721
593
  setTemplateType(contentType.text_message);
722
594
  } else {
723
595
  setTemplateType(contentType.rich_card);
724
596
  }
725
597
  setEditFlow(true);
726
- setTemplateName(details?.name || details?.creativeName || '');
727
- const loadedTitle = loadedTitleForMap;
728
- const loadedDesc = loadedDescForMap;
729
- const { normalizedTitle, normalizedDesc } = normalizeLibraryLoadedTitleDesc({
730
- loadedTitle,
731
- loadedDesc,
732
- isFullMode,
733
- cardVarMappedAfterHydration: hydratedCardVarMappedResult,
734
- rcsVarRegex,
735
- });
598
+ setTemplateName(details.name || '');
599
+ const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
600
+ const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
601
+ const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
602
+ const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
603
+ const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
736
604
  setTemplateTitle(normalizedTitle);
737
605
  setTemplateDesc(normalizedDesc);
738
- setSuggestions(
739
- Array.isArray(card0.suggestions)
740
- ? card0.suggestions
741
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []),
742
- );
743
- const cardForStatus = {
744
- ...card0,
745
- Status:
746
- card0.Status
747
- ?? card0.status
748
- ?? card0.approvalStatus
749
- ?? get(details, 'templateStatus')
750
- ?? get(details, 'approvalStatus')
751
- ?? get(details, 'creativeStatus')
752
- ?? get(details, 'versions.base.content.RCS.templateApprovalStatus')
753
- ?? '',
754
- };
755
- templateStatusHelper(cardForStatus);
756
- const mediaData =
757
- card0.media != null && card0.media !== ''
758
- ? card0.media
759
- : get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
760
- const cardSettings =
761
- get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '')
762
- || get(details, 'rcsContent.cardSettings', '');
606
+ setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
607
+ templateStatusHelper(details);
608
+ const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
609
+ const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
763
610
  setMediaData(mediaData, mediaType, cardSettings);
764
-
765
- const smsFallbackContent = mergeRcsSmsFallBackContentFromDetails(details);
766
- const base = get(smsFallbackContent, 'versions.base', {});
767
- const updatedEditor = base['updated-sms-editor'] ?? base['sms-editor'];
768
- const smsEditor = base['sms-editor'];
769
- const fromNested = Array.isArray(updatedEditor)
770
- ? updatedEditor.join('')
771
- : (typeof updatedEditor === 'string' ? updatedEditor : (smsEditor || ''));
772
- const fallbackMessage = smsFallbackContent.smsContent
773
- || smsFallbackContent.smsTemplateContent
774
- || smsFallbackContent.message
775
- || fromNested
776
- || '';
777
- const varMappedFromPayload = smsFallbackContent[RCS_SMS_FALLBACK_VAR_MAPPED_PROP] || {};
778
- const hasVarMapped = Object.keys(varMappedFromPayload).length > 0;
779
- const hasFallbackPayload =
780
- smsFallbackContent
781
- && Object.keys(smsFallbackContent).length > 0
782
- && (
783
- !!smsFallbackContent.smsTemplateName
784
- || !!fallbackMessage
785
- || hasVarMapped
786
- );
787
- if (hasFallbackPayload) {
788
- if (!fallbackMessage && !hasVarMapped && process.env.NODE_ENV !== 'production') {
789
- console.warn('[RCS SMS Fallback] No message text found in API response. Inspect shape:', smsFallbackContent);
790
- }
791
- const unicodeFromApi =
792
- typeof smsFallbackContent.unicodeValidity === 'boolean'
793
- ? smsFallbackContent.unicodeValidity
794
- : (typeof base['unicode-validity'] === 'boolean' ? base['unicode-validity'] : true);
795
- const registeredSenderIdsFromApi =
796
- extractRegisteredSenderIdsFromSmsFallbackRecord(smsFallbackContent);
797
- const nextSmsState = {
798
- templateName: smsFallbackContent.smsTemplateName || '',
799
- content: fallbackMessage,
800
- templateContent: fallbackMessage,
801
- unicodeValidity: unicodeFromApi,
802
- ...(hasVarMapped && { rcsSmsFallbackVarMapped: varMappedFromPayload }),
803
- ...(Array.isArray(registeredSenderIdsFromApi) && registeredSenderIdsFromApi.length > 0
804
- ? { registeredSenderIds: registeredSenderIdsFromApi }
805
- : {}),
806
- };
807
- const hydrationKey = JSON.stringify({
808
- creativeKey: details._id || details.name || details.creativeName || '',
809
- templateName: nextSmsState.templateName,
810
- content: nextSmsState.content,
811
- unicodeValidity: nextSmsState.unicodeValidity,
812
- varMapped: nextSmsState.rcsSmsFallbackVarMapped || {},
813
- senderIds:
814
- Array.isArray(registeredSenderIdsFromApi)
815
- ? registeredSenderIdsFromApi.join('\u001f')
816
- : '',
817
- });
818
- if (
819
- isFullMode
820
- || lastSmsFallbackHydrationKeyRef.current !== hydrationKey
821
- ) {
822
- lastSmsFallbackHydrationKeyRef.current = hydrationKey;
823
- setSmsFallbackData(nextSmsState);
824
- }
825
- } else if (isFullMode || lastSmsFallbackHydrationKeyRef.current !== '__EMPTY__') {
826
- lastSmsFallbackHydrationKeyRef.current = '__EMPTY__';
827
- setSmsFallbackData(null);
611
+ if (details?.edit) {
612
+ const rcsAccountId = get(details, 'versions.base.content.RCS.rcsContent.accountId', '');
613
+ setRcsAccount(rcsAccountId);
828
614
  }
829
615
  }
830
- }, [rcsHydrationDetails, isFullMode]);
616
+ }, [rcsData, templateData, isFullMode, isEditFlow]);
617
+
831
618
 
832
619
  useEffect(() => {
833
620
  if (templateType === contentType.text_message) {
834
621
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
835
- // Full-mode create only: switching to plain text clears draft title/media. Never clear when
836
- // hydrating library/edit (would wipe templateData after load) — regression seen after SMS fallback work.
622
+ setTemplateTitle('');
623
+ setTemplateTitleError('');
837
624
  if (!isEditFlow && isFullMode) {
838
- setTemplateTitle('');
839
- setTemplateTitleError('');
840
625
  setUpdateRcsImageSrc('');
626
+ setUpdateRcsVideoSrc({});
841
627
  setRcsVideoSrc({});
842
628
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
843
629
  }
@@ -864,8 +650,7 @@ export const Rcs = (props) => {
864
650
  if (!showDltContainer) {
865
651
  const { type, module } = location.query || {};
866
652
  const isEmbedded = type === EMBEDDED;
867
- // Match TagList initial fetch (getTagsforContext('Outbound') → context "outbound") so we do not request a different context than the two TagList headers.
868
- const context = isEmbedded ? module : 'outbound';
653
+ const context = isEmbedded ? module : DEFAULT;
869
654
  const embedded = isEmbedded ? type : FULL;
870
655
  const query = {
871
656
  layout: SMS,
@@ -876,9 +661,9 @@ export const Rcs = (props) => {
876
661
  if (getDefaultTags) {
877
662
  query.context = getDefaultTags;
878
663
  }
879
- fetchTagSchemaIfNewQuery(query);
664
+ globalActions.fetchSchemaForEntity(query);
880
665
  }
881
- }, [showDltContainer, fetchTagSchemaIfNewQuery]);
666
+ }, [showDltContainer]);
882
667
 
883
668
  useEffect(() => {
884
669
  let tag = get(metaEntities, `tags.standard`, []);
@@ -901,107 +686,39 @@ export const Rcs = (props) => {
901
686
  context,
902
687
  embedded,
903
688
  };
904
- if (getDefaultTags) {
905
- query.context = getDefaultTags;
906
- }
907
- fetchTagSchemaIfNewQuery(query);
689
+ globalActions.fetchSchemaForEntity(query);
908
690
  };
909
691
 
910
- const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
911
- if (!templateStr || !numericVarName || !tagName) return templateStr;
912
- const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
913
- return templateStr.replace(re, `{{${tagName}}}`);
692
+ const onTagSelect = (data, areaId) => {
693
+ if (!areaId) return;
694
+ const sep = areaId.lastIndexOf('_');
695
+ if (sep === -1) return;
696
+ const numId = Number(areaId.slice(sep + 1));
697
+ if (isNaN(numId)) return;
698
+ const token = areaId.slice(0, sep);
699
+ const variableName = getVarNameFromToken(token);
700
+ if (!variableName) return;
701
+ setCardVarMapped((prev) => {
702
+ const base = (prev?.[variableName] ?? '').toString();
703
+ const nextVal = `${base}{{${data}}}`;
704
+ return {
705
+ ...(prev || {}),
706
+ [variableName]: nextVal,
707
+ };
708
+ });
914
709
  };
915
710
 
916
- const onTagSelect = (selectedTagNameFromPicker, varSegmentCompositeDomId, tagAreaField) => {
917
- if (!varSegmentCompositeDomId) return;
918
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
919
- if (underscoreIndexInCompositeId === -1) return;
920
- const segmentIndexSuffix = varSegmentCompositeDomId.slice(underscoreIndexInCompositeId + 1);
921
- if (segmentIndexSuffix === '' || isNaN(Number(segmentIndexSuffix))) return;
922
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
923
- const semanticOrNumericVarName = getVarNameFromToken(mustacheTokenFromCompositeId);
924
- if (!semanticOrNumericVarName) return;
925
- const isNumericPlaceholderSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(semanticOrNumericVarName));
926
- const templateStringForField =
927
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? templateTitle : templateDesc;
928
- const titleOrMessageFieldType =
929
- tagAreaField === RCS_TAG_AREA_FIELD_TITLE ? TITLE_TEXT : MESSAGE_TEXT;
930
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
931
- varSegmentCompositeDomId,
932
- templateStringForField,
933
- titleOrMessageFieldType,
934
- );
935
- const cardVarMappedNumericSlotKey =
936
- globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined
937
- ? String(globalVarSlotIndexZeroBased + 1)
938
- : null;
939
-
940
- setCardVarMapped((previousCardVarMapped) => {
941
- const updatedCardVarMapped = { ...(previousCardVarMapped || {}) };
942
- if (isNumericPlaceholderSlot) {
943
- const existingValueBeforeAppend = (
944
- previousCardVarMapped?.[semanticOrNumericVarName] ?? ''
945
- ).toString();
946
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
947
- delete updatedCardVarMapped[semanticOrNumericVarName];
948
- updatedCardVarMapped[selectedTagNameFromPicker] = mappedValueAfterAppendingTag;
949
- } else {
950
- // Same semantic token (e.g. {{adv}}) in title and body must not share one map key for
951
- // "existing value" — that appends the new tag onto the other field. Match handleRcsVarChange:
952
- // read/write the global numeric slot only and drop the shared semantic key.
953
- const existingValueBeforeAppend = cardVarMappedNumericSlotKey
954
- ? String(previousCardVarMapped?.[cardVarMappedNumericSlotKey] ?? '')
955
- : String(previousCardVarMapped?.[semanticOrNumericVarName] ?? '');
956
- const mappedValueAfterAppendingTag = `${existingValueBeforeAppend}{{${selectedTagNameFromPicker}}}`;
957
- delete updatedCardVarMapped[semanticOrNumericVarName];
958
- if (cardVarMappedNumericSlotKey) {
959
- updatedCardVarMapped[cardVarMappedNumericSlotKey] = mappedValueAfterAppendingTag;
960
- } else {
961
- updatedCardVarMapped[semanticOrNumericVarName] = mappedValueAfterAppendingTag;
962
- }
963
- }
964
- return updatedCardVarMapped;
965
- });
711
+ const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
966
712
 
967
- if (
968
- isNumericPlaceholderSlot
969
- && (tagAreaField === RCS_TAG_AREA_FIELD_TITLE || tagAreaField === RCS_TAG_AREA_FIELD_DESC)
970
- ) {
971
- if (tagAreaField === RCS_TAG_AREA_FIELD_TITLE) {
972
- setTemplateTitle((previousTitle) => {
973
- const titleAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
974
- previousTitle || '',
975
- semanticOrNumericVarName,
976
- selectedTagNameFromPicker,
977
- );
978
- if (titleAfterReplacingNumericPlaceholder === previousTitle) return previousTitle;
979
- setTemplateTitleError(variableErrorHandling(titleAfterReplacingNumericPlaceholder));
980
- // Remount segment editor: tag insert replaces {{n}} with e.g. {{tag.FORMAT_1}} — slot ids change; avoids stale UI vs manual typing in full-mode TextArea
981
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
982
- return titleAfterReplacingNumericPlaceholder;
983
- });
984
- } else {
985
- setTemplateDesc((previousDescription) => {
986
- const descriptionAfterReplacingNumericPlaceholder = replaceNumericPlaceholderWithTagInTemplate(
987
- previousDescription || '',
988
- semanticOrNumericVarName,
989
- selectedTagNameFromPicker,
990
- );
991
- if (descriptionAfterReplacingNumericPlaceholder === previousDescription) {
992
- return previousDescription;
993
- }
994
- setTemplateDescError(variableErrorHandling(descriptionAfterReplacingNumericPlaceholder));
995
- setRcsVarSegmentEditorRemountKey((k) => k + 1);
996
- return descriptionAfterReplacingNumericPlaceholder;
997
- });
998
- }
999
- }
1000
- };
713
+ const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
1001
714
 
1002
- const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
715
+ const onTagSelectFallback = (data) => {
716
+ const tempMsg = `${fallbackMessage}{{${data}}}`;
717
+ const error = fallbackMessageErrorHandler(tempMsg);
718
+ setFallbackMessage(tempMsg);
719
+ setFallbackMessageError(error);
720
+ };
1003
721
 
1004
- const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
1005
722
 
1006
723
  //removing optout tag for rcs
1007
724
  const getRcsTags = () => {
@@ -1014,14 +731,15 @@ export const Rcs = (props) => {
1014
731
  };
1015
732
  // tag Code end
1016
733
 
1017
- const renderLabel = (value, desc) => {
734
+ const renderLabel = (value, showLabel, desc) => {
735
+ const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
1018
736
  return (
1019
737
  <>
1020
- <div className="rcs-form-section-heading">
738
+ <RcsLabel>
1021
739
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
1022
- </div>
740
+ </RcsLabel>
1023
741
  {desc && (
1024
- <CapLabel type="label3" className="rcs-form-field-caption">
742
+ <CapLabel type="label3" style={{ marginBottom: '17px' }}>
1025
743
  {formatMessage(messages[desc])}
1026
744
  </CapLabel>
1027
745
  )}
@@ -1107,6 +825,47 @@ export const Rcs = (props) => {
1107
825
  setTemplateDescError(error);
1108
826
  };
1109
827
 
828
+
829
+ const templateDescErrorHandler = (value) => {
830
+ let errorMessage = false;
831
+ const { isBraceError } = validateTags({
832
+ content: value,
833
+ tagsParam: tags,
834
+ location,
835
+ tagModule: getDefaultTags,
836
+ isFullMode,
837
+ }) || {};
838
+
839
+ const maxLength = templateType === contentType.text_message
840
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH
841
+ : RCS_RICH_CARD_MAX_LENGTH;
842
+
843
+ if (value === '' && isMediaTypeText) {
844
+ errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
845
+ } else if (value?.length > maxLength) {
846
+ errorMessage = formatMessage(messages.templateMessageLengthError);
847
+ }
848
+
849
+ if (isBraceError) {
850
+ errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
851
+ }
852
+ return errorMessage;
853
+ };
854
+
855
+
856
+ const onFallbackMessageChange = ({ target: { value } }) => {
857
+ const error = fallbackMessageErrorHandler(value);
858
+ setFallbackMessage(value);
859
+ setFallbackMessageError(error);
860
+ };
861
+
862
+ const fallbackMessageErrorHandler = (value) => {
863
+ if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
864
+ return formatMessage(messages.fallbackMsgLenError);
865
+ }
866
+ return false;
867
+ };
868
+
1110
869
  // Check for forbidden characters: square brackets [] and single curly braces {}
1111
870
  const forbiddenCharactersValidation = (value) => {
1112
871
  if (!value) return false;
@@ -1151,43 +910,53 @@ export const Rcs = (props) => {
1151
910
  if(!isFullMode){
1152
911
  return false;
1153
912
  }
1154
- // Allow Liquid-style param names: letters, digits, underscore, dots (e.g. dynamic_expiry_date_after_3_days.FORMAT_1)
1155
- if (!/^[\w.]+$/.test(paramName)) {
913
+ if (!/^\w+$/.test(paramName)) {
1156
914
  return formatMessage(messages.unknownCharactersError);
1157
915
  }
1158
916
  }
1159
917
  return false;
1160
918
  };
919
+
920
+ const templateHeaderErrorHandler = (value) => {
921
+ let errorMessage = false;
922
+ if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
923
+ errorMessage = formatMessage(messages.templateHeaderLengthError);
924
+ } else {
925
+ errorMessage = variableErrorHandling(value);
926
+ }
927
+ return errorMessage;
928
+ };
929
+
930
+
931
+ const templateMessageErrorHandler = (value) => {
932
+ let errorMessage = false;
933
+ if (value === '') {
934
+ errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
935
+ } else if (
936
+ value?.length
937
+ > TEMPLATE_MESSAGE_MAX_LENGTH
938
+ ) {
939
+ errorMessage = formatMessage(messages.templateMessageLengthError);
940
+ } else {
941
+ errorMessage = variableErrorHandling(value);
942
+ }
943
+ return errorMessage;
944
+ };
945
+
1161
946
 
1162
947
  const onMessageAddVar = () => {
1163
- onAddVar(templateDesc);
948
+ onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
1164
949
  };
1165
950
 
1166
- /**
1167
- * Returns the smallest positive integer not already used as a `{{N}}` variable
1168
- * in either the title or description fields, or null if the limit (19) is reached.
1169
- * Scans both fields so title and description vars never share the same number
1170
- * (duplicate numbers would share a cardVarMapped key and bleed values across fields).
1171
- */
1172
- const getNextRcsNumericVarNumber = (titleStr, descStr) => {
1173
- const allExistingVars = [
1174
- ...(titleStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
1175
- ...(descStr.match(RCS_NUMERIC_VAR_TOKEN_REGEX) || []),
1176
- ];
1177
- const existingNumbers = allExistingVars.flatMap(v => {
1178
- const m = v.match(/\d+/);
1179
- return m ? [parseInt(m[0], 10)] : [];
1180
- });
951
+ const onAddVar = (type, messageContent, regex) => {
952
+ // Always append the next variable at the end, like WhatsApp
953
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
954
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
1181
955
  let nextNumber = 1;
1182
956
  while (existingNumbers.includes(nextNumber)) {
1183
957
  nextNumber++;
1184
958
  }
1185
- return nextNumber > 19 ? null : nextNumber;
1186
- };
1187
-
1188
- const onAddVar = (messageContent) => {
1189
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
1190
- if (nextNumber === null) {
959
+ if (nextNumber > 19) {
1191
960
  return;
1192
961
  }
1193
962
  const nextVar = `{{${nextNumber}}}`;
@@ -1199,13 +968,15 @@ const onAddVar = (messageContent) => {
1199
968
  };
1200
969
 
1201
970
  const onTitleAddVar = () => {
1202
- // Scan both title AND description so the new title var number doesn't
1203
- // duplicate a number already used in the description. Duplicate numeric
1204
- // names would share the same cardVarMapped semantic key, causing the
1205
- // description slot to reflect the title slot value and vice-versa.
971
+ // Always append the next variable at the end, like WhatsApp
1206
972
  const messageContent = templateTitle;
1207
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
1208
- if (nextNumber === null) {
973
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
974
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
975
+ let nextNumber = 1;
976
+ while (existingNumbers.includes(nextNumber)) {
977
+ nextNumber++;
978
+ }
979
+ if (nextNumber > 19) {
1209
980
  return;
1210
981
  }
1211
982
  const nextVar = `{{${nextNumber}}}`;
@@ -1217,6 +988,66 @@ const onTitleAddVar = () => {
1217
988
  setTemplateTitleError(error);
1218
989
  };
1219
990
 
991
+
992
+ const splitTemplateVarString = (str) => {
993
+ if (!str) return [];
994
+ const validVarArr = str.match(rcsVarRegex) || [];
995
+ const templateVarArray = [];
996
+ let content = str;
997
+ while (content?.length !== 0) {
998
+ const index = content.indexOf(validVarArr?.[0]);
999
+ if (index !== -1) {
1000
+ templateVarArray.push(content.substring(0, index));
1001
+ templateVarArray.push(validVarArr?.[0]);
1002
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length);
1003
+ validVarArr?.shift();
1004
+ } else {
1005
+ templateVarArray.push(content);
1006
+ break;
1007
+ }
1008
+ }
1009
+ return templateVarArray.filter(Boolean);
1010
+ };
1011
+
1012
+ const textAreaValue = (idValue, type) => {
1013
+ if (idValue >= 0) {
1014
+ const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1015
+ const templateArr = splitTemplateVarString(templateStr);
1016
+ const token = templateArr?.[idValue] || "";
1017
+ if (token && rcsVarTestRegex.test(token)) {
1018
+ const varName = getVarNameFromToken(token);
1019
+ return (cardVarMapped?.[varName] ?? '').toString();
1020
+ }
1021
+ return "";
1022
+ }
1023
+ return "";
1024
+ };
1025
+
1026
+ const textAreaValueChange = (e, type) => {
1027
+ const value = e?.target?.value ?? '';
1028
+ const id = e?.target?.id || e?.currentTarget?.id || '';
1029
+ if (!id) return;
1030
+ const sep = id.lastIndexOf('_');
1031
+ if (sep === -1) return;
1032
+ const isInvalidValue = value?.trim() === "";
1033
+ const token = id.slice(0, sep);
1034
+ const variableName = getVarNameFromToken(token);
1035
+
1036
+ if (variableName) {
1037
+ setCardVarMapped((prev) => ({
1038
+ ...prev,
1039
+ [variableName]: isInvalidValue ? "" : value,
1040
+ }));
1041
+ }
1042
+ };
1043
+
1044
+ const setTextAreaId = (e, type) => {
1045
+ const id = e?.target?.id || e?.currentTarget?.id || '';
1046
+ if (!id) return;
1047
+ if (type === TITLE_TEXT) setTitleTextAreaId(id);
1048
+ else setDescTextAreaId(id);
1049
+ };
1050
+
1220
1051
  const renderButtonComponent = () => {
1221
1052
  return (
1222
1053
  <>
@@ -1247,65 +1078,39 @@ const onTitleAddVar = () => {
1247
1078
  );
1248
1079
  };
1249
1080
 
1250
- const getRcsValueMap = (fieldTemplateString, fieldType) => {
1251
- if (!fieldTemplateString) return {};
1252
- const titleVarTokenMatches = templateTitle?.match(rcsVarRegex) ?? [];
1253
- const slotOffset = fieldType === TITLE_TEXT ? 0 : titleVarTokenMatches.length;
1254
- const templateSegments = splitTemplateVarStringRcs(fieldTemplateString);
1255
- const segmentIdToResolvedValue = {};
1256
- let varOrdinal = 0;
1257
- templateSegments.forEach((segmentToken, segmentIndexInField) => {
1258
- if (rcsVarTestRegex.test(segmentToken)) {
1259
- const varSegmentCompositeId = `${segmentToken}_${segmentIndexInField}`;
1260
- const varName = getVarNameFromToken(segmentToken);
1261
- const globalSlot = slotOffset + varOrdinal;
1262
- varOrdinal += 1;
1263
- segmentIdToResolvedValue[varSegmentCompositeId] = resolveCardVarMappedSlotValue(
1264
- cardVarMapped,
1265
- varName,
1266
- globalSlot,
1267
- isEditLike,
1268
- rcsSpanningSemanticVarNames.has(varName),
1269
- );
1270
- }
1271
- });
1272
- return segmentIdToResolvedValue;
1273
- };
1274
-
1275
- const titleVarSegmentValueMapById = useMemo(
1276
- () => getRcsValueMap(templateTitle, TITLE_TEXT),
1277
- [templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
1278
- );
1279
- const descriptionVarSegmentValueMapById = useMemo(
1280
- () => getRcsValueMap(templateDesc, MESSAGE_TEXT),
1281
- [templateDesc, templateTitle, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames],
1282
- );
1283
-
1284
- const handleRcsVarChange = (varSegmentCompositeDomId, value, type) => {
1285
- const underscoreIndexInCompositeId = varSegmentCompositeDomId.lastIndexOf('_');
1286
- if (underscoreIndexInCompositeId === -1) return;
1287
- const mustacheTokenFromCompositeId = varSegmentCompositeDomId.slice(0, underscoreIndexInCompositeId);
1288
- const variableName = getVarNameFromToken(mustacheTokenFromCompositeId);
1289
- if (variableName === undefined || variableName === null || variableName === '') return;
1290
- const isInvalidValue = value?.trim() === '';
1291
- const coercedSlotValue = isInvalidValue ? '' : value;
1292
- const templateStringForField = type === TITLE_TEXT ? templateTitle : templateDesc;
1293
- const globalVarSlotIndexZeroBased = getGlobalSlotIndexForRcsFieldId(
1294
- varSegmentCompositeDomId,
1295
- templateStringForField,
1296
- type,
1297
- );
1298
- setCardVarMapped((previousCardVarMapped) => {
1299
- const updatedCardVarMapped = { ...previousCardVarMapped };
1300
- // Remove stale semantic key: keeping it causes every other slot sharing the same
1301
- // variable name (e.g. {{adv}} in both title and description) to read the same value
1302
- // via the semantic-key fallback in resolveCardVarMappedSlotValue.
1303
- delete updatedCardVarMapped[variableName];
1304
- if (globalVarSlotIndexZeroBased !== null && globalVarSlotIndexZeroBased !== undefined) {
1305
- updatedCardVarMapped[String(globalVarSlotIndexZeroBased + 1)] = coercedSlotValue;
1306
- }
1307
- return updatedCardVarMapped;
1308
- });
1081
+ const renderedRCSEditMessage = (descArray, type) => {
1082
+ const renderArray = [];
1083
+ if (descArray?.length) {
1084
+ descArray.forEach((elem, index) => {
1085
+ if (rcsVarTestRegex.test(elem)) {
1086
+ // Variable input
1087
+ renderArray.push(
1088
+ <TextArea
1089
+ id={`${elem}_${index}`}
1090
+ key={`${elem}_${index}`}
1091
+ placeholder={`enter the value for ${elem}`}
1092
+ autosize={{ minRows: 1, maxRows: 3 }}
1093
+ onChange={e => textAreaValueChange(e, type)}
1094
+ value={textAreaValue(index, type)}
1095
+ onFocus={(e) => setTextAreaId(e, type)}
1096
+ />
1097
+ );
1098
+ } else if (elem) {
1099
+ // Static text
1100
+ renderArray.push(
1101
+ <TextArea
1102
+ key={`static_${index}`}
1103
+ value={elem}
1104
+ autosize={{ minRows: 1, maxRows: 3 }}
1105
+ disabled
1106
+ className="rcs-edit-template-message-static-textarea"
1107
+ style={{ background: '#fafafa', color: '#888' }}
1108
+ />
1109
+ );
1110
+ }
1111
+ });
1112
+ }
1113
+ return renderArray;
1309
1114
  };
1310
1115
 
1311
1116
  const renderTextComponent = () => {
@@ -1349,19 +1154,10 @@ const onTitleAddVar = () => {
1349
1154
  </>
1350
1155
  }
1351
1156
  />
1157
+ <div className="rcs_text_area_wrapper">
1352
1158
  {(isEditFlow || !isFullMode) ? (
1353
- <VarSegmentMessageEditor
1354
- key={`rcs-title-vars-${rcsVarSegmentEditorRemountKey}`}
1355
- templateString={templateTitle}
1356
- valueMap={titleVarSegmentValueMapById}
1357
- onChange={(id, value) => handleRcsVarChange(id, value, TITLE_TEXT)}
1358
- onFocus={(id) => setTitleTextAreaId(id)}
1359
- varRegex={rcsVarRegex}
1360
- placeholderPrefix=""
1361
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1362
- />
1159
+ renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
1363
1160
  ) : (
1364
- <div className="rcs_text_area_wrapper">
1365
1161
  <CapInput
1366
1162
  className={`rcs-template-title-input ${
1367
1163
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -1374,8 +1170,8 @@ const onTitleAddVar = () => {
1374
1170
  errorMessage={templateTitleError}
1375
1171
  disabled={isEditFlow || !isFullMode}
1376
1172
  />
1377
- </div>
1378
1173
  )}
1174
+ </div>
1379
1175
  {(isEditFlow || !isFullMode) && templateTitleError && (
1380
1176
  <CapError className="rcs-template-title-error">
1381
1177
  {templateTitleError}
@@ -1422,20 +1218,8 @@ const onTitleAddVar = () => {
1422
1218
  </CapRow>
1423
1219
  <CapRow className="rcs-create-template-message-input">
1424
1220
  <div className="rcs_text_area_wrapper">
1425
- {/* Edit/library: segmented inputs (split on {{…}}). Full-mode create: single TextArea below — manual entry there never hits segment split. TagList replaces {{n}} in template string here. */}
1426
1221
  {(isEditFlow || !isFullMode)
1427
- ? (
1428
- <VarSegmentMessageEditor
1429
- key={`rcs-desc-vars-${rcsVarSegmentEditorRemountKey}`}
1430
- templateString={templateDesc}
1431
- valueMap={descriptionVarSegmentValueMapById}
1432
- onChange={(id, value) => handleRcsVarChange(id, value, MESSAGE_TEXT)}
1433
- onFocus={(id) => setDescTextAreaId(id)}
1434
- varRegex={rcsVarRegex}
1435
- placeholderPrefix=""
1436
- getPlaceholder={() => formatMessage(messages.rcsVarSlotPlaceholder)}
1437
- />
1438
- )
1222
+ ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
1439
1223
  : (
1440
1224
  <>
1441
1225
  <TextArea
@@ -1479,9 +1263,7 @@ const onTitleAddVar = () => {
1479
1263
  {templateDescError}
1480
1264
  </CapError>
1481
1265
  )}
1482
- {(isEditFlow || !isFullMode)
1483
- ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
1484
- : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1266
+ {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
1485
1267
  {!isFullMode && hasTag() && (
1486
1268
  <CapAlert
1487
1269
  message={
@@ -1501,6 +1283,18 @@ const onTitleAddVar = () => {
1501
1283
  );
1502
1284
  };
1503
1285
 
1286
+
1287
+ const fallbackSmsLength = () => (
1288
+ <CapLabel type="label1" className="fallback-sms-length">
1289
+ {formatMessage(messages.totalCharacters, {
1290
+ smsCount: Math.ceil(
1291
+ fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1292
+ ),
1293
+ number: fallbackMessage?.length,
1294
+ })}
1295
+ </CapLabel>
1296
+ );
1297
+
1504
1298
  // Get character count for title (rich card only)
1505
1299
  const getTitleCharacterCount = () => {
1506
1300
  if (templateType === contentType.text_message) return 0;
@@ -1560,7 +1354,7 @@ const onTitleAddVar = () => {
1560
1354
  const hasTag = () => {
1561
1355
  // Check cardVarMapped values for tags
1562
1356
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1563
- const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
1357
+ const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1564
1358
  isTagIncluded(value)
1565
1359
  );
1566
1360
  if (hasTagInMapped) return true;
@@ -1578,6 +1372,14 @@ const onTitleAddVar = () => {
1578
1372
  return false;
1579
1373
  };
1580
1374
 
1375
+ //adding creative dlt fallback sms handlers
1376
+ const addDltMsgHandler = () => {
1377
+ setShowDltContainer(true);
1378
+ setDltMode(RCS_DLT_MODE.TEMPLATES);
1379
+ setDltEditData({});
1380
+ setFallbackMessage('');
1381
+ };
1382
+
1581
1383
  const closeDltContainerHandler = () => {
1582
1384
  setShowDltContainer(false);
1583
1385
  setDltMode('');
@@ -1600,17 +1402,68 @@ const onTitleAddVar = () => {
1600
1402
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1601
1403
  '',
1602
1404
  );
1603
- const templateNameFromDlt = get(dltEditData, 'name', '')
1604
- || get(tempData, 'versions.base.name', '')
1605
- || '';
1606
1405
  closeDltContainerHandler();
1607
1406
  setDltEditData(tempData);
1608
- const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
1609
- setSmsFallbackData({
1610
- templateName: templateNameFromDlt,
1611
- content: fallMsg,
1612
- ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
1613
- });
1407
+ setFallbackMessage(fallMsg);
1408
+ setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1409
+ setShowDltCard(true);
1410
+ };
1411
+
1412
+ const rcsDltCardDeleteHandler = () => {
1413
+ closeDltContainerHandler();
1414
+ setDltEditData({});
1415
+ setFallbackMessage('');
1416
+ setFallbackMessageError(false);
1417
+ setShowDltCard(false);
1418
+ };
1419
+
1420
+ const dltFallbackListingPreviewhandler = (data) => {
1421
+ const {
1422
+ 'updated-sms-editor': updatedSmsEditor = [],
1423
+ 'sms-editor': smsEditor = '',
1424
+ } = data.versions.base || {};
1425
+ setFallbackPreviewmode(true);
1426
+ setDltPreviewData(
1427
+ updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1428
+ );
1429
+ };
1430
+
1431
+ const getDltContentCardList = (content, channel) => {
1432
+ const extra = [
1433
+ <CapIcon
1434
+ type="edit"
1435
+ style={{ marginRight: '8px' }}
1436
+ onClick={() => rcsDltEditSelectHandler(dltEditData)}
1437
+ />,
1438
+ <CapDropdown
1439
+ overlay={(
1440
+ <CapMenu>
1441
+ <>
1442
+ <CapMenu.Item
1443
+ className="ant-dropdown-menu-item"
1444
+ onClick={() => setFallbackPreviewmode(true)}
1445
+ >
1446
+ {formatMessage(globalMessages.preview)}
1447
+ </CapMenu.Item>
1448
+ <CapMenu.Item
1449
+ className="ant-dropdown-menu-item"
1450
+ onClick={rcsDltCardDeleteHandler}
1451
+ >
1452
+ {formatMessage(globalMessages.delete)}
1453
+ </CapMenu.Item>
1454
+ </>
1455
+ </CapMenu>
1456
+ )}
1457
+ >
1458
+ <CapIcon type="more" />
1459
+ </CapDropdown>,
1460
+ ];
1461
+ return {
1462
+ title: channel,
1463
+ content,
1464
+ cardType: channel,
1465
+ extra,
1466
+ };
1614
1467
  };
1615
1468
 
1616
1469
  const getDltSlideBoxContent = () => {
@@ -1637,6 +1490,7 @@ const onTitleAddVar = () => {
1637
1490
  isFullMode={isFullMode}
1638
1491
  isDltFromRcs
1639
1492
  onSelectTemplate={rcsDltEditSelectHandler}
1493
+ handlePeviewTemplate={dltFallbackListingPreviewhandler}
1640
1494
  />
1641
1495
  );
1642
1496
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1657,32 +1511,147 @@ const onTitleAddVar = () => {
1657
1511
  return { dltHeader, dltContent };
1658
1512
  };
1659
1513
 
1660
- const renderFallBackSmsComponent = () => (
1661
- <SmsFallback
1662
- value={smsFallbackData}
1663
- onChange={setSmsFallbackData}
1664
- parentLocation={location}
1665
- smsRegister={smsRegister}
1666
- isFullMode={isFullMode}
1667
- selectedOfferDetails={selectedOfferDetails}
1668
- channelsToHide={CHANNELS_TO_HIDE_FOR_SMS_ONLY}
1669
- sectionTitle={
1670
- smsFallbackData
1671
- ? formatMessage(messages.fallbackLabel)
1672
- : formatMessage(messages.smsFallbackOptional)
1673
- }
1674
- templateListTitle={formatMessage(creativesMessages.creativeTemplates)}
1675
- templateListDescription={formatMessage(creativesMessages.creativeTemplatesDesc)}
1676
- /* Full-mode: card layout only while drafting a new template; after send for approval or when a template is loaded, use inline layout. */
1677
- showAsCard={isFullMode && !isEditFlow && templateStatus === ''}
1678
- disableSelectTemplate={isEditFlow}
1679
- eventContextTags={eventContextTags}
1680
- onRcsFallbackEditorStateChange={handleSmsFallbackEditorStateChange}
1681
- isRcsEditFlow={isEditFlow}
1682
- />
1683
- );
1514
+ const renderFallBackSmsComponent = () => {
1515
+ // Completely disable fallback functionality when DLT is disabled
1516
+ return null;
1517
+
1518
+ // const contentArr = [];
1519
+ // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1520
+ // const showCardForDlt = isDltEnabled && showDltCard;
1521
+ // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1522
+ // //pushing common fallback sms headings
1523
+ // contentArr.push(
1524
+ // <CapRow
1525
+ // style={{
1526
+ // marginBottom: isDltEnabled ? '20px' : '10px',
1527
+ // }}
1528
+ // >
1529
+ // <CapHeader
1530
+ // title={(
1531
+ // <CapRow type="flex">
1532
+ // <CapHeading type="h4">
1533
+ // {formatMessage(messages.fallbackLabel)}
1534
+ // </CapHeading>
1535
+ // <CapTooltipWithInfo
1536
+ // placement="right"
1537
+ // infoIconProps={{
1538
+ // style: { marginLeft: '4px', marginTop: '3px' },
1539
+ // }}
1540
+ // title={formatMessage(messages.fallbackToolTip)}
1541
+ // />
1542
+ // </CapRow>
1543
+ // )}
1544
+ // description={formatMessage(messages.fallbackDesc)}
1545
+ // suffix={
1546
+ // isDltEnabled ? null : (
1547
+ // <CapButton
1548
+ // type="flat"
1549
+ // className="fallback-preview-btn"
1550
+ // prefix={<CapIcon type="eye" />}
1551
+ // style={{ color: CAP_SECONDARY.base }}
1552
+ // onClick={() => setFallbackPreviewmode(true)}
1553
+ // disabled={fallbackMessage === '' || fallbackMessageError}
1554
+ // >
1555
+ // {formatMessage(globalMessages.preview)}
1556
+ // </CapButton>
1557
+ // )
1558
+ // }
1559
+ // />
1560
+ // </CapRow>,
1561
+ // );
1562
+
1563
+ //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1564
+ // showAddCreativeBtnForDlt
1565
+ // && contentArr.push(
1566
+ // <CapCard className="rcs-dlt-fallback-card">
1567
+ // <CapRow type="flex" justify="center" align="middle">
1568
+ // <CapColumn span={10}>
1569
+ // <CapImage src={addCreativesIcon} />
1570
+ // </CapColumn>
1571
+ // <CapColumn span={14}>
1572
+ // <CapButton
1573
+ // className="add-dlt-btn"
1574
+ // type="secondary"
1575
+ // onClick={addDltMsgHandler}
1576
+ // >
1577
+ // {formatMessage(messages.addSmsCreative)}
1578
+ // </CapButton>
1579
+ // </CapColumn>
1580
+ // </CapRow>
1581
+ // </CapCard>,
1582
+ // );
1583
+
1584
+ // //dlt is enabled and dlt content is added, show it in a card
1585
+ // showCardForDlt
1586
+ // && contentArr.push(
1587
+ // <CapCustomCardList
1588
+ // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1589
+ // className="rcs-dlt-card"
1590
+ // />,
1591
+ // fallbackMessageError && (
1592
+ // <CapError className="rcs-fallback-len-error">
1593
+ // {formatMessage(messages.fallbackMsgLenError)}
1594
+ // </CapError>
1595
+ // ),
1596
+ // );
1597
+
1598
+ // //dlt is not enabled, show non dlt text area
1599
+ // showNonDltFallbackComp
1600
+ // && contentArr.push(
1601
+ // <>
1602
+ // <CapRow>
1603
+ // <CapHeader
1604
+ // title={(
1605
+ // <CapHeading type="h4">
1606
+ // {formatMessage(messages.fallbackTextAreaLabel)}
1607
+ // </CapHeading>
1608
+ // )}
1609
+ // suffix={(
1610
+ // <TagList
1611
+ // label={formatMessage(globalMessages.addLabels)}
1612
+ // onTagSelect={onTagSelectFallback}
1613
+ // location={location}
1614
+ // tags={tags || []}
1615
+ // onContextChange={handleOnTagsContextChange}
1616
+ // injectedTags={injectedTags || {}}
1617
+ // selectedOfferDetails={selectedOfferDetails}
1618
+ // />
1619
+ // )}
1620
+ // />
1621
+ // </CapRow>
1622
+ // <div className="rcs_fallback_msg_textarea_wrapper">
1623
+ // <TextArea
1624
+ // id="rcs_fallback_message_textarea"
1625
+ // autosize={{ minRows: 3, maxRows: 5 }}
1626
+ // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1627
+ // onChange={onFallbackMessageChange}
1628
+ // errorMessage={fallbackMessageError}
1629
+ // value={fallbackMessage || ""}
1630
+ // />
1631
+ // {!aiContentBotDisabled && (
1632
+ // <CapAskAira.ContentGenerationBot
1633
+ // text={fallbackMessage || ""}
1634
+ // setText={(text) => {
1635
+ // onFallbackMessageChange({ target: { value: text } });
1636
+ // }}
1637
+ // iconPlacement="float-br"
1638
+ // rootStyle={{
1639
+ // bottom: "0.5rem",
1640
+ // right: "0.5rem",
1641
+ // position: "absolute",
1642
+ // }}
1643
+ // />
1644
+ // )}
1645
+ // </div>
1646
+ // <CapRow>{fallbackSmsLength()}</CapRow>
1647
+ // </>
1648
+ // );
1649
+
1650
+ // return <>{contentArr}</>;
1651
+ };
1684
1652
 
1685
1653
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1654
+ setImageError(null);
1686
1655
  const isRcsThumbnail = index === 1;
1687
1656
  actions.uploadRcsAsset(file, type, {
1688
1657
  isRcsThumbnail,
@@ -1694,6 +1663,7 @@ const onTitleAddVar = () => {
1694
1663
 
1695
1664
  const setUpdateRcsImageSrc = useCallback(
1696
1665
  (val) => {
1666
+ setAssetListImage(val);
1697
1667
  updateRcsImageSrc(val);
1698
1668
  actions.clearRcsMediaAsset(0);
1699
1669
  },
@@ -1723,6 +1693,7 @@ const onTitleAddVar = () => {
1723
1693
 
1724
1694
 
1725
1695
  const uploadRcsVideo = (file, type, fileParams) => {
1696
+ setImageError(null);
1726
1697
  actions.uploadRcsAsset(file, type, {
1727
1698
  ...fileParams,
1728
1699
  type: 'video',
@@ -1731,6 +1702,9 @@ const onTitleAddVar = () => {
1731
1702
  });
1732
1703
  };
1733
1704
 
1705
+ const updateRcsVideoSrc = (val) => {
1706
+ setRcsVideoSrc(val);
1707
+ };
1734
1708
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
1735
1709
  setRcsVideoSrc(val);
1736
1710
  setAssetList(val);
@@ -1759,7 +1733,7 @@ const onTitleAddVar = () => {
1759
1733
  <>
1760
1734
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1761
1735
  <CapImageUpload
1762
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1736
+ style={{ paddingTop: '20px' }}
1763
1737
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1764
1738
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1765
1739
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1771,6 +1745,7 @@ const onTitleAddVar = () => {
1771
1745
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1772
1746
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1773
1747
  index={1}
1748
+ className="cap-custom-image-upload"
1774
1749
  key={`rcs-uploaded-image-${currentDimension}`}
1775
1750
  imageData={thumbnailData}
1776
1751
  channel={RCS}
@@ -1801,7 +1776,7 @@ const onTitleAddVar = () => {
1801
1776
  value: dim.type,
1802
1777
  label: `${dim.label}`
1803
1778
  }))}
1804
- className="rcs-dimension-select--bottom-spacing"
1779
+ style={{ marginBottom: '20px' }}
1805
1780
  />
1806
1781
  </>
1807
1782
  )}
@@ -1815,6 +1790,7 @@ const onTitleAddVar = () => {
1815
1790
  </div>
1816
1791
  ) : (
1817
1792
  <CapImageUpload
1793
+ style={{ paddingTop: '20px' }}
1818
1794
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1819
1795
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1820
1796
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1825,7 +1801,7 @@ const onTitleAddVar = () => {
1825
1801
  updateImageSrc={setUpdateRcsImageSrc}
1826
1802
  updateOnReUpload={updateOnRcsImageReUpload}
1827
1803
  index={0}
1828
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1804
+ className="cap-custom-image-upload"
1829
1805
  key={`rcs-uploaded-image-${selectedDimension}`}
1830
1806
  imageData={rcsData}
1831
1807
  channel={RCS}
@@ -1855,7 +1831,7 @@ const onTitleAddVar = () => {
1855
1831
  value: dim.type,
1856
1832
  label: `${dim.label}`
1857
1833
  }))}
1858
- className="rcs-dimension-select--bottom-spacing"
1834
+ style={{ marginBottom: '20px' }}
1859
1835
  />
1860
1836
  )}
1861
1837
  {(isEditFlow || !isFullMode) ? (
@@ -1915,16 +1891,8 @@ const onTitleAddVar = () => {
1915
1891
  const getRcsPreview = () => {
1916
1892
 
1917
1893
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
1918
- const isSlotMappingMode = isEditFlow || !isFullMode;
1919
- const titleVarCountForResolve = isMediaTypeText
1920
- ? 0
1921
- : ((templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []).length);
1922
- const resolvedTitle = isMediaTypeText
1923
- ? ''
1924
- : (isSlotMappingMode ? resolveTemplateWithMap(templateTitle, 0) : templateTitle);
1925
- const resolvedDesc = isSlotMappingMode
1926
- ? resolveTemplateWithMap(templateDesc, titleVarCountForResolve)
1927
- : templateDesc;
1894
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1895
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1928
1896
  return (
1929
1897
  <UnifiedPreview
1930
1898
  channel={RCS}
@@ -1947,65 +1915,51 @@ const onTitleAddVar = () => {
1947
1915
  );
1948
1916
  };
1949
1917
 
1918
+ const getUnmappedDesc = (str, mapping) => {
1919
+ if (!str) return '';
1920
+ if (!mapping || Object.keys(mapping).length === 0) return str;
1921
+ let result = str;
1922
+ const replacements = [];
1923
+ Object.entries(mapping).forEach(([key, value]) => {
1924
+ const raw = (value ?? '').toString();
1925
+ if (!raw || raw?.trim?.() === '') return;
1926
+ const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1927
+ replacements.push({ key, needle: raw });
1928
+ if (braced !== raw) replacements.push({ key, needle: braced });
1929
+ });
1930
+ const seen = new Set();
1931
+ const uniq = replacements
1932
+ .filter(({ key, needle }) => {
1933
+ const id = `${key}::${needle}`;
1934
+ if (seen.has(id)) return false;
1935
+ seen.add(id);
1936
+ return true;
1937
+ })
1938
+ .sort((a, b) => (b.needle.length - a.needle.length));
1939
+
1940
+ uniq.forEach(({ key, needle }) => {
1941
+ if (!needle) return;
1942
+ const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1943
+ const regex = new RegExp(escaped, 'g');
1944
+ result = result.replace(regex, `{{${key}}}`);
1945
+ });
1946
+ return result;
1947
+ };
1948
+
1950
1949
  const createPayload = () => {
1951
- const isSlotMappingMode = isEditFlow || !isFullMode;
1950
+ const base = get(dltEditData, `versions.base`, {});
1951
+ const {
1952
+ template_id: templateId = '',
1953
+ template_name = '',
1954
+ 'sms-editor': template = '',
1955
+ header: registeredSenderIds = [],
1956
+ } = base;
1957
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1958
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1952
1959
  const alignment = isMediaTypeImage
1953
1960
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
1954
1961
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
1955
1962
 
1956
- const heightTypeForCardWidth = isMediaTypeText
1957
- ? undefined
1958
- : isMediaTypeImage
1959
- ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType
1960
- : isMediaTypeVideo
1961
- ? RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType
1962
- : undefined;
1963
- const cardWidthFromSelection =
1964
- heightTypeForCardWidth === MEDIUM ? MEDIUM : SMALL;
1965
-
1966
- /** Library: merge props + state so SMS fallback is not dropped when local state is empty but templateData has consumer data. */
1967
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
1968
- const smsFallbackMerged = !isFullMode
1969
- ? (() => {
1970
- const local =
1971
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
1972
- return {
1973
- ...smsFromApiShape,
1974
- ...local,
1975
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
1976
- };
1977
- })()
1978
- : (smsFallbackData || {});
1979
- const smsFallbackForPayload = (() => {
1980
- if (isFullMode) {
1981
- return hasMeaningfulSmsFallbackShape(smsFallbackData) ? smsFallbackData : null;
1982
- }
1983
- const mapped = {
1984
- templateName:
1985
- smsFallbackMerged.templateName
1986
- || smsFallbackMerged.smsTemplateName
1987
- || '',
1988
- // Use `||` so empty `content` does not block campaign/API `message` (common in embedded flows).
1989
- content:
1990
- smsFallbackMerged.content
1991
- || smsFallbackMerged.smsContent
1992
- || smsFallbackMerged.smsTemplateContent
1993
- || smsFallbackMerged.message
1994
- || '',
1995
- templateContent:
1996
- pickFirstSmsFallbackTemplateString(smsFallbackMerged)
1997
- || '',
1998
- ...(typeof smsFallbackMerged.unicodeValidity === 'boolean'
1999
- && { unicodeValidity: smsFallbackMerged.unicodeValidity }),
2000
- ...(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]
2001
- && Object.keys(smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP]).length > 0 && {
2002
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]:
2003
- smsFallbackMerged[RCS_SMS_FALLBACK_VAR_MAPPED_PROP],
2004
- }),
2005
- };
2006
- return hasMeaningfulSmsFallbackShape(mapped) ? mapped : null;
2007
- })();
2008
-
2009
1963
  const payload = {
2010
1964
  name: templateName,
2011
1965
  versions: {
@@ -2013,18 +1967,17 @@ const onTitleAddVar = () => {
2013
1967
  content: {
2014
1968
  RCS: {
2015
1969
  rcsContent: {
1970
+ ...(rcsAccount && !isFullMode && { accountId: rcsAccount }),
2016
1971
  cardType: STANDALONE,
2017
1972
  cardSettings: {
2018
1973
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
2019
1974
  ...(alignment && { mediaAlignment: alignment }),
2020
- cardWidth: cardWidthFromSelection,
1975
+ cardWidth: SMALL,
2021
1976
  },
2022
1977
  cardContent: [
2023
1978
  {
2024
- // Persist raw template copy + cardVarMapped — not resolveTemplateWithMap output — so library
2025
- // / getFormData round-trip keeps {{…}} and slot values (resolved strings broke reopen hydration).
2026
- title: templateTitle,
2027
- description: templateDesc,
1979
+ title: resolvedTitle,
1980
+ description: resolvedDesc,
2028
1981
  mediaType: templateMediaType,
2029
1982
  ...(!isMediaTypeText && {media: {
2030
1983
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -2033,32 +1986,23 @@ const onTitleAddVar = () => {
2033
1986
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
2034
1987
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
2035
1988
  }}),
2036
- ...(isSlotMappingMode && (() => {
2037
- const templateVarTokens = [
2038
- ...(templateTitle?.match(rcsVarRegex) ?? []),
2039
- ...(templateDesc?.match(rcsVarRegex) ?? []),
1989
+ ...(!isFullMode && (() => {
1990
+ const tokens = [
1991
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1992
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
2040
1993
  ];
2041
- const cardVarMappedForRcsCardOnly = pickRcsCardVarMappedEntries(
2042
- cardVarMapped,
2043
- );
2044
- // Persist numeric slot keys only ("1","2",…) — avoids duplicating the same value under
2045
- // semantic names (e.g. both "1" and dynamic_expiry_date_after_3_days.FORMAT_1). Hydration
2046
- // and coalesceCardVarMappedToTemplate still resolve from numeric keys + template tokens.
2047
- const persistedSlotVarMap = {};
2048
- templateVarTokens.forEach((token, slotIndexZeroBased) => {
2049
- const varName = getVarNameFromToken(token);
2050
- if (!varName) return;
2051
- const resolvedRawValue = resolveCardVarMappedSlotValue(
2052
- cardVarMappedForRcsCardOnly,
2053
- varName,
2054
- slotIndexZeroBased,
2055
- isSlotMappingMode,
2056
- rcsSpanningSemanticVarNames.has(varName),
2057
- );
2058
- const sanitizedSlotValue = sanitizeCardVarMappedValue(resolvedRawValue);
2059
- persistedSlotVarMap[String(slotIndexZeroBased + 1)] = sanitizedSlotValue;
1994
+ const allowedKeys = tokens
1995
+ .map((t) => getVarNameFromToken(t))
1996
+ .filter(Boolean);
1997
+ const nextMap = {};
1998
+ allowedKeys.forEach((k) => {
1999
+ if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
2000
+ nextMap[k] = cardVarMapped[k];
2001
+ } else {
2002
+ nextMap[k] = '';
2003
+ }
2060
2004
  });
2061
- return { cardVarMapped: persistedSlotVarMap };
2005
+ return { cardVarMapped: nextMap };
2062
2006
  })()),
2063
2007
  ...(suggestions.length > 0 && { suggestions }),
2064
2008
  }
@@ -2066,79 +2010,17 @@ const onTitleAddVar = () => {
2066
2010
  contentType: isFullMode ? templateType : RICHCARD,
2067
2011
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2068
2012
  },
2069
- ...(smsFallbackForPayload && (() => {
2070
- const smsBodyText =
2071
- smsFallbackForPayload.content
2072
- || smsFallbackForPayload.templateContent
2073
- || smsFallbackForPayload.message
2074
- || smsFallbackForPayload.smsContent
2075
- || '';
2076
- /**
2077
- * Campaigns `getTraiSenderIds` / Iris read `smsFallBackContent.templateConfigs.registeredSenderIds`.
2078
- * Library `smsFallbackForPayload` omits ids — use merged state (`smsFallbackMerged`) like test preview.
2079
- */
2080
- const m = smsFallbackMerged || {};
2081
- const tcSibling = m.templateConfigs && typeof m.templateConfigs === 'object'
2082
- ? m.templateConfigs
2083
- : {};
2084
- const smsFallbackTemplateId =
2085
- (m.smsTemplateId != null && String(m.smsTemplateId).trim() !== ''
2086
- ? String(m.smsTemplateId)
2087
- : '')
2088
- || (tcSibling.templateId != null && String(tcSibling.templateId).trim() !== ''
2089
- ? String(tcSibling.templateId)
2090
- : '');
2091
- const smsFallbackTemplateStr =
2092
- pickFirstSmsFallbackTemplateString(m)
2093
- || (typeof m.templateContent === 'string' ? m.templateContent : '')
2094
- || (typeof tcSibling.template === 'string' ? tcSibling.template : '')
2095
- || '';
2096
- const smsFallbackTemplateName =
2097
- m.templateName
2098
- || m.smsTemplateName
2099
- || tcSibling.templateName
2100
- || tcSibling.name
2101
- || '';
2102
- const registeredSenderIdsForPayload = Array.isArray(m.registeredSenderIds)
2103
- ? m.registeredSenderIds
2104
- : Array.isArray(tcSibling.registeredSenderIds)
2105
- ? tcSibling.registeredSenderIds
2106
- : Array.isArray(tcSibling.header)
2107
- ? tcSibling.header
2108
- : null;
2109
- const hasRegisteredSenderIds = Array.isArray(registeredSenderIdsForPayload);
2110
- const smsFallbackTemplateConfigs =
2111
- smsFallbackTemplateId || hasRegisteredSenderIds
2112
- ? {
2113
- ...(smsFallbackTemplateId && { templateId: smsFallbackTemplateId }),
2114
- ...(smsFallbackTemplateStr && { template: smsFallbackTemplateStr }),
2115
- ...(smsFallbackTemplateName && {
2116
- templateName: smsFallbackTemplateName,
2117
- }),
2118
- ...(hasRegisteredSenderIds && {
2119
- registeredSenderIds: registeredSenderIdsForPayload,
2120
- }),
2121
- }
2122
- : null;
2123
- return {
2124
- smsFallBackContent: {
2125
- smsTemplateName: smsFallbackForPayload.templateName || '',
2126
- smsContent: smsBodyText,
2127
- // cap-campaigns-v2 `normalizeRcsMessageContentForApi` only serializes `message` (+ templateConfigs); without this key SMS fallback is dropped on send.
2128
- message: smsBodyText,
2129
- ...(typeof smsFallbackForPayload.unicodeValidity === 'boolean' && {
2130
- unicodeValidity: smsFallbackForPayload.unicodeValidity,
2131
- }),
2132
- ...(smsFallbackForPayload.rcsSmsFallbackVarMapped
2133
- && Object.keys(smsFallbackForPayload.rcsSmsFallbackVarMapped).length > 0 && {
2134
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: smsFallbackForPayload.rcsSmsFallbackVarMapped,
2135
- }),
2136
- ...(smsFallbackTemplateConfigs && {
2137
- templateConfigs: smsFallbackTemplateConfigs,
2138
- }),
2013
+ smsFallBackContent: {
2014
+ message: fallbackMessage,
2015
+ ...(isDltEnabled && {
2016
+ templateConfigs: {
2017
+ templateId,
2018
+ templateName: template_name,
2019
+ template,
2020
+ registeredSenderIds,
2139
2021
  },
2140
- };
2141
- })()),
2022
+ }),
2023
+ },
2142
2024
  },
2143
2025
  },
2144
2026
  },
@@ -2148,107 +2030,6 @@ const onTitleAddVar = () => {
2148
2030
  return payload;
2149
2031
  };
2150
2032
 
2151
- /** Shape expected by CommonTestAndPreview buildRcsTestMessagePayload (versions.base.content.RCS). */
2152
- const testPreviewFormData = useMemo(() => {
2153
- const payload = createPayload();
2154
- const rcs = payload?.versions?.base?.content?.RCS;
2155
- if (!rcs) return null;
2156
- // createMessageMeta uses WeCRM `id` when present; else template API account id (sourceAccountIdentifier).
2157
- const accountIdForCreateMessageMeta =
2158
- (wecrmAccountId != null && String(wecrmAccountId).trim() !== '')
2159
- ? String(wecrmAccountId)
2160
- : accountId;
2161
- const isSlotMappingModeForPreview = isEditFlow || !isFullMode;
2162
- let rcsForTest = {
2163
- ...rcs,
2164
- rcsContent: {
2165
- ...rcs.rcsContent,
2166
- ...(accountIdForCreateMessageMeta ? { accountId: accountIdForCreateMessageMeta } : {}),
2167
- },
2168
- };
2169
- /** Approval payload keeps numeric-only `cardVarMapped`; preview APIs still need semantic keys. */
2170
- if (isSlotMappingModeForPreview) {
2171
- const cardContent = rcsForTest.rcsContent?.cardContent;
2172
- if (Array.isArray(cardContent) && cardContent[0]) {
2173
- const fullCardVarMapped = coalesceCardVarMappedToTemplate(
2174
- pickRcsCardVarMappedEntries(cardVarMapped),
2175
- templateTitle,
2176
- templateDesc,
2177
- rcsVarRegex,
2178
- );
2179
- rcsForTest = {
2180
- ...rcsForTest,
2181
- rcsContent: {
2182
- ...rcsForTest.rcsContent,
2183
- cardContent: [
2184
- { ...cardContent[0], cardVarMapped: fullCardVarMapped },
2185
- ...cardContent.slice(1),
2186
- ],
2187
- },
2188
- };
2189
- }
2190
- }
2191
- const out = {
2192
- versions: {
2193
- base: {
2194
- content: {
2195
- RCS: rcsForTest,
2196
- },
2197
- },
2198
- },
2199
- };
2200
- const fb = smsFallbackData;
2201
- if (fb && (fb.smsTemplateId || fb.templateContent || fb.content)) {
2202
- out.templateConfigs = {
2203
- templateId: fb.smsTemplateId || '',
2204
- template: fb.templateContent || fb.content || '',
2205
- traiDltEnabled: isTraiDLTEnable(isFullMode, smsRegister),
2206
- registeredSenderIds: Array.isArray(fb.registeredSenderIds) ? fb.registeredSenderIds : [],
2207
- };
2208
- }
2209
- return out;
2210
- }, [
2211
- templateName,
2212
- templateTitle,
2213
- templateDesc,
2214
- templateMediaType,
2215
- cardVarMapped,
2216
- suggestions,
2217
- rcsImageSrc,
2218
- rcsVideoSrc,
2219
- rcsThumbnailSrc,
2220
- selectedDimension,
2221
- smsFallbackData,
2222
- isFullMode,
2223
- isEditFlow,
2224
- templateType,
2225
- accountId,
2226
- wecrmAccountId,
2227
- accessToken,
2228
- accountName,
2229
- hostName,
2230
- smsRegister,
2231
- ]);
2232
-
2233
- /**
2234
- * Library/campaign: `createPayload` merges root + nested `smsFallBackContent` from `templateData`
2235
- * with `smsFallbackData`. Done/slot validation must use the same merge — otherwise local state can
2236
- * miss `templateContent` / var map while the parent payload still has them (DLT campaigns).
2237
- */
2238
- const librarySmsFallbackMergedForValidation = useMemo(() => {
2239
- if (isFullMode) {
2240
- return smsFallbackData;
2241
- }
2242
- const smsFromApiShape = getLibrarySmsFallbackApiBaselineFromTemplateData(templateData);
2243
- const local =
2244
- smsFallbackData && typeof smsFallbackData === 'object' ? smsFallbackData : {};
2245
- return {
2246
- ...smsFromApiShape,
2247
- ...local,
2248
- rcsSmsFallbackVarMapped: mergeRcsSmsFallbackVarMapLayers(smsFromApiShape, local),
2249
- };
2250
- }, [isFullMode, templateData, smsFallbackData]);
2251
-
2252
2033
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2253
2034
  // eslint-disable-next-line no-undef
2254
2035
  const error = errorMessage?.message || errorMessage;
@@ -2278,9 +2059,6 @@ const onTitleAddVar = () => {
2278
2059
  _id: params?.id,
2279
2060
  validity: true,
2280
2061
  type: RCS,
2281
- // CreativesContainer closes the slide box *after* getCreativesData runs so the parent receives
2282
- // the RCS payload first (closing immediately used to skip getCreativesData → empty "Add creative").
2283
- closeSlideBoxAfterSubmit: !isFullMode,
2284
2062
  };
2285
2063
  getFormData(formDataParams);
2286
2064
  };
@@ -2294,7 +2072,6 @@ const onTitleAddVar = () => {
2294
2072
  actionCallback({ resp, errorMessage });
2295
2073
  setSpin(false); // Always turn off spinner
2296
2074
  if (!errorMessage) {
2297
- setTemplateStatus(RCS_STATUSES.pending);
2298
2075
  onCreateComplete();
2299
2076
  }
2300
2077
  });
@@ -2306,70 +2083,6 @@ const onTitleAddVar = () => {
2306
2083
  }
2307
2084
  };
2308
2085
 
2309
- /** When a fallback SMS row exists, require non-empty body (trimmed) and filled var slots (DLT). */
2310
- const smsFallbackBlocksDone = () => {
2311
- // Non-DLT library: user removed SMS fallback (local null) but template still carries fallback — block Done.
2312
- if (
2313
- !isFullMode
2314
- && !isTraiDLTEnable(isFullMode, smsRegister)
2315
- && smsFallbackData == null
2316
- && hasMeaningfulSmsFallbackShape(
2317
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
2318
- )
2319
- ) {
2320
- return true;
2321
- }
2322
- if (!smsFallbackData) return false;
2323
- // Full-mode (Send for approval): SMS fallback is optional. Tag-slot mapping is a display/preview
2324
- // concern, not a structural requirement for approval — the registered SMS template body stands on
2325
- // its own. Never block the Send for approval button due to missing or unfilled fallback var slots.
2326
- if (isFullMode) return false;
2327
- const merged = librarySmsFallbackMergedForValidation;
2328
- const templateText = pickFirstSmsFallbackTemplateString(merged);
2329
- if (!templateText) {
2330
- return true;
2331
- }
2332
- const rawVarMap =
2333
- merged.rcsSmsFallbackVarMapped
2334
- || merged['rcs-sms-fallback-var-mapped'];
2335
- const varMap =
2336
- rawVarMap != null && typeof rawVarMap === 'object' ? rawVarMap : {};
2337
- return !areAllRcsSmsFallbackVarSlotsFilled(templateText, varMap);
2338
- };
2339
-
2340
- /**
2341
- * Library / campaigns (`!isFullMode`): card slots are often stored on numeric keys (`1`,`2`,…) while
2342
- * semantic keys stay `""` from API round-trip. `resolveCardVarMappedSlotValue` matches createPayload
2343
- * / preview — naive `cardVarMapped[name]` wrongly kept Done disabled for DLT.
2344
- */
2345
- const isLibraryCampaignCardVarMappingIncomplete = () => {
2346
- if (isFullMode) return false;
2347
- const titleTokens = splitTemplateVarStringRcs(templateTitle).filter((elem) =>
2348
- rcsVarTestRegex.test(elem),
2349
- );
2350
- const descTokens = splitTemplateVarStringRcs(templateDesc).filter((elem) =>
2351
- rcsVarTestRegex.test(elem),
2352
- );
2353
- const orderedVarNames = [
2354
- ...titleTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2355
- ...descTokens.map((t) => t.replace(/^\{\{|\}\}$/g, '')),
2356
- ];
2357
- if (orderedVarNames.length > 0 && isEmpty(cardVarMapped)) {
2358
- return true;
2359
- }
2360
- return orderedVarNames.some((name, globalIdx) => {
2361
- const v = resolveCardVarMappedSlotValue(
2362
- cardVarMapped,
2363
- name,
2364
- globalIdx,
2365
- true,
2366
- rcsSpanningSemanticVarNames.has(name),
2367
- );
2368
- const s = v == null ? '' : String(v);
2369
- return s.trim() === '';
2370
- });
2371
- };
2372
-
2373
2086
  const isDisableDone = () => {
2374
2087
  if(isEditFlow){
2375
2088
  return false;
@@ -2380,16 +2093,40 @@ const onTitleAddVar = () => {
2380
2093
  }
2381
2094
  }
2382
2095
 
2383
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2384
- return true;
2385
- }
2096
+ if(!isFullMode){
2097
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2098
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2099
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2386
2100
 
2387
- if (smsFallbackBlocksDone()) {
2388
- return true;
2101
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2102
+ return true;
2103
+ }
2104
+
2105
+ const hasEmptyMapping =
2106
+ cardVarMapped &&
2107
+ Object.keys(cardVarMapped).length > 0 &&
2108
+ Object.entries(cardVarMapped).some(([_, v]) => {
2109
+ if (typeof v !== 'string') return !v; // null/undefined
2110
+ return v.trim() === ''; // empty string
2111
+ });
2112
+
2113
+ if (hasEmptyMapping) {
2114
+ return true;
2115
+ }
2116
+
2117
+ const anyMissing = allVars.some(name => {
2118
+ const v = cardVarMapped?.[name];
2119
+ if (typeof v !== 'string') return !v;
2120
+ return v.trim() === '';
2121
+ });
2122
+ if (anyMissing) {
2123
+ return true;
2124
+ }
2389
2125
  }
2390
2126
 
2391
- if (isMediaTypeText && templateDesc.trim() === '') {
2127
+ if (isMediaTypeText && templateDesc.trim() === '') {
2392
2128
  return true;
2129
+
2393
2130
  }
2394
2131
  if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2395
2132
  return true;
@@ -2407,36 +2144,53 @@ const onTitleAddVar = () => {
2407
2144
  return true;
2408
2145
  }
2409
2146
  }
2410
- if (templateDescError || templateTitleError) {
2411
- return true;
2412
- }
2413
- if (
2414
- smsFallbackData?.content
2415
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2416
- ) {
2147
+ if (templateDescError || templateTitleError || fallbackMessageError) {
2417
2148
  return true;
2418
2149
  }
2419
2150
  return false;
2420
2151
  };
2421
2152
 
2422
2153
  const isEditDisableDone = () => {
2154
+
2423
2155
  if (templateStatus !== RCS_STATUSES.approved) {
2424
2156
  return true;
2425
2157
  }
2426
2158
 
2427
- // if (!isFullMode) {
2428
- // if (templateName.trim() === '' || templateNameError) {
2429
- // return true;
2430
- // }
2431
- // }
2432
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2433
- return true;
2159
+ if (!isFullMode) {
2160
+ if (templateName.trim() === '' || templateNameError) {
2161
+ return true;
2162
+ }
2434
2163
  }
2164
+ if(!isFullMode){
2165
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2166
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2167
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2435
2168
 
2436
- if (smsFallbackBlocksDone()) {
2437
- return true;
2438
- }
2169
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2170
+ return true;
2171
+ }
2439
2172
 
2173
+ const hasEmptyMapping =
2174
+ cardVarMapped &&
2175
+ Object.keys(cardVarMapped).length > 0 &&
2176
+ Object.entries(cardVarMapped).some(([_, v]) => {
2177
+ if (typeof v !== 'string') return !v; // null/undefined
2178
+ return v.trim() === ''; // empty string
2179
+ });
2180
+
2181
+ if (hasEmptyMapping) {
2182
+ return true;
2183
+ }
2184
+
2185
+ const anyMissing = allVars.some(name => {
2186
+ const v = cardVarMapped?.[name];
2187
+ if (typeof v !== 'string') return !v;
2188
+ return v.trim() === '';
2189
+ });
2190
+ if (anyMissing) {
2191
+ return true;
2192
+ }
2193
+ }
2440
2194
  if (isMediaTypeText && templateDesc.trim() === '') {
2441
2195
  return true;
2442
2196
  }
@@ -2455,13 +2209,7 @@ const onTitleAddVar = () => {
2455
2209
  return true;
2456
2210
  }
2457
2211
  }
2458
- if (templateTitleError || templateDescError) {
2459
- return true;
2460
- }
2461
- if (
2462
- smsFallbackData?.content
2463
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2464
- ) {
2212
+ if (templateTitleError || templateDescError || fallbackMessageError) {
2465
2213
  return true;
2466
2214
  }
2467
2215
  return false;
@@ -2511,56 +2259,52 @@ const onTitleAddVar = () => {
2511
2259
  };
2512
2260
 
2513
2261
  const getMainContent = () => {
2514
- // Slideboxes are rendered outside the page-level spinner to avoid
2515
- // stacking/blur issues during initial loads.
2516
- if (showDltContainer) return null;
2262
+ if (showDltContainer && !fallbackPreviewmode) {
2263
+ const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2264
+ const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2265
+ return (
2266
+ <CapSlideBox
2267
+ show={showDltContainer}
2268
+ header={dltHeader}
2269
+ content={dltContent}
2270
+ handleClose={closeDltContainerHandler}
2271
+ size="size-xl"
2272
+ />
2273
+ );
2274
+ }
2517
2275
 
2518
2276
  return (
2519
2277
  <>
2520
- {templateStatus !== '' && (
2521
- <CapRow className="template-status-container">
2522
- <CapColumn span={14}>
2523
- <CapLabel type="label2">
2524
- {formatMessage(messages.templateStatusLabel)}
2525
- </CapLabel>
2526
-
2527
- {templateStatus && (
2528
- <CapAlert
2529
- message={getTemplateStatusMessage()}
2530
- type={getTemplateStatusType(templateStatus)}
2531
- />
2532
- )}
2533
- </CapColumn>
2534
- </CapRow>
2278
+ {templateStatus !== '' && (<CapRow className="template-status-container">
2279
+ <CapLabel type="label2">
2280
+ {formatMessage(messages.templateStatusLabel)}
2281
+ </CapLabel>
2282
+
2283
+ {templateStatus && (
2284
+ <CapAlert
2285
+ message={getTemplateStatusMessage()}
2286
+ type={getTemplateStatusType(templateStatus)}
2287
+ />
2288
+ )}
2289
+ </CapRow>
2535
2290
  )}
2536
- <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2291
+ <CapRow className="cap-rcs-creatives">
2537
2292
  <CapColumn span={14}>
2538
2293
  {/* template name */}
2539
2294
  {isFullMode && (
2540
- isEditFlow ? (
2541
- <div className="rcs-creative-name-readonly">
2542
- <CapHeading type="h4">
2543
- {formatMessage(globalMessages.creativeNameLabel)}
2544
- </CapHeading>
2545
- <CapHeading type="h5" className="rcs-creative-name-value">
2546
- {templateName || '-'}
2547
- </CapHeading>
2548
- </div>
2549
- ) : (
2550
- <CapInput
2551
- id="rcs_template_name_input"
2552
- data-testid="template_name"
2553
- onChange={onTemplateNameChange}
2554
- errorMessage={templateNameError}
2555
- placeholder={formatMessage(
2556
- globalMessages.templateNamePlaceholder,
2557
- )}
2558
- value={templateName || ''}
2559
- size="default"
2560
- label={formatMessage(globalMessages.creativeNameLabel)}
2561
- disabled={(isEditFlow || !isFullMode)}
2562
- />
2563
- )
2295
+ <CapInput
2296
+ id="rcs_template_name_input"
2297
+ data-testid="template_name"
2298
+ onChange={onTemplateNameChange}
2299
+ errorMessage={templateNameError}
2300
+ placeholder={formatMessage(
2301
+ globalMessages.templateNamePlaceholder,
2302
+ )}
2303
+ value={templateName || ''}
2304
+ size="default"
2305
+ label={formatMessage(globalMessages.creativeNameLabel)}
2306
+ disabled={(isEditFlow || !isFullMode)}
2307
+ />
2564
2308
  )}
2565
2309
  {renderLabel('templateTypeLabel')}
2566
2310
  <CapRadioGroup
@@ -2588,7 +2332,7 @@ const onTitleAddVar = () => {
2588
2332
  </>
2589
2333
  )}
2590
2334
  {renderTextComponent()}
2591
- <CapDivider className="rcs-fallback-section-divider" />
2335
+ <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2592
2336
  {renderFallBackSmsComponent()}
2593
2337
  <div className="rcs-scroll-div" />
2594
2338
  </CapColumn>
@@ -2600,8 +2344,7 @@ const onTitleAddVar = () => {
2600
2344
 
2601
2345
 
2602
2346
  <div className="rcs-footer">
2603
- {/* 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). */}
2604
- {!isEditFlow && isFullMode && (
2347
+ {!isEditFlow && (
2605
2348
  <>
2606
2349
  <div className="button-disabled-tooltip-wrapper">
2607
2350
  <CapButton
@@ -2622,6 +2365,7 @@ const onTitleAddVar = () => {
2622
2365
  className="rcs-test-preview-btn"
2623
2366
  type="secondary"
2624
2367
  disabled={true}
2368
+ style={{ marginLeft: "8px" }}
2625
2369
  >
2626
2370
  <FormattedMessage {...creativesMessages.testAndPreview} />
2627
2371
  </CapButton>
@@ -2654,6 +2398,51 @@ const onTitleAddVar = () => {
2654
2398
  </>
2655
2399
  )}
2656
2400
  </div>
2401
+
2402
+
2403
+ {fallbackPreviewmode && (
2404
+ <CapSlideBox
2405
+ className="rcs-fallback-preview"
2406
+ show={fallbackPreviewmode}
2407
+ header={(
2408
+ <CapHeading type="h7" style={{ color: CAP_G01 }}>
2409
+ {formatMessage(messages.fallbackPreviewtitle)}
2410
+ </CapHeading>
2411
+ )}
2412
+ content={(
2413
+ <>
2414
+ <UnifiedPreview
2415
+ channel={RCS}
2416
+ content={{
2417
+ rcsPreviewContent: {
2418
+ rcsDesc: tempMsg,
2419
+ },
2420
+ }}
2421
+ device={ANDROID}
2422
+ showDeviceToggle={false}
2423
+ showHeader={false}
2424
+ formatMessage={formatMessage}
2425
+ />
2426
+ <CapHeading
2427
+ type="h3"
2428
+ style={{ textAlign: 'center' }}
2429
+ className="margin-t-16"
2430
+ >
2431
+ {formatMessage(messages.totalCharacters, {
2432
+ smsCount: Math.ceil(
2433
+ tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2434
+ ),
2435
+ number: tempMsg.length,
2436
+ })}
2437
+ </CapHeading>
2438
+ </>
2439
+ )}
2440
+ handleClose={() => {
2441
+ setFallbackPreviewmode(false);
2442
+ setDltPreviewData('');
2443
+ }}
2444
+ />
2445
+ )}
2657
2446
  </>
2658
2447
  );
2659
2448
  };
@@ -2662,44 +2451,12 @@ const onTitleAddVar = () => {
2662
2451
  <CapSpin spinning={loadingTags || spin}>
2663
2452
  {getMainContent()}
2664
2453
  </CapSpin>
2665
-
2666
- {showDltContainer && (() => {
2667
- const { dltHeader = '', dltContent = '' } = getDltSlideBoxContent() || {};
2668
- return (
2669
- <CapSlideBox
2670
- show={showDltContainer}
2671
- header={dltHeader}
2672
- content={dltContent}
2673
- handleClose={closeDltContainerHandler}
2674
- size="size-xl"
2675
- />
2676
- );
2677
- })()}
2678
-
2679
2454
  <TestAndPreviewSlidebox
2680
2455
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
2681
2456
  onClose={handleCloseTestAndPreview}
2682
- formData={testPreviewFormData}
2683
- content={testAndPreviewContent}
2457
+ formData={null} // RCS doesn't use formData structure like SMS
2458
+ content={getTemplateContent()}
2684
2459
  currentChannel={RCS}
2685
- orgUnitId={orgUnitId}
2686
- rcsTestPreviewOptions={{ isLibraryMode: !isFullMode }}
2687
- smsFallbackContent={
2688
- smsFallbackData && (smsFallbackData.templateContent || smsFallbackData.content)
2689
- ? {
2690
- templateContent:
2691
- smsFallbackData.templateContent || smsFallbackData.content || '',
2692
- templateName: smsFallbackData.templateName || '',
2693
- [RCS_SMS_FALLBACK_VAR_MAPPED_PROP]: !isFullMode
2694
- ? mergeRcsSmsFallbackVarMapLayers(
2695
- getLibrarySmsFallbackApiBaselineFromTemplateData(templateData),
2696
- smsFallbackData,
2697
- )
2698
- : mergeRcsSmsFallbackVarMapLayers({}, smsFallbackData),
2699
- }
2700
- : null
2701
- }
2702
- smsRegister={smsRegister}
2703
2460
  />
2704
2461
  </>
2705
2462
  );