@capillarytech/creatives-library 8.0.316 → 8.0.317-alpha.0

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