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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/constants/unified.js +0 -18
  2. package/package.json +1 -1
  3. package/services/api.js +0 -17
  4. package/services/tests/api.test.js +0 -85
  5. package/utils/commonUtils.js +0 -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 +93 -292
  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 +895 -1145
  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,28 +280,23 @@ 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('');
@@ -268,23 +304,14 @@ export const Rcs = (props) => {
268
304
  const accountObj = accountData.selectedRcsAccount || {};
269
305
  if (!isEmpty(accountObj)) {
270
306
  const {
271
- id: wecrmId,
272
307
  sourceAccountIdentifier = '',
273
308
  configs = {},
274
309
  } = accountObj;
310
+
275
311
  setAccountId(sourceAccountIdentifier);
276
- setWecrmAccountId(
277
- wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
278
- );
279
312
  setAccessToken(configs.accessToken || '');
280
313
  setHostName(accountObj.hostName || '');
281
314
  setAccountName(accountObj.name || '');
282
- } else {
283
- setAccountId('');
284
- setWecrmAccountId('');
285
- setAccessToken('');
286
- setHostName('');
287
- setAccountName('');
288
315
  }
289
316
  }, [accountData.selectedRcsAccount]);
290
317
 
@@ -340,9 +367,7 @@ export const Rcs = (props) => {
340
367
  if (isFullMode) return;
341
368
  if (loadingTags || !tags || tags.length === 0) return;
342
369
  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 '')
370
+ const resolved = resolveTemplateWithMap(templateStr); // placeholders -> mapped value (or '')
346
371
  if (!resolved) {
347
372
  if (type === TITLE_TEXT) setTemplateTitleError(false);
348
373
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -387,48 +412,13 @@ export const Rcs = (props) => {
387
412
 
388
413
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
389
414
 
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) => {
415
+ const resolveTemplateWithMap = (str = '') => {
417
416
  if (!str) return '';
418
- const arr = splitTemplateVarStringRcs(str);
419
- let varOrdinal = 0;
417
+ const arr = splitTemplateVarString(str);
420
418
  return arr.map((elem) => {
421
419
  if (rcsVarTestRegex.test(elem)) {
422
420
  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
- );
421
+ const v = cardVarMapped?.[key];
432
422
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
433
423
  return String(v);
434
424
  }
@@ -436,156 +426,107 @@ export const Rcs = (props) => {
436
426
  }).join('');
437
427
  };
438
428
 
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
429
 
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
- ]);
430
+ useEffect(() => {
431
+ if (isFullMode || isEditFlow) return;
432
+ const tokens = [
433
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
434
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
435
+ ];
436
+ if (!tokens.length) return;
437
+ setCardVarMapped((prev) => {
438
+ const next = { ...(prev || {}) };
439
+ let changed = false;
440
+ tokens.forEach((t) => {
441
+ const name = getVarNameFromToken(t);
442
+ if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
443
+ next[name] = '';
444
+ changed = true;
445
+ }
446
+ });
447
+ return changed ? next : prev;
448
+ });
449
+ }, [isFullMode, templateTitle, templateDesc]);
499
450
 
500
- const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
501
451
 
452
+ const RcsLabel = styled.div`
453
+ display: flex;
454
+ margin-top: 20px;
455
+ `;
502
456
  const paramObj = params || {};
503
457
  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]);
458
+ const { id } = paramObj;
459
+ if (id && isFullMode) {
460
+ setSpin(true);
461
+ actions.getTemplateDetails(id, setSpin);
462
+ }
463
+ return () => {
464
+ actions.clearEditResponse();
465
+ };
466
+ }, [paramObj.id]);
513
467
 
514
468
  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;
469
+ if (!(isEditFlow || !isFullMode)) return;
470
+
471
+ const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
472
+ const arr = splitTemplateVarString(targetString);
473
+ if (!arr?.length) {
474
+ setVarMap({});
475
+ setUpdated([]);
476
+ return;
542
477
  }
543
- });
544
- setVarMap(nextVarMap);
545
- };
546
-
547
- initField(templateTitle, setTitleVarMappedData, 0);
548
- initField(templateDesc, setDescVarMappedData, titleTokenCount);
549
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode, rcsSpanningSemanticVarNames]);
478
+ const nextVarMap = {};
479
+ const nextUpdated = [...arr];
480
+ arr.forEach((elem, idx) => {
481
+ // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
482
+ if (rcsVarTestRegex.test(elem)) {
483
+ const id = `${elem}_${idx}`;
484
+ const varName = getVarNameFromToken(elem);
485
+ const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
486
+ nextVarMap[id] = mappedValue;
487
+ if (mappedValue !== '') {
488
+ nextUpdated[idx] = mappedValue;
489
+ } else {
490
+ nextUpdated[idx] = elem;
491
+ }
492
+ }
493
+ });
494
+ setVarMap(nextVarMap);
495
+ setUpdated(nextUpdated);
496
+ };
550
497
 
551
- useEffect(() => {
552
- if (!isEditFlow && isFullMode) {
498
+ initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
499
+ initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
500
+ }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
501
+
502
+ useEffect(() => {
503
+ if(!isEditFlow && isFullMode){
553
504
  setRcsVideoSrc({});
554
505
  updateRcsImageSrc('');
555
506
  setUpdateRcsImageSrc('');
556
507
  updateRcsThumbnailSrc('');
557
508
  setAssetList({});
558
- }
559
- }, [templateMediaType]);
509
+ }
510
+ }, [templateMediaType]);
560
511
 
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':
512
+ const templateStatusHelper = (details) => {
513
+ const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
514
+ switch (status) {
515
+ case RCS_STATUSES.approved:
572
516
  setTemplateStatus(RCS_STATUSES.approved);
573
517
  break;
574
- case 'pending':
518
+ case RCS_STATUSES.pending:
575
519
  setTemplateStatus(RCS_STATUSES.pending);
576
520
  break;
577
- case 'awaitingapproval':
521
+ case RCS_STATUSES.awaitingApproval:
578
522
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
579
523
  break;
580
- case 'unavailable':
524
+ case RCS_STATUSES.unavailable:
581
525
  setTemplateStatus(RCS_STATUSES.unavailable);
582
526
  break;
583
- case 'rejected':
527
+ case RCS_STATUSES.rejected:
584
528
  setTemplateStatus(RCS_STATUSES.rejected);
585
529
  break;
586
- case 'created':
587
- setTemplateStatus(RCS_STATUSES.created);
588
- break;
589
530
  default:
590
531
  setTemplateStatus(status);
591
532
  break;
@@ -596,6 +537,7 @@ export const Rcs = (props) => {
596
537
  if (mediaType) {
597
538
  setTemplateMediaType(mediaType);
598
539
  }
540
+ const tempOrientation = cardSettings.cardOrientation;
599
541
  const tempAlignment = cardSettings.mediaAlignment;
600
542
  const tempHeight = mediaData.height;
601
543
 
@@ -638,206 +580,44 @@ export const Rcs = (props) => {
638
580
  };
639
581
 
640
582
  useEffect(() => {
641
- const details = rcsHydrationDetails;
583
+ const details = isFullMode ? rcsData?.templateDetails : templateData;
642
584
  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);
585
+ if (!isFullMode) {
586
+ const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
587
+ setCardVarMapped(tempCardVarMapped);
677
588
  }
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', '');
589
+ const mediaType = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].mediaType', '');
720
590
  if (mediaType === RCS_MEDIA_TYPES.NONE) {
721
591
  setTemplateType(contentType.text_message);
722
592
  } else {
723
593
  setTemplateType(contentType.rich_card);
724
594
  }
725
595
  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
- });
596
+ setTemplateName(details.name || '');
597
+ const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
598
+ const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
599
+ const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
600
+ const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
601
+ const normalizedDesc = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedDesc, loadedMap) : loadedDesc;
736
602
  setTemplateTitle(normalizedTitle);
737
603
  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', '');
604
+ setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
605
+ templateStatusHelper(details);
606
+ const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
607
+ const cardSettings = get(details, 'versions.base.content.RCS.rcsContent.cardSettings', '');
763
608
  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);
828
- }
829
609
  }
830
- }, [rcsHydrationDetails, isFullMode]);
610
+ }, [rcsData, templateData, isFullMode, isEditFlow]);
611
+
831
612
 
832
613
  useEffect(() => {
833
614
  if (templateType === contentType.text_message) {
834
615
  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.
616
+ setTemplateTitle('');
617
+ setTemplateTitleError('');
837
618
  if (!isEditFlow && isFullMode) {
838
- setTemplateTitle('');
839
- setTemplateTitleError('');
840
619
  setUpdateRcsImageSrc('');
620
+ setUpdateRcsVideoSrc({});
841
621
  setRcsVideoSrc({});
842
622
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
843
623
  }
@@ -864,8 +644,7 @@ export const Rcs = (props) => {
864
644
  if (!showDltContainer) {
865
645
  const { type, module } = location.query || {};
866
646
  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';
647
+ const context = isEmbedded ? module : DEFAULT;
869
648
  const embedded = isEmbedded ? type : FULL;
870
649
  const query = {
871
650
  layout: SMS,
@@ -876,9 +655,9 @@ export const Rcs = (props) => {
876
655
  if (getDefaultTags) {
877
656
  query.context = getDefaultTags;
878
657
  }
879
- fetchTagSchemaIfNewQuery(query);
658
+ globalActions.fetchSchemaForEntity(query);
880
659
  }
881
- }, [showDltContainer, fetchTagSchemaIfNewQuery]);
660
+ }, [showDltContainer]);
882
661
 
883
662
  useEffect(() => {
884
663
  let tag = get(metaEntities, `tags.standard`, []);
@@ -901,107 +680,39 @@ export const Rcs = (props) => {
901
680
  context,
902
681
  embedded,
903
682
  };
904
- if (getDefaultTags) {
905
- query.context = getDefaultTags;
906
- }
907
- fetchTagSchemaIfNewQuery(query);
683
+ globalActions.fetchSchemaForEntity(query);
908
684
  };
909
685
 
910
- const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
911
- if (!templateStr || !numericVarName || !tagName) return templateStr;
912
- const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
913
- return templateStr.replace(re, `{{${tagName}}}`);
686
+ const onTagSelect = (data, areaId) => {
687
+ if (!areaId) return;
688
+ const sep = areaId.lastIndexOf('_');
689
+ if (sep === -1) return;
690
+ const numId = Number(areaId.slice(sep + 1));
691
+ if (isNaN(numId)) return;
692
+ const token = areaId.slice(0, sep);
693
+ const variableName = getVarNameFromToken(token);
694
+ if (!variableName) return;
695
+ setCardVarMapped((prev) => {
696
+ const base = (prev?.[variableName] ?? '').toString();
697
+ const nextVal = `${base}{{${data}}}`;
698
+ return {
699
+ ...(prev || {}),
700
+ [variableName]: nextVal,
701
+ };
702
+ });
914
703
  };
915
704
 
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
- });
705
+ const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
966
706
 
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
- };
707
+ const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
1001
708
 
1002
- const onTitleTagSelect = (tagName) => onTagSelect(tagName, titleTextAreaId, RCS_TAG_AREA_FIELD_TITLE);
709
+ const onTagSelectFallback = (data) => {
710
+ const tempMsg = `${fallbackMessage}{{${data}}}`;
711
+ const error = fallbackMessageErrorHandler(tempMsg);
712
+ setFallbackMessage(tempMsg);
713
+ setFallbackMessageError(error);
714
+ };
1003
715
 
1004
- const onDescTagSelect = (tagName) => onTagSelect(tagName, descTextAreaId, RCS_TAG_AREA_FIELD_DESC);
1005
716
 
1006
717
  //removing optout tag for rcs
1007
718
  const getRcsTags = () => {
@@ -1014,14 +725,15 @@ export const Rcs = (props) => {
1014
725
  };
1015
726
  // tag Code end
1016
727
 
1017
- const renderLabel = (value, desc) => {
728
+ const renderLabel = (value, showLabel, desc) => {
729
+ const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
1018
730
  return (
1019
731
  <>
1020
- <div className="rcs-form-section-heading">
732
+ <RcsLabel>
1021
733
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
1022
- </div>
734
+ </RcsLabel>
1023
735
  {desc && (
1024
- <CapLabel type="label3" className="rcs-form-field-caption">
736
+ <CapLabel type="label3" style={{ marginBottom: '17px' }}>
1025
737
  {formatMessage(messages[desc])}
1026
738
  </CapLabel>
1027
739
  )}
@@ -1107,6 +819,47 @@ export const Rcs = (props) => {
1107
819
  setTemplateDescError(error);
1108
820
  };
1109
821
 
822
+
823
+ const templateDescErrorHandler = (value) => {
824
+ let errorMessage = false;
825
+ const { isBraceError } = validateTags({
826
+ content: value,
827
+ tagsParam: tags,
828
+ location,
829
+ tagModule: getDefaultTags,
830
+ isFullMode,
831
+ }) || {};
832
+
833
+ const maxLength = templateType === contentType.text_message
834
+ ? RCS_TEXT_MESSAGE_MAX_LENGTH
835
+ : RCS_RICH_CARD_MAX_LENGTH;
836
+
837
+ if (value === '' && isMediaTypeText) {
838
+ errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
839
+ } else if (value?.length > maxLength) {
840
+ errorMessage = formatMessage(messages.templateMessageLengthError);
841
+ }
842
+
843
+ if (isBraceError) {
844
+ errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
845
+ }
846
+ return errorMessage;
847
+ };
848
+
849
+
850
+ const onFallbackMessageChange = ({ target: { value } }) => {
851
+ const error = fallbackMessageErrorHandler(value);
852
+ setFallbackMessage(value);
853
+ setFallbackMessageError(error);
854
+ };
855
+
856
+ const fallbackMessageErrorHandler = (value) => {
857
+ if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
858
+ return formatMessage(messages.fallbackMsgLenError);
859
+ }
860
+ return false;
861
+ };
862
+
1110
863
  // Check for forbidden characters: square brackets [] and single curly braces {}
1111
864
  const forbiddenCharactersValidation = (value) => {
1112
865
  if (!value) return false;
@@ -1151,43 +904,53 @@ export const Rcs = (props) => {
1151
904
  if(!isFullMode){
1152
905
  return false;
1153
906
  }
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)) {
907
+ if (!/^\w+$/.test(paramName)) {
1156
908
  return formatMessage(messages.unknownCharactersError);
1157
909
  }
1158
910
  }
1159
911
  return false;
1160
912
  };
913
+
914
+ const templateHeaderErrorHandler = (value) => {
915
+ let errorMessage = false;
916
+ if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
917
+ errorMessage = formatMessage(messages.templateHeaderLengthError);
918
+ } else {
919
+ errorMessage = variableErrorHandling(value);
920
+ }
921
+ return errorMessage;
922
+ };
923
+
924
+
925
+ const templateMessageErrorHandler = (value) => {
926
+ let errorMessage = false;
927
+ if (value === '') {
928
+ errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
929
+ } else if (
930
+ value?.length
931
+ > TEMPLATE_MESSAGE_MAX_LENGTH
932
+ ) {
933
+ errorMessage = formatMessage(messages.templateMessageLengthError);
934
+ } else {
935
+ errorMessage = variableErrorHandling(value);
936
+ }
937
+ return errorMessage;
938
+ };
939
+
1161
940
 
1162
941
  const onMessageAddVar = () => {
1163
- onAddVar(templateDesc);
942
+ onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
1164
943
  };
1165
944
 
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
- });
945
+ const onAddVar = (type, messageContent, regex) => {
946
+ // Always append the next variable at the end, like WhatsApp
947
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
948
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
1181
949
  let nextNumber = 1;
1182
950
  while (existingNumbers.includes(nextNumber)) {
1183
951
  nextNumber++;
1184
952
  }
1185
- return nextNumber > 19 ? null : nextNumber;
1186
- };
1187
-
1188
- const onAddVar = (messageContent) => {
1189
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, messageContent);
1190
- if (nextNumber === null) {
953
+ if (nextNumber > 19) {
1191
954
  return;
1192
955
  }
1193
956
  const nextVar = `{{${nextNumber}}}`;
@@ -1199,13 +962,15 @@ const onAddVar = (messageContent) => {
1199
962
  };
1200
963
 
1201
964
  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.
965
+ // Always append the next variable at the end, like WhatsApp
1206
966
  const messageContent = templateTitle;
1207
- const nextNumber = getNextRcsNumericVarNumber(templateTitle, templateDesc);
1208
- if (nextNumber === null) {
967
+ const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
968
+ const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
969
+ let nextNumber = 1;
970
+ while (existingNumbers.includes(nextNumber)) {
971
+ nextNumber++;
972
+ }
973
+ if (nextNumber > 19) {
1209
974
  return;
1210
975
  }
1211
976
  const nextVar = `{{${nextNumber}}}`;
@@ -1217,6 +982,66 @@ const onTitleAddVar = () => {
1217
982
  setTemplateTitleError(error);
1218
983
  };
1219
984
 
985
+
986
+ const splitTemplateVarString = (str) => {
987
+ if (!str) return [];
988
+ const validVarArr = str.match(rcsVarRegex) || [];
989
+ const templateVarArray = [];
990
+ let content = str;
991
+ while (content?.length !== 0) {
992
+ const index = content.indexOf(validVarArr?.[0]);
993
+ if (index !== -1) {
994
+ templateVarArray.push(content.substring(0, index));
995
+ templateVarArray.push(validVarArr?.[0]);
996
+ content = content.substring(index + validVarArr?.[0]?.length, content?.length);
997
+ validVarArr?.shift();
998
+ } else {
999
+ templateVarArray.push(content);
1000
+ break;
1001
+ }
1002
+ }
1003
+ return templateVarArray.filter(Boolean);
1004
+ };
1005
+
1006
+ const textAreaValue = (idValue, type) => {
1007
+ if (idValue >= 0) {
1008
+ const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1009
+ const templateArr = splitTemplateVarString(templateStr);
1010
+ const token = templateArr?.[idValue] || "";
1011
+ if (token && rcsVarTestRegex.test(token)) {
1012
+ const varName = getVarNameFromToken(token);
1013
+ return (cardVarMapped?.[varName] ?? '').toString();
1014
+ }
1015
+ return "";
1016
+ }
1017
+ return "";
1018
+ };
1019
+
1020
+ const textAreaValueChange = (e, type) => {
1021
+ const value = e?.target?.value ?? '';
1022
+ const id = e?.target?.id || e?.currentTarget?.id || '';
1023
+ if (!id) return;
1024
+ const sep = id.lastIndexOf('_');
1025
+ if (sep === -1) return;
1026
+ const isInvalidValue = value?.trim() === "";
1027
+ const token = id.slice(0, sep);
1028
+ const variableName = getVarNameFromToken(token);
1029
+
1030
+ if (variableName) {
1031
+ setCardVarMapped((prev) => ({
1032
+ ...prev,
1033
+ [variableName]: isInvalidValue ? "" : value,
1034
+ }));
1035
+ }
1036
+ };
1037
+
1038
+ const setTextAreaId = (e, type) => {
1039
+ const id = e?.target?.id || e?.currentTarget?.id || '';
1040
+ if (!id) return;
1041
+ if (type === TITLE_TEXT) setTitleTextAreaId(id);
1042
+ else setDescTextAreaId(id);
1043
+ };
1044
+
1220
1045
  const renderButtonComponent = () => {
1221
1046
  return (
1222
1047
  <>
@@ -1247,65 +1072,39 @@ const onTitleAddVar = () => {
1247
1072
  );
1248
1073
  };
1249
1074
 
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
- });
1075
+ const renderedRCSEditMessage = (descArray, type) => {
1076
+ const renderArray = [];
1077
+ if (descArray?.length) {
1078
+ descArray.forEach((elem, index) => {
1079
+ if (rcsVarTestRegex.test(elem)) {
1080
+ // Variable input
1081
+ renderArray.push(
1082
+ <TextArea
1083
+ id={`${elem}_${index}`}
1084
+ key={`${elem}_${index}`}
1085
+ placeholder={`enter the value for ${elem}`}
1086
+ autosize={{ minRows: 1, maxRows: 3 }}
1087
+ onChange={e => textAreaValueChange(e, type)}
1088
+ value={textAreaValue(index, type)}
1089
+ onFocus={(e) => setTextAreaId(e, type)}
1090
+ />
1091
+ );
1092
+ } else if (elem) {
1093
+ // Static text
1094
+ renderArray.push(
1095
+ <TextArea
1096
+ key={`static_${index}`}
1097
+ value={elem}
1098
+ autosize={{ minRows: 1, maxRows: 3 }}
1099
+ disabled
1100
+ className="rcs-edit-template-message-static-textarea"
1101
+ style={{ background: '#fafafa', color: '#888' }}
1102
+ />
1103
+ );
1104
+ }
1105
+ });
1106
+ }
1107
+ return renderArray;
1309
1108
  };
1310
1109
 
1311
1110
  const renderTextComponent = () => {
@@ -1349,19 +1148,10 @@ const onTitleAddVar = () => {
1349
1148
  </>
1350
1149
  }
1351
1150
  />
1151
+ <div className="rcs_text_area_wrapper">
1352
1152
  {(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
- />
1153
+ renderedRCSEditMessage(splitTemplateVarString(templateTitle), TITLE_TEXT)
1363
1154
  ) : (
1364
- <div className="rcs_text_area_wrapper">
1365
1155
  <CapInput
1366
1156
  className={`rcs-template-title-input ${
1367
1157
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -1374,8 +1164,8 @@ const onTitleAddVar = () => {
1374
1164
  errorMessage={templateTitleError}
1375
1165
  disabled={isEditFlow || !isFullMode}
1376
1166
  />
1377
- </div>
1378
1167
  )}
1168
+ </div>
1379
1169
  {(isEditFlow || !isFullMode) && templateTitleError && (
1380
1170
  <CapError className="rcs-template-title-error">
1381
1171
  {templateTitleError}
@@ -1422,20 +1212,8 @@ const onTitleAddVar = () => {
1422
1212
  </CapRow>
1423
1213
  <CapRow className="rcs-create-template-message-input">
1424
1214
  <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
1215
  {(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
- )
1216
+ ? renderedRCSEditMessage(splitTemplateVarString(templateDesc), MESSAGE_TEXT)
1439
1217
  : (
1440
1218
  <>
1441
1219
  <TextArea
@@ -1479,9 +1257,7 @@ const onTitleAddVar = () => {
1479
1257
  {templateDescError}
1480
1258
  </CapError>
1481
1259
  )}
1482
- {(isEditFlow || !isFullMode)
1483
- ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
1484
- : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1260
+ {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
1485
1261
  {!isFullMode && hasTag() && (
1486
1262
  <CapAlert
1487
1263
  message={
@@ -1501,6 +1277,18 @@ const onTitleAddVar = () => {
1501
1277
  );
1502
1278
  };
1503
1279
 
1280
+
1281
+ const fallbackSmsLength = () => (
1282
+ <CapLabel type="label1" className="fallback-sms-length">
1283
+ {formatMessage(messages.totalCharacters, {
1284
+ smsCount: Math.ceil(
1285
+ fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1286
+ ),
1287
+ number: fallbackMessage?.length,
1288
+ })}
1289
+ </CapLabel>
1290
+ );
1291
+
1504
1292
  // Get character count for title (rich card only)
1505
1293
  const getTitleCharacterCount = () => {
1506
1294
  if (templateType === contentType.text_message) return 0;
@@ -1560,7 +1348,7 @@ const onTitleAddVar = () => {
1560
1348
  const hasTag = () => {
1561
1349
  // Check cardVarMapped values for tags
1562
1350
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1563
- const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
1351
+ const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1564
1352
  isTagIncluded(value)
1565
1353
  );
1566
1354
  if (hasTagInMapped) return true;
@@ -1578,6 +1366,14 @@ const onTitleAddVar = () => {
1578
1366
  return false;
1579
1367
  };
1580
1368
 
1369
+ //adding creative dlt fallback sms handlers
1370
+ const addDltMsgHandler = () => {
1371
+ setShowDltContainer(true);
1372
+ setDltMode(RCS_DLT_MODE.TEMPLATES);
1373
+ setDltEditData({});
1374
+ setFallbackMessage('');
1375
+ };
1376
+
1581
1377
  const closeDltContainerHandler = () => {
1582
1378
  setShowDltContainer(false);
1583
1379
  setDltMode('');
@@ -1600,17 +1396,68 @@ const onTitleAddVar = () => {
1600
1396
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1601
1397
  '',
1602
1398
  );
1603
- const templateNameFromDlt = get(dltEditData, 'name', '')
1604
- || get(tempData, 'versions.base.name', '')
1605
- || '';
1606
1399
  closeDltContainerHandler();
1607
1400
  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
- });
1401
+ setFallbackMessage(fallMsg);
1402
+ setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1403
+ setShowDltCard(true);
1404
+ };
1405
+
1406
+ const rcsDltCardDeleteHandler = () => {
1407
+ closeDltContainerHandler();
1408
+ setDltEditData({});
1409
+ setFallbackMessage('');
1410
+ setFallbackMessageError(false);
1411
+ setShowDltCard(false);
1412
+ };
1413
+
1414
+ const dltFallbackListingPreviewhandler = (data) => {
1415
+ const {
1416
+ 'updated-sms-editor': updatedSmsEditor = [],
1417
+ 'sms-editor': smsEditor = '',
1418
+ } = data.versions.base || {};
1419
+ setFallbackPreviewmode(true);
1420
+ setDltPreviewData(
1421
+ updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1422
+ );
1423
+ };
1424
+
1425
+ const getDltContentCardList = (content, channel) => {
1426
+ const extra = [
1427
+ <CapIcon
1428
+ type="edit"
1429
+ style={{ marginRight: '8px' }}
1430
+ onClick={() => rcsDltEditSelectHandler(dltEditData)}
1431
+ />,
1432
+ <CapDropdown
1433
+ overlay={(
1434
+ <CapMenu>
1435
+ <>
1436
+ <CapMenu.Item
1437
+ className="ant-dropdown-menu-item"
1438
+ onClick={() => setFallbackPreviewmode(true)}
1439
+ >
1440
+ {formatMessage(globalMessages.preview)}
1441
+ </CapMenu.Item>
1442
+ <CapMenu.Item
1443
+ className="ant-dropdown-menu-item"
1444
+ onClick={rcsDltCardDeleteHandler}
1445
+ >
1446
+ {formatMessage(globalMessages.delete)}
1447
+ </CapMenu.Item>
1448
+ </>
1449
+ </CapMenu>
1450
+ )}
1451
+ >
1452
+ <CapIcon type="more" />
1453
+ </CapDropdown>,
1454
+ ];
1455
+ return {
1456
+ title: channel,
1457
+ content,
1458
+ cardType: channel,
1459
+ extra,
1460
+ };
1614
1461
  };
1615
1462
 
1616
1463
  const getDltSlideBoxContent = () => {
@@ -1637,6 +1484,7 @@ const onTitleAddVar = () => {
1637
1484
  isFullMode={isFullMode}
1638
1485
  isDltFromRcs
1639
1486
  onSelectTemplate={rcsDltEditSelectHandler}
1487
+ handlePeviewTemplate={dltFallbackListingPreviewhandler}
1640
1488
  />
1641
1489
  );
1642
1490
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1657,32 +1505,147 @@ const onTitleAddVar = () => {
1657
1505
  return { dltHeader, dltContent };
1658
1506
  };
1659
1507
 
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
- );
1508
+ const renderFallBackSmsComponent = () => {
1509
+ // Completely disable fallback functionality when DLT is disabled
1510
+ return null;
1511
+
1512
+ // const contentArr = [];
1513
+ // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1514
+ // const showCardForDlt = isDltEnabled && showDltCard;
1515
+ // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1516
+ // //pushing common fallback sms headings
1517
+ // contentArr.push(
1518
+ // <CapRow
1519
+ // style={{
1520
+ // marginBottom: isDltEnabled ? '20px' : '10px',
1521
+ // }}
1522
+ // >
1523
+ // <CapHeader
1524
+ // title={(
1525
+ // <CapRow type="flex">
1526
+ // <CapHeading type="h4">
1527
+ // {formatMessage(messages.fallbackLabel)}
1528
+ // </CapHeading>
1529
+ // <CapTooltipWithInfo
1530
+ // placement="right"
1531
+ // infoIconProps={{
1532
+ // style: { marginLeft: '4px', marginTop: '3px' },
1533
+ // }}
1534
+ // title={formatMessage(messages.fallbackToolTip)}
1535
+ // />
1536
+ // </CapRow>
1537
+ // )}
1538
+ // description={formatMessage(messages.fallbackDesc)}
1539
+ // suffix={
1540
+ // isDltEnabled ? null : (
1541
+ // <CapButton
1542
+ // type="flat"
1543
+ // className="fallback-preview-btn"
1544
+ // prefix={<CapIcon type="eye" />}
1545
+ // style={{ color: CAP_SECONDARY.base }}
1546
+ // onClick={() => setFallbackPreviewmode(true)}
1547
+ // disabled={fallbackMessage === '' || fallbackMessageError}
1548
+ // >
1549
+ // {formatMessage(globalMessages.preview)}
1550
+ // </CapButton>
1551
+ // )
1552
+ // }
1553
+ // />
1554
+ // </CapRow>,
1555
+ // );
1556
+
1557
+ //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1558
+ // showAddCreativeBtnForDlt
1559
+ // && contentArr.push(
1560
+ // <CapCard className="rcs-dlt-fallback-card">
1561
+ // <CapRow type="flex" justify="center" align="middle">
1562
+ // <CapColumn span={10}>
1563
+ // <CapImage src={addCreativesIcon} />
1564
+ // </CapColumn>
1565
+ // <CapColumn span={14}>
1566
+ // <CapButton
1567
+ // className="add-dlt-btn"
1568
+ // type="secondary"
1569
+ // onClick={addDltMsgHandler}
1570
+ // >
1571
+ // {formatMessage(messages.addSmsCreative)}
1572
+ // </CapButton>
1573
+ // </CapColumn>
1574
+ // </CapRow>
1575
+ // </CapCard>,
1576
+ // );
1577
+
1578
+ // //dlt is enabled and dlt content is added, show it in a card
1579
+ // showCardForDlt
1580
+ // && contentArr.push(
1581
+ // <CapCustomCardList
1582
+ // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1583
+ // className="rcs-dlt-card"
1584
+ // />,
1585
+ // fallbackMessageError && (
1586
+ // <CapError className="rcs-fallback-len-error">
1587
+ // {formatMessage(messages.fallbackMsgLenError)}
1588
+ // </CapError>
1589
+ // ),
1590
+ // );
1591
+
1592
+ // //dlt is not enabled, show non dlt text area
1593
+ // showNonDltFallbackComp
1594
+ // && contentArr.push(
1595
+ // <>
1596
+ // <CapRow>
1597
+ // <CapHeader
1598
+ // title={(
1599
+ // <CapHeading type="h4">
1600
+ // {formatMessage(messages.fallbackTextAreaLabel)}
1601
+ // </CapHeading>
1602
+ // )}
1603
+ // suffix={(
1604
+ // <TagList
1605
+ // label={formatMessage(globalMessages.addLabels)}
1606
+ // onTagSelect={onTagSelectFallback}
1607
+ // location={location}
1608
+ // tags={tags || []}
1609
+ // onContextChange={handleOnTagsContextChange}
1610
+ // injectedTags={injectedTags || {}}
1611
+ // selectedOfferDetails={selectedOfferDetails}
1612
+ // />
1613
+ // )}
1614
+ // />
1615
+ // </CapRow>
1616
+ // <div className="rcs_fallback_msg_textarea_wrapper">
1617
+ // <TextArea
1618
+ // id="rcs_fallback_message_textarea"
1619
+ // autosize={{ minRows: 3, maxRows: 5 }}
1620
+ // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1621
+ // onChange={onFallbackMessageChange}
1622
+ // errorMessage={fallbackMessageError}
1623
+ // value={fallbackMessage || ""}
1624
+ // />
1625
+ // {!aiContentBotDisabled && (
1626
+ // <CapAskAira.ContentGenerationBot
1627
+ // text={fallbackMessage || ""}
1628
+ // setText={(text) => {
1629
+ // onFallbackMessageChange({ target: { value: text } });
1630
+ // }}
1631
+ // iconPlacement="float-br"
1632
+ // rootStyle={{
1633
+ // bottom: "0.5rem",
1634
+ // right: "0.5rem",
1635
+ // position: "absolute",
1636
+ // }}
1637
+ // />
1638
+ // )}
1639
+ // </div>
1640
+ // <CapRow>{fallbackSmsLength()}</CapRow>
1641
+ // </>
1642
+ // );
1643
+
1644
+ // return <>{contentArr}</>;
1645
+ };
1684
1646
 
1685
1647
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1648
+ setImageError(null);
1686
1649
  const isRcsThumbnail = index === 1;
1687
1650
  actions.uploadRcsAsset(file, type, {
1688
1651
  isRcsThumbnail,
@@ -1694,6 +1657,7 @@ const onTitleAddVar = () => {
1694
1657
 
1695
1658
  const setUpdateRcsImageSrc = useCallback(
1696
1659
  (val) => {
1660
+ setAssetListImage(val);
1697
1661
  updateRcsImageSrc(val);
1698
1662
  actions.clearRcsMediaAsset(0);
1699
1663
  },
@@ -1723,6 +1687,7 @@ const onTitleAddVar = () => {
1723
1687
 
1724
1688
 
1725
1689
  const uploadRcsVideo = (file, type, fileParams) => {
1690
+ setImageError(null);
1726
1691
  actions.uploadRcsAsset(file, type, {
1727
1692
  ...fileParams,
1728
1693
  type: 'video',
@@ -1731,6 +1696,9 @@ const onTitleAddVar = () => {
1731
1696
  });
1732
1697
  };
1733
1698
 
1699
+ const updateRcsVideoSrc = (val) => {
1700
+ setRcsVideoSrc(val);
1701
+ };
1734
1702
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
1735
1703
  setRcsVideoSrc(val);
1736
1704
  setAssetList(val);
@@ -1759,7 +1727,7 @@ const onTitleAddVar = () => {
1759
1727
  <>
1760
1728
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1761
1729
  <CapImageUpload
1762
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1730
+ style={{ paddingTop: '20px' }}
1763
1731
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1764
1732
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1765
1733
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1771,6 +1739,7 @@ const onTitleAddVar = () => {
1771
1739
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1772
1740
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1773
1741
  index={1}
1742
+ className="cap-custom-image-upload"
1774
1743
  key={`rcs-uploaded-image-${currentDimension}`}
1775
1744
  imageData={thumbnailData}
1776
1745
  channel={RCS}
@@ -1801,7 +1770,7 @@ const onTitleAddVar = () => {
1801
1770
  value: dim.type,
1802
1771
  label: `${dim.label}`
1803
1772
  }))}
1804
- className="rcs-dimension-select--bottom-spacing"
1773
+ style={{ marginBottom: '20px' }}
1805
1774
  />
1806
1775
  </>
1807
1776
  )}
@@ -1815,6 +1784,7 @@ const onTitleAddVar = () => {
1815
1784
  </div>
1816
1785
  ) : (
1817
1786
  <CapImageUpload
1787
+ style={{ paddingTop: '20px' }}
1818
1788
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1819
1789
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1820
1790
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1825,7 +1795,7 @@ const onTitleAddVar = () => {
1825
1795
  updateImageSrc={setUpdateRcsImageSrc}
1826
1796
  updateOnReUpload={updateOnRcsImageReUpload}
1827
1797
  index={0}
1828
- className="cap-custom-image-upload rcs-image-upload--top-spacing"
1798
+ className="cap-custom-image-upload"
1829
1799
  key={`rcs-uploaded-image-${selectedDimension}`}
1830
1800
  imageData={rcsData}
1831
1801
  channel={RCS}
@@ -1855,7 +1825,7 @@ const onTitleAddVar = () => {
1855
1825
  value: dim.type,
1856
1826
  label: `${dim.label}`
1857
1827
  }))}
1858
- className="rcs-dimension-select--bottom-spacing"
1828
+ style={{ marginBottom: '20px' }}
1859
1829
  />
1860
1830
  )}
1861
1831
  {(isEditFlow || !isFullMode) ? (
@@ -1915,16 +1885,8 @@ const onTitleAddVar = () => {
1915
1885
  const getRcsPreview = () => {
1916
1886
 
1917
1887
  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;
1888
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1889
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1928
1890
  return (
1929
1891
  <UnifiedPreview
1930
1892
  channel={RCS}
@@ -1947,65 +1909,51 @@ const onTitleAddVar = () => {
1947
1909
  );
1948
1910
  };
1949
1911
 
1912
+ const getUnmappedDesc = (str, mapping) => {
1913
+ if (!str) return '';
1914
+ if (!mapping || Object.keys(mapping).length === 0) return str;
1915
+ let result = str;
1916
+ const replacements = [];
1917
+ Object.entries(mapping).forEach(([key, value]) => {
1918
+ const raw = (value ?? '').toString();
1919
+ if (!raw || raw?.trim?.() === '') return;
1920
+ const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1921
+ replacements.push({ key, needle: raw });
1922
+ if (braced !== raw) replacements.push({ key, needle: braced });
1923
+ });
1924
+ const seen = new Set();
1925
+ const uniq = replacements
1926
+ .filter(({ key, needle }) => {
1927
+ const id = `${key}::${needle}`;
1928
+ if (seen.has(id)) return false;
1929
+ seen.add(id);
1930
+ return true;
1931
+ })
1932
+ .sort((a, b) => (b.needle.length - a.needle.length));
1933
+
1934
+ uniq.forEach(({ key, needle }) => {
1935
+ if (!needle) return;
1936
+ const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1937
+ const regex = new RegExp(escaped, 'g');
1938
+ result = result.replace(regex, `{{${key}}}`);
1939
+ });
1940
+ return result;
1941
+ };
1942
+
1950
1943
  const createPayload = () => {
1951
- const isSlotMappingMode = isEditFlow || !isFullMode;
1944
+ const base = get(dltEditData, `versions.base`, {});
1945
+ const {
1946
+ template_id: templateId = '',
1947
+ template_name = '',
1948
+ 'sms-editor': template = '',
1949
+ header: registeredSenderIds = [],
1950
+ } = base;
1951
+ const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1952
+ const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1952
1953
  const alignment = isMediaTypeImage
1953
1954
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
1954
1955
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
1955
1956
 
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
1957
  const payload = {
2010
1958
  name: templateName,
2011
1959
  versions: {
@@ -2017,14 +1965,12 @@ const onTitleAddVar = () => {
2017
1965
  cardSettings: {
2018
1966
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
2019
1967
  ...(alignment && { mediaAlignment: alignment }),
2020
- cardWidth: cardWidthFromSelection,
1968
+ cardWidth: SMALL,
2021
1969
  },
2022
1970
  cardContent: [
2023
1971
  {
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,
1972
+ title: resolvedTitle,
1973
+ description: resolvedDesc,
2028
1974
  mediaType: templateMediaType,
2029
1975
  ...(!isMediaTypeText && {media: {
2030
1976
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -2033,32 +1979,23 @@ const onTitleAddVar = () => {
2033
1979
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
2034
1980
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
2035
1981
  }}),
2036
- ...(isSlotMappingMode && (() => {
2037
- const templateVarTokens = [
2038
- ...(templateTitle?.match(rcsVarRegex) ?? []),
2039
- ...(templateDesc?.match(rcsVarRegex) ?? []),
1982
+ ...(!isFullMode && (() => {
1983
+ const tokens = [
1984
+ ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
1985
+ ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
2040
1986
  ];
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;
1987
+ const allowedKeys = tokens
1988
+ .map((t) => getVarNameFromToken(t))
1989
+ .filter(Boolean);
1990
+ const nextMap = {};
1991
+ allowedKeys.forEach((k) => {
1992
+ if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
1993
+ nextMap[k] = cardVarMapped[k];
1994
+ } else {
1995
+ nextMap[k] = '';
1996
+ }
2060
1997
  });
2061
- return { cardVarMapped: persistedSlotVarMap };
1998
+ return { cardVarMapped: nextMap };
2062
1999
  })()),
2063
2000
  ...(suggestions.length > 0 && { suggestions }),
2064
2001
  }
@@ -2066,79 +2003,17 @@ const onTitleAddVar = () => {
2066
2003
  contentType: isFullMode ? templateType : RICHCARD,
2067
2004
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2068
2005
  },
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
- }),
2006
+ smsFallBackContent: {
2007
+ message: fallbackMessage,
2008
+ ...(isDltEnabled && {
2009
+ templateConfigs: {
2010
+ templateId,
2011
+ templateName: template_name,
2012
+ template,
2013
+ registeredSenderIds,
2139
2014
  },
2140
- };
2141
- })()),
2015
+ }),
2016
+ },
2142
2017
  },
2143
2018
  },
2144
2019
  },
@@ -2148,107 +2023,6 @@ const onTitleAddVar = () => {
2148
2023
  return payload;
2149
2024
  };
2150
2025
 
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
2026
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2253
2027
  // eslint-disable-next-line no-undef
2254
2028
  const error = errorMessage?.message || errorMessage;
@@ -2278,9 +2052,6 @@ const onTitleAddVar = () => {
2278
2052
  _id: params?.id,
2279
2053
  validity: true,
2280
2054
  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
2055
  };
2285
2056
  getFormData(formDataParams);
2286
2057
  };
@@ -2294,7 +2065,6 @@ const onTitleAddVar = () => {
2294
2065
  actionCallback({ resp, errorMessage });
2295
2066
  setSpin(false); // Always turn off spinner
2296
2067
  if (!errorMessage) {
2297
- setTemplateStatus(RCS_STATUSES.pending);
2298
2068
  onCreateComplete();
2299
2069
  }
2300
2070
  });
@@ -2306,70 +2076,6 @@ const onTitleAddVar = () => {
2306
2076
  }
2307
2077
  };
2308
2078
 
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
2079
  const isDisableDone = () => {
2374
2080
  if(isEditFlow){
2375
2081
  return false;
@@ -2380,16 +2086,40 @@ const onTitleAddVar = () => {
2380
2086
  }
2381
2087
  }
2382
2088
 
2383
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2384
- return true;
2385
- }
2089
+ if(!isFullMode){
2090
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2091
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2092
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2386
2093
 
2387
- if (smsFallbackBlocksDone()) {
2388
- return true;
2094
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2095
+ return true;
2096
+ }
2097
+
2098
+ const hasEmptyMapping =
2099
+ cardVarMapped &&
2100
+ Object.keys(cardVarMapped).length > 0 &&
2101
+ Object.entries(cardVarMapped).some(([_, v]) => {
2102
+ if (typeof v !== 'string') return !v; // null/undefined
2103
+ return v.trim() === ''; // empty string
2104
+ });
2105
+
2106
+ if (hasEmptyMapping) {
2107
+ return true;
2108
+ }
2109
+
2110
+ const anyMissing = allVars.some(name => {
2111
+ const v = cardVarMapped?.[name];
2112
+ if (typeof v !== 'string') return !v;
2113
+ return v.trim() === '';
2114
+ });
2115
+ if (anyMissing) {
2116
+ return true;
2117
+ }
2389
2118
  }
2390
2119
 
2391
- if (isMediaTypeText && templateDesc.trim() === '') {
2120
+ if (isMediaTypeText && templateDesc.trim() === '') {
2392
2121
  return true;
2122
+
2393
2123
  }
2394
2124
  if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2395
2125
  return true;
@@ -2407,36 +2137,53 @@ const onTitleAddVar = () => {
2407
2137
  return true;
2408
2138
  }
2409
2139
  }
2410
- if (templateDescError || templateTitleError) {
2411
- return true;
2412
- }
2413
- if (
2414
- smsFallbackData?.content
2415
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2416
- ) {
2140
+ if (templateDescError || templateTitleError || fallbackMessageError) {
2417
2141
  return true;
2418
2142
  }
2419
2143
  return false;
2420
2144
  };
2421
2145
 
2422
2146
  const isEditDisableDone = () => {
2147
+
2423
2148
  if (templateStatus !== RCS_STATUSES.approved) {
2424
2149
  return true;
2425
2150
  }
2426
2151
 
2427
- // if (!isFullMode) {
2428
- // if (templateName.trim() === '' || templateNameError) {
2429
- // return true;
2430
- // }
2431
- // }
2432
- if (isLibraryCampaignCardVarMappingIncomplete()) {
2433
- return true;
2152
+ if (!isFullMode) {
2153
+ if (templateName.trim() === '' || templateNameError) {
2154
+ return true;
2155
+ }
2434
2156
  }
2157
+ if(!isFullMode){
2158
+ const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2159
+ const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2160
+ const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2435
2161
 
2436
- if (smsFallbackBlocksDone()) {
2437
- return true;
2438
- }
2162
+ if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2163
+ return true;
2164
+ }
2439
2165
 
2166
+ const hasEmptyMapping =
2167
+ cardVarMapped &&
2168
+ Object.keys(cardVarMapped).length > 0 &&
2169
+ Object.entries(cardVarMapped).some(([_, v]) => {
2170
+ if (typeof v !== 'string') return !v; // null/undefined
2171
+ return v.trim() === ''; // empty string
2172
+ });
2173
+
2174
+ if (hasEmptyMapping) {
2175
+ return true;
2176
+ }
2177
+
2178
+ const anyMissing = allVars.some(name => {
2179
+ const v = cardVarMapped?.[name];
2180
+ if (typeof v !== 'string') return !v;
2181
+ return v.trim() === '';
2182
+ });
2183
+ if (anyMissing) {
2184
+ return true;
2185
+ }
2186
+ }
2440
2187
  if (isMediaTypeText && templateDesc.trim() === '') {
2441
2188
  return true;
2442
2189
  }
@@ -2455,13 +2202,7 @@ const onTitleAddVar = () => {
2455
2202
  return true;
2456
2203
  }
2457
2204
  }
2458
- if (templateTitleError || templateDescError) {
2459
- return true;
2460
- }
2461
- if (
2462
- smsFallbackData?.content
2463
- && smsFallbackData.content.length > FALLBACK_MESSAGE_MAX_LENGTH
2464
- ) {
2205
+ if (templateTitleError || templateDescError || fallbackMessageError) {
2465
2206
  return true;
2466
2207
  }
2467
2208
  return false;
@@ -2511,56 +2252,52 @@ const onTitleAddVar = () => {
2511
2252
  };
2512
2253
 
2513
2254
  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;
2255
+ if (showDltContainer && !fallbackPreviewmode) {
2256
+ const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2257
+ const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2258
+ return (
2259
+ <CapSlideBox
2260
+ show={showDltContainer}
2261
+ header={dltHeader}
2262
+ content={dltContent}
2263
+ handleClose={closeDltContainerHandler}
2264
+ size="size-xl"
2265
+ />
2266
+ );
2267
+ }
2517
2268
 
2518
2269
  return (
2519
2270
  <>
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>
2271
+ {templateStatus !== '' && (<CapRow className="template-status-container">
2272
+ <CapLabel type="label2">
2273
+ {formatMessage(messages.templateStatusLabel)}
2274
+ </CapLabel>
2275
+
2276
+ {templateStatus && (
2277
+ <CapAlert
2278
+ message={getTemplateStatusMessage()}
2279
+ type={getTemplateStatusType(templateStatus)}
2280
+ />
2281
+ )}
2282
+ </CapRow>
2535
2283
  )}
2536
- <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2284
+ <CapRow className="cap-rcs-creatives">
2537
2285
  <CapColumn span={14}>
2538
2286
  {/* template name */}
2539
2287
  {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
- )
2288
+ <CapInput
2289
+ id="rcs_template_name_input"
2290
+ data-testid="template_name"
2291
+ onChange={onTemplateNameChange}
2292
+ errorMessage={templateNameError}
2293
+ placeholder={formatMessage(
2294
+ globalMessages.templateNamePlaceholder,
2295
+ )}
2296
+ value={templateName || ''}
2297
+ size="default"
2298
+ label={formatMessage(globalMessages.creativeNameLabel)}
2299
+ disabled={(isEditFlow || !isFullMode)}
2300
+ />
2564
2301
  )}
2565
2302
  {renderLabel('templateTypeLabel')}
2566
2303
  <CapRadioGroup
@@ -2588,7 +2325,7 @@ const onTitleAddVar = () => {
2588
2325
  </>
2589
2326
  )}
2590
2327
  {renderTextComponent()}
2591
- <CapDivider className="rcs-fallback-section-divider" />
2328
+ <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2592
2329
  {renderFallBackSmsComponent()}
2593
2330
  <div className="rcs-scroll-div" />
2594
2331
  </CapColumn>
@@ -2600,8 +2337,7 @@ const onTitleAddVar = () => {
2600
2337
 
2601
2338
 
2602
2339
  <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 && (
2340
+ {!isEditFlow && (
2605
2341
  <>
2606
2342
  <div className="button-disabled-tooltip-wrapper">
2607
2343
  <CapButton
@@ -2622,6 +2358,7 @@ const onTitleAddVar = () => {
2622
2358
  className="rcs-test-preview-btn"
2623
2359
  type="secondary"
2624
2360
  disabled={true}
2361
+ style={{ marginLeft: "8px" }}
2625
2362
  >
2626
2363
  <FormattedMessage {...creativesMessages.testAndPreview} />
2627
2364
  </CapButton>
@@ -2654,6 +2391,51 @@ const onTitleAddVar = () => {
2654
2391
  </>
2655
2392
  )}
2656
2393
  </div>
2394
+
2395
+
2396
+ {fallbackPreviewmode && (
2397
+ <CapSlideBox
2398
+ className="rcs-fallback-preview"
2399
+ show={fallbackPreviewmode}
2400
+ header={(
2401
+ <CapHeading type="h7" style={{ color: CAP_G01 }}>
2402
+ {formatMessage(messages.fallbackPreviewtitle)}
2403
+ </CapHeading>
2404
+ )}
2405
+ content={(
2406
+ <>
2407
+ <UnifiedPreview
2408
+ channel={RCS}
2409
+ content={{
2410
+ rcsPreviewContent: {
2411
+ rcsDesc: tempMsg,
2412
+ },
2413
+ }}
2414
+ device={ANDROID}
2415
+ showDeviceToggle={false}
2416
+ showHeader={false}
2417
+ formatMessage={formatMessage}
2418
+ />
2419
+ <CapHeading
2420
+ type="h3"
2421
+ style={{ textAlign: 'center' }}
2422
+ className="margin-t-16"
2423
+ >
2424
+ {formatMessage(messages.totalCharacters, {
2425
+ smsCount: Math.ceil(
2426
+ tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2427
+ ),
2428
+ number: tempMsg.length,
2429
+ })}
2430
+ </CapHeading>
2431
+ </>
2432
+ )}
2433
+ handleClose={() => {
2434
+ setFallbackPreviewmode(false);
2435
+ setDltPreviewData('');
2436
+ }}
2437
+ />
2438
+ )}
2657
2439
  </>
2658
2440
  );
2659
2441
  };
@@ -2662,44 +2444,12 @@ const onTitleAddVar = () => {
2662
2444
  <CapSpin spinning={loadingTags || spin}>
2663
2445
  {getMainContent()}
2664
2446
  </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
2447
  <TestAndPreviewSlidebox
2680
2448
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
2681
2449
  onClose={handleCloseTestAndPreview}
2682
- formData={testPreviewFormData}
2683
- content={testAndPreviewContent}
2450
+ formData={null} // RCS doesn't use formData structure like SMS
2451
+ content={getTemplateContent()}
2684
2452
  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
2453
  />
2704
2454
  </>
2705
2455
  );