cap-creatives-ui 8.0.280 → 8.0.321

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 (247) hide show
  1. package/.github/workflows/pr-title-check.yml +88 -0
  2. package/app/constants/unified.js +21 -1
  3. package/app/containers/App/constants.js +0 -1
  4. package/app/containers/Login/test/index.test.js +123 -0
  5. package/app/containers/Login/test/selectors.test.js +165 -0
  6. package/app/initialState.js +0 -2
  7. package/app/services/api.js +6 -0
  8. package/app/services/tests/api.test.js +7 -0
  9. package/app/services/tests/getSchema.test.js +95 -0
  10. package/app/utils/common.js +23 -9
  11. package/app/utils/commonUtils.js +64 -93
  12. package/app/utils/tagValidations.js +83 -219
  13. package/app/utils/templateVarUtils.js +172 -0
  14. package/app/utils/tests/common.test.js +265 -323
  15. package/app/utils/tests/commonUtil.test.js +461 -118
  16. package/app/utils/tests/commonUtils.test.js +581 -0
  17. package/app/utils/tests/messageUtils.test.js +95 -0
  18. package/app/utils/tests/smsCharCount.test.js +304 -0
  19. package/app/utils/tests/smsCharCountV2.test.js +213 -10
  20. package/app/utils/tests/tagValidations.test.js +474 -357
  21. package/app/utils/tests/templateVarUtils.test.js +160 -0
  22. package/app/v2Components/CapDeviceContent/index.js +10 -7
  23. package/app/v2Components/CapTagList/index.js +32 -24
  24. package/app/v2Components/CapTagList/style.scss +48 -0
  25. package/app/v2Components/CapTagListWithInput/__tests__/CapTagListWithInput.test.js +63 -0
  26. package/app/v2Components/CapTagListWithInput/index.js +8 -0
  27. package/app/v2Components/CapWhatsappCTA/index.js +2 -0
  28. package/app/v2Components/CapWhatsappCarouselButton/index.js +32 -14
  29. package/app/v2Components/CapWhatsappCarouselButton/tests/index.test.js +120 -2
  30. package/app/v2Components/CommonTestAndPreview/CustomValuesEditor.js +70 -49
  31. package/app/v2Components/CommonTestAndPreview/DeliverySettings/DeliverySettings.scss +39 -0
  32. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.js +606 -0
  33. package/app/v2Components/CommonTestAndPreview/DeliverySettings/ModifyDeliverySettings.scss +36 -0
  34. package/app/v2Components/CommonTestAndPreview/DeliverySettings/constants.js +79 -0
  35. package/app/v2Components/CommonTestAndPreview/DeliverySettings/index.js +314 -0
  36. package/app/v2Components/CommonTestAndPreview/DeliverySettings/messages.js +141 -0
  37. package/app/v2Components/CommonTestAndPreview/DeliverySettings/utils/parseSenderDetailsResponse.js +156 -0
  38. package/app/v2Components/CommonTestAndPreview/SendTestMessage.js +57 -1
  39. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/_unifiedPreview.scss +20 -1
  40. package/app/v2Components/CommonTestAndPreview/UnifiedPreview/index.js +133 -4
  41. package/app/v2Components/CommonTestAndPreview/_commonTestAndPreview.scss +210 -4
  42. package/app/v2Components/CommonTestAndPreview/actions.js +20 -0
  43. package/app/v2Components/CommonTestAndPreview/constants.js +57 -1
  44. package/app/v2Components/CommonTestAndPreview/index.js +878 -156
  45. package/app/v2Components/CommonTestAndPreview/messages.js +41 -3
  46. package/app/v2Components/CommonTestAndPreview/previewApiUtils.js +59 -0
  47. package/app/v2Components/CommonTestAndPreview/reducer.js +47 -0
  48. package/app/v2Components/CommonTestAndPreview/sagas.js +75 -5
  49. package/app/v2Components/CommonTestAndPreview/selectors.js +51 -0
  50. package/app/v2Components/CommonTestAndPreview/tests/CustomValuesEditor.test.js +352 -0
  51. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/ModifyDeliverySettings.test.js +1156 -0
  52. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/index.test.js +334 -0
  53. package/app/v2Components/CommonTestAndPreview/tests/DeliverySettings/utils/parseSenderDetailsResponse.test.js +576 -0
  54. package/app/v2Components/CommonTestAndPreview/tests/SendTestMessage.test.js +156 -0
  55. package/app/v2Components/CommonTestAndPreview/tests/UnifiedPreview/index.test.js +199 -1
  56. package/app/v2Components/CommonTestAndPreview/tests/actions.test.js +50 -0
  57. package/app/v2Components/CommonTestAndPreview/tests/constants.test.js +18 -7
  58. package/app/v2Components/CommonTestAndPreview/tests/index.test.js +914 -5
  59. package/app/v2Components/CommonTestAndPreview/tests/previewApiUtils.test.js +67 -0
  60. package/app/v2Components/CommonTestAndPreview/tests/reducer.test.js +118 -0
  61. package/app/v2Components/CommonTestAndPreview/tests/sagas.test.js +146 -378
  62. package/app/v2Components/CommonTestAndPreview/tests/selectors.test.js +146 -0
  63. package/app/v2Components/ErrorInfoNote/index.js +24 -26
  64. package/app/v2Components/FormBuilder/index.js +182 -204
  65. package/app/v2Components/FormBuilder/messages.js +4 -8
  66. package/app/v2Components/HtmlEditor/HTMLEditor.js +7 -6
  67. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +1 -1
  68. package/app/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +928 -17
  69. package/app/v2Components/HtmlEditor/components/CodeEditorPane/index.js +4 -2
  70. package/app/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +452 -3
  71. package/app/v2Components/HtmlEditor/hooks/useValidation.js +12 -9
  72. package/app/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +132 -0
  73. package/app/v2Components/HtmlEditor/utils/htmlValidator.js +4 -2
  74. package/app/v2Components/SmsFallback/SmsFallbackLocalSelector.js +87 -0
  75. package/app/v2Components/SmsFallback/constants.js +73 -0
  76. package/app/v2Components/SmsFallback/index.js +956 -0
  77. package/app/v2Components/SmsFallback/index.scss +265 -0
  78. package/app/v2Components/SmsFallback/messages.js +78 -0
  79. package/app/v2Components/SmsFallback/smsFallbackUtils.js +107 -0
  80. package/app/v2Components/SmsFallback/tests/SmsFallbackLocalSelector.test.js +50 -0
  81. package/app/v2Components/SmsFallback/tests/rcsSmsFallback.acceptance.test.js +147 -0
  82. package/app/v2Components/SmsFallback/tests/smsFallbackHandlers.test.js +304 -0
  83. package/app/v2Components/SmsFallback/tests/smsFallbackUi.test.js +197 -0
  84. package/app/v2Components/SmsFallback/tests/smsFallbackUtils.test.js +261 -0
  85. package/app/v2Components/SmsFallback/tests/useLocalTemplateList.test.js +422 -0
  86. package/app/v2Components/SmsFallback/useLocalTemplateList.js +92 -0
  87. package/app/v2Components/TestAndPreviewSlidebox/index.js +22 -1
  88. package/app/v2Components/TestAndPreviewSlidebox/sagas.js +11 -4
  89. package/app/v2Components/TestAndPreviewSlidebox/tests/saga.test.js +3 -1
  90. package/app/v2Components/VarSegmentMessageEditor/constants.js +2 -0
  91. package/app/v2Components/VarSegmentMessageEditor/index.js +125 -0
  92. package/app/v2Components/VarSegmentMessageEditor/index.scss +46 -0
  93. package/app/v2Containers/BeeEditor/index.js +3 -0
  94. package/app/v2Containers/BeePopupEditor/index.js +9 -2
  95. package/app/v2Containers/Cap/mockData.js +0 -14
  96. package/app/v2Containers/Cap/reducer.js +3 -55
  97. package/app/v2Containers/Cap/tests/reducer.test.js +0 -102
  98. package/app/v2Containers/CommunicationFlow/CommunicationFlow.js +291 -0
  99. package/app/v2Containers/CommunicationFlow/CommunicationFlow.scss +25 -0
  100. package/app/v2Containers/CommunicationFlow/Tests/CommunicationFlow.test.js +255 -0
  101. package/app/v2Containers/CommunicationFlow/constants.js +200 -0
  102. package/app/v2Containers/CommunicationFlow/index.js +102 -0
  103. package/app/v2Containers/CommunicationFlow/messages.js +346 -0
  104. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.js +522 -0
  105. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/ChannelSelectionStep.scss +170 -0
  106. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/Tests/ChannelSelectionStep.test.js +796 -0
  107. package/app/v2Containers/CommunicationFlow/steps/ChannelSelectionStep/index.js +5 -0
  108. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/CommunicationStrategyStep.js +95 -0
  109. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/Tests/CommunicationStrategyStep.test.js +133 -0
  110. package/app/v2Containers/CommunicationFlow/steps/CommunicationStrategyStep/index.js +5 -0
  111. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.js +289 -0
  112. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/DeliverySettingsSection.scss +70 -0
  113. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.js +319 -0
  114. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/SenderDetails.scss +69 -0
  115. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/DeliverySettingsSection.test.js +616 -0
  116. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/SenderDetails.test.js +577 -0
  117. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/Tests/deliverySettingsConfig.test.js +1111 -0
  118. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/deliverySettingsConfig.js +696 -0
  119. package/app/v2Containers/CommunicationFlow/steps/DeliverySettingsStep/index.js +7 -0
  120. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.js +102 -0
  121. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/DynamicControlsStep.scss +36 -0
  122. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/Tests/DynamicControlsStep.test.js +91 -0
  123. package/app/v2Containers/CommunicationFlow/steps/DynamicControlsStep/index.js +5 -0
  124. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/MessageTypeStep.js +86 -0
  125. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/Tests/MessageTypeStep.test.js +100 -0
  126. package/app/v2Containers/CommunicationFlow/steps/MessageTypeStep/index.js +5 -0
  127. package/app/v2Containers/CommunicationFlow/utils/getEnabledSteps.js +30 -0
  128. package/app/v2Containers/CreativesContainer/CreativesSlideBoxWrapper.js +43 -0
  129. package/app/v2Containers/CreativesContainer/SlideBoxContent.js +127 -11
  130. package/app/v2Containers/CreativesContainer/SlideBoxFooter.js +62 -9
  131. package/app/v2Containers/CreativesContainer/SlideBoxHeader.js +29 -4
  132. package/app/v2Containers/CreativesContainer/constants.js +24 -0
  133. package/app/v2Containers/CreativesContainer/embeddedSlideboxUtils.js +67 -0
  134. package/app/v2Containers/CreativesContainer/index.js +346 -71
  135. package/app/v2Containers/CreativesContainer/index.scss +51 -1
  136. package/app/v2Containers/CreativesContainer/messages.js +12 -0
  137. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.localTemplates.test.js +90 -0
  138. package/app/v2Containers/CreativesContainer/tests/SlideBoxContent.test.js +69 -1
  139. package/app/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +443 -0
  140. package/app/v2Containers/CreativesContainer/tests/SlideBoxHeader.test.js +110 -0
  141. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +147 -4
  142. package/app/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxHeader.test.js.snap +363 -0
  143. package/app/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +57 -10
  144. package/app/v2Containers/CreativesContainer/tests/embeddedSlideboxUtils.test.js +258 -0
  145. package/app/v2Containers/CreativesContainer/tests/index.test.js +71 -9
  146. package/app/v2Containers/CreativesContainer/tests/useLocalTemplatesProp.test.js +125 -0
  147. package/app/v2Containers/Email/index.js +2 -5
  148. package/app/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +58 -77
  149. package/app/v2Containers/EmailWrapper/components/EmailWrapperView.js +3 -0
  150. package/app/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +158 -89
  151. package/app/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +16 -1
  152. package/app/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +17 -12
  153. package/app/v2Containers/EmailWrapper/index.js +4 -0
  154. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +1 -0
  155. package/app/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +133 -0
  156. package/app/v2Containers/FTP/index.js +2 -51
  157. package/app/v2Containers/FTP/messages.js +0 -4
  158. package/app/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +110 -155
  159. package/app/v2Containers/InApp/index.js +297 -118
  160. package/app/v2Containers/InApp/tests/index.test.js +17 -6
  161. package/app/v2Containers/InApp/tests/mockData.js +1 -1
  162. package/app/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +19 -0
  163. package/app/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +3 -0
  164. package/app/v2Containers/InAppWrapper/index.js +3 -0
  165. package/app/v2Containers/InappAdvance/index.js +5 -104
  166. package/app/v2Containers/InappAdvance/tests/index.test.js +2 -0
  167. package/app/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +24 -3
  168. package/app/v2Containers/Line/Container/Text/index.js +0 -1
  169. package/app/v2Containers/MobilePush/Create/index.js +105 -28
  170. package/app/v2Containers/MobilePush/Create/messages.js +4 -0
  171. package/app/v2Containers/MobilePush/Edit/index.js +250 -68
  172. package/app/v2Containers/MobilePush/Edit/messages.js +4 -0
  173. package/app/v2Containers/MobilePushNew/components/PlatformContentFields.js +36 -12
  174. package/app/v2Containers/MobilePushNew/components/tests/PlatformContentFields.test.js +68 -27
  175. package/app/v2Containers/MobilePushNew/index.js +78 -35
  176. package/app/v2Containers/MobilePushNew/messages.js +8 -0
  177. package/app/v2Containers/MobilepushWrapper/index.js +11 -1
  178. package/app/v2Containers/Rcs/constants.js +32 -1
  179. package/app/v2Containers/Rcs/index.js +963 -916
  180. package/app/v2Containers/Rcs/index.scss +85 -6
  181. package/app/v2Containers/Rcs/messages.js +10 -1
  182. package/app/v2Containers/Rcs/rcsLibraryHydrationUtils.js +205 -0
  183. package/app/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +41136 -1566
  184. package/app/v2Containers/Rcs/tests/__snapshots__/utils.test.js.snap +0 -5
  185. package/app/v2Containers/Rcs/tests/index.test.js +41 -38
  186. package/app/v2Containers/Rcs/tests/mockData.js +38 -0
  187. package/app/v2Containers/Rcs/tests/rcsLibraryHydrationUtils.test.js +251 -0
  188. package/app/v2Containers/Rcs/tests/utils.test.js +379 -1
  189. package/app/v2Containers/Rcs/utils.js +358 -10
  190. package/app/v2Containers/Sms/Create/index.js +122 -39
  191. package/app/v2Containers/Sms/Create/messages.js +4 -0
  192. package/app/v2Containers/Sms/Edit/index.js +37 -3
  193. package/app/v2Containers/Sms/commonMethods.js +3 -6
  194. package/app/v2Containers/Sms/smsFormDataHelpers.js +67 -0
  195. package/app/v2Containers/Sms/tests/commonMethods.test.js +122 -0
  196. package/app/v2Containers/Sms/tests/smsFormDataHelpers.test.js +253 -0
  197. package/app/v2Containers/SmsTrai/Create/index.js +9 -4
  198. package/app/v2Containers/SmsTrai/Create/index.scss +1 -1
  199. package/app/v2Containers/SmsTrai/Edit/constants.js +2 -0
  200. package/app/v2Containers/SmsTrai/Edit/index.js +667 -160
  201. package/app/v2Containers/SmsTrai/Edit/index.scss +121 -0
  202. package/app/v2Containers/SmsTrai/Edit/messages.js +9 -4
  203. package/app/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4590 -2436
  204. package/app/v2Containers/SmsWrapper/index.js +41 -8
  205. package/app/v2Containers/TagList/index.js +63 -2
  206. package/app/v2Containers/TagList/messages.js +8 -0
  207. package/app/v2Containers/TagList/tests/TagList.test.js +122 -20
  208. package/app/v2Containers/TagList/tests/mockdata.js +17 -0
  209. package/app/v2Containers/Templates/TemplatesActionBar.js +101 -0
  210. package/app/v2Containers/Templates/_templates.scss +61 -2
  211. package/app/v2Containers/Templates/actions.js +11 -0
  212. package/app/v2Containers/Templates/constants.js +2 -0
  213. package/app/v2Containers/Templates/index.js +90 -40
  214. package/app/v2Containers/Templates/reducer.js +3 -1
  215. package/app/v2Containers/Templates/sagas.js +57 -12
  216. package/app/v2Containers/Templates/tests/TemplatesActionBar.test.js +120 -0
  217. package/app/v2Containers/Templates/tests/__snapshots__/index.test.js.snap +1043 -1079
  218. package/app/v2Containers/Templates/tests/reducer.test.js +12 -0
  219. package/app/v2Containers/Templates/tests/sagas.test.js +193 -12
  220. package/app/v2Containers/Templates/tests/smsTemplatesListApi.test.js +180 -0
  221. package/app/v2Containers/Templates/utils/smsTemplatesListApi.js +79 -0
  222. package/app/v2Containers/TemplatesV2/TemplatesV2.style.js +72 -1
  223. package/app/v2Containers/TemplatesV2/index.js +147 -49
  224. package/app/v2Containers/TemplatesV2/tests/TemplatesV2.localTemplates.test.js +131 -0
  225. package/app/v2Containers/Viber/index.js +9 -10
  226. package/app/v2Containers/Viber/index.scss +1 -1
  227. package/app/v2Containers/WebPush/Create/components/BrandIconSection.test.js +264 -0
  228. package/app/v2Containers/WebPush/Create/components/MessageSection.js +78 -19
  229. package/app/v2Containers/WebPush/Create/components/MessageSection.test.js +82 -0
  230. package/app/v2Containers/WebPush/Create/components/__snapshots__/BrandIconSection.test.js.snap +187 -0
  231. package/app/v2Containers/WebPush/Create/components/__snapshots__/MessageSection.test.js.snap +25 -17
  232. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.js +80 -0
  233. package/app/v2Containers/WebPush/Create/hooks/useAiraTriggerPosition.test.js +210 -0
  234. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.js +1 -5
  235. package/app/v2Containers/WebPush/Create/hooks/useTagManagement.test.js +0 -7
  236. package/app/v2Containers/WebPush/Create/index.js +36 -6
  237. package/app/v2Containers/WebPush/Create/index.scss +5 -0
  238. package/app/v2Containers/WebPush/Create/messages.js +8 -1
  239. package/app/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +269 -0
  240. package/app/v2Containers/WebPush/Create/utils/validation.js +31 -15
  241. package/app/v2Containers/WebPush/Create/utils/validation.test.js +72 -24
  242. package/app/v2Containers/Whatsapp/index.js +28 -53
  243. package/app/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +26939 -3982
  244. package/app/v2Containers/Whatsapp/tests/index.test.js +172 -0
  245. package/app/v2Containers/Zalo/index.js +5 -11
  246. package/package.json +2 -2
  247. package/version +9 -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,44 +21,34 @@ 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,
57
37
  setInjectedTags,
58
- selectCurrentOrgDetails,
59
38
  } from '../Cap/selectors';
60
39
  import * as RcsActions from './actions';
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,
@@ -76,14 +65,11 @@ import {
76
65
  RCS_IMG_SIZE,
77
66
  RCS_DLT_MODE,
78
67
  CTA,
79
- AI_CONTENT_BOT_DISABLED,
80
68
  RCS_STATUSES,
81
69
  TITLE_TEXT,
82
70
  MESSAGE_TEXT,
83
71
  ALLOWED_EXTENSIONS_VIDEO_REGEX,
84
72
  RCS_VIDEO_SIZE,
85
- TEMPLATE_HEADER_MAX_LENGTH,
86
- TEMPLATE_MESSAGE_MAX_LENGTH,
87
73
  RCS_THUMBNAIL_MIN_SIZE,
88
74
  RCS_THUMBNAIL_MAX_SIZE,
89
75
  contentType,
@@ -96,35 +82,45 @@ import {
96
82
  MAX_BUTTONS,
97
83
  INITIAL_SUGGESTIONS_DATA_STOP,
98
84
  RCS_BUTTON_TYPES,
99
- titletype,
100
- descType,
101
85
  STANDALONE,
102
86
  VERTICAL,
103
87
  SMALL,
104
88
  MEDIUM,
105
89
  RICHCARD,
90
+ RCS_NUMERIC_VAR_NAME_REGEX,
91
+ RCS_TAG_AREA_FIELD_TITLE,
92
+ RCS_TAG_AREA_FIELD_DESC,
106
93
  } from './constants';
107
94
  import globalMessages from '../Cap/messages';
108
95
  import messages from './messages';
109
96
  import creativesMessages from '../CreativesContainer/messages';
110
97
  import withCreatives from '../../hoc/withCreatives';
111
98
  import UnifiedPreview from '../../v2Components/CommonTestAndPreview/UnifiedPreview';
112
- 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';
113
101
  import TestAndPreviewSlidebox from '../../v2Components/TestAndPreviewSlidebox';
102
+ import { splitTemplateVarString } from '../../utils/templateVarUtils';
114
103
  import CapImageUpload from '../../v2Components/CapImageUpload';
115
- import addCreativesIcon from '../Assets/images/addCreativesIllustration.svg';
116
104
  import Templates from '../Templates';
117
105
  import SmsTraiEdit from '../SmsTrai/Edit';
106
+ import SmsFallback from '../../v2Components/SmsFallback';
107
+ import { CHANNELS_TO_HIDE_FOR_SMS_ONLY } from '../../v2Components/SmsFallback/constants';
118
108
  import TagList from '../TagList';
119
109
  import { validateTags } from '../../utils/tagValidations';
120
- import { getCdnUrl } from '../../utils/cdnTransformation';
110
+ import { isTraiDLTEnable } from '../../utils/common';
121
111
  import { isTagIncluded } from '../../utils/commonUtils';
122
112
  import injectReducer from '../../utils/injectReducer';
123
113
  import v2RcsReducer from './reducer';
124
- import { getTemplateStatusType } from './utils';
125
-
114
+ import {
115
+ areAllRcsSmsFallbackVarSlotsFilled,
116
+ buildRcsNumericMustachePlaceholderRegex,
117
+ getTemplateStatusType,
118
+ normalizeCardVarMapped,
119
+ coalesceCardVarMappedToTemplate,
120
+ resolveCardVarMappedSlotValue,
121
+ sanitizeCardVarMappedValue,
122
+ } from './utils';
126
123
 
127
- const { Group: CapCheckboxGroup } = CapCheckbox;
128
124
  export const Rcs = (props) => {
129
125
  const {
130
126
  intl,
@@ -138,18 +134,17 @@ export const Rcs = (props) => {
138
134
  templatesActions,
139
135
  globalActions,
140
136
  location,
141
- handleClose,
142
137
  getDefaultTags,
143
138
  supportedTags,
144
139
  metaEntities,
145
140
  injectedTags,
146
141
  loadingTags,
147
142
  getFormData,
148
- isDltEnabled,
149
143
  smsRegister,
144
+ orgUnitId,
150
145
  selectedOfferDetails,
151
- currentOrgDetails,
152
146
  eventContextTags,
147
+ waitEventContextTags,
153
148
  accountData = {},
154
149
  // TestAndPreviewSlidebox props
155
150
  showTestAndPreviewSlidebox: propsShowTestAndPreviewSlidebox,
@@ -158,8 +153,8 @@ export const Rcs = (props) => {
158
153
  } = props || {};
159
154
  const { formatMessage } = intl;
160
155
  const { TextArea } = CapInput;
161
- const { CapCustomCardList } = CapCustomCard;
162
156
  const [isEditFlow, setEditFlow] = useState(false);
157
+ const isEditLike = isEditFlow || !isFullMode;
163
158
  const [tags, updateTags] = useState([]);
164
159
  const [spin, setSpin] = useState(false);
165
160
  //template
@@ -168,112 +163,71 @@ export const Rcs = (props) => {
168
163
  const [templateMediaType, setTemplateMediaType] = useState(
169
164
  RCS_MEDIA_TYPES.NONE,
170
165
  );
171
- const [templateRejectionReason, setTemplateRejectionReason] = useState(null);
172
166
  const [templateTitle, setTemplateTitle] = useState('');
173
167
  const [templateDesc, setTemplateDesc] = useState('');
174
168
  const [templateDescError, setTemplateDescError] = useState(false);
175
169
  const [templateStatus, setTemplateStatus] = useState('');
176
- const [templateDate, setTemplateDate] = useState('');
177
- //fallback
178
- const [fallbackMessage, setFallbackMessage] = useState('');
179
- const [fallbackMessageError, setFallbackMessageError] = useState(false);
180
170
  //fallback dlt
181
171
  const [showDltContainer, setShowDltContainer] = useState(false);
182
172
  const [dltMode, setDltMode] = useState('');
183
173
  const [dltEditData, setDltEditData] = useState({});
184
- const [showDltCard, setShowDltCard] = useState(false);
185
- const [fallbackPreviewmode, setFallbackPreviewmode] = useState(false);
186
- const [dltPreviewData, setDltPreviewData] = useState('');
174
+ /** `undefined` = not hydrated yet; `null` = no fallback / user removed template; object = selected fallback */
175
+ const [smsFallbackData, setSmsFallbackData] = useState(undefined);
187
176
  const [suggestions, setSuggestions] = useState(INITIAL_SUGGESTIONS_DATA_STOP);
188
- const [buttonType, setButtonType] = useState(RCS_BUTTON_TYPES.NONE);
177
+ const buttonType = RCS_BUTTON_TYPES.NONE;
189
178
  const [suggestionError, setSuggestionError] = useState(true);
190
179
  const [templateType, setTemplateType] = useState('text_message');
191
- const [templateHeader, setTemplateHeader] = useState('');
192
- const [templateMessage, setTemplateMessage] = useState('');
193
- const [templateHeaderError, setTemplateHeaderError] = useState('');
194
- const [templateMessageError, setTemplateMessageError] = useState('');
195
- const validVarRegex = /\{\{(\d+)\}\}/g;
196
- const [updatedTitleData, setUpdatedTitleData] = useState([]);
197
- const [updatedDescData, setUpdatedDescData] = useState([]);
198
180
  const [titleVarMappedData, setTitleVarMappedData] = useState({});
199
181
  const [descVarMappedData, setDescVarMappedData] = useState({});
200
182
  const [titleTextAreaId, setTitleTextAreaId] = useState();
201
183
  const [descTextAreaId, setDescTextAreaId] = useState();
202
184
  const [assetList, setAssetList] = useState({});
203
- const [assetListImage, setAssetListImage] = useState('');
204
185
  const [rcsImageSrc, updateRcsImageSrc] = useState('');
205
186
  const [rcsVideoSrc, setRcsVideoSrc] = useState({});
206
187
  const [rcsThumbnailSrc, setRcsThumbnailSrc] = useState('');
207
188
  const [selectedDimension, setSelectedDimension] = useState(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
208
- const [imageError, setImageError] = useState(null);
209
189
  const [templateTitleError, setTemplateTitleError] = useState(false);
210
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
+ );
211
204
 
212
- // TestAndPreviewSlidebox state
213
- const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
214
- const [isTestAndPreviewMode, setIsTestAndPreviewMode] = useState(false);
215
-
216
- const tempMsg = dltPreviewData === '' ? fallbackMessage : dltPreviewData;
217
-
218
- // Get template content for TestAndPreviewSlidebox
219
- // Reference: Based on getRcsPreview() function (lines 2087-2111) which prepares content for TemplatePreview
220
- // getRcsPreview ALWAYS uses templateTitle and templateDesc for ALL template types (text_message, rich_card, carousel)
221
- // renderTextComponent (lines 1317-1485) also uses templateTitle and templateDesc
222
- // Note: templateHeader and templateMessage are defined but NOT used in the component
223
- const getTemplateContent = useCallback(() => {
224
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
225
- const isMediaTypeVideo = templateMediaType === RCS_MEDIA_TYPES.VIDEO;
226
- const isMediaTypeText = templateMediaType === RCS_MEDIA_TYPES.NONE;
227
-
228
- // Build media preview object (same pattern as getRcsPreview)
229
- const mediaPreview = {};
230
- if (isMediaTypeImage && rcsImageSrc) {
231
- mediaPreview.rcsImageSrc = rcsImageSrc;
232
- }
233
- if (isMediaTypeVideo && !isMediaTypeText) {
234
- // For video, use thumbnailSrc as rcsVideoSrc (same as getRcsPreview line 2104)
235
- if (rcsThumbnailSrc) {
236
- mediaPreview.rcsVideoSrc = rcsThumbnailSrc;
237
- } else if (rcsVideoSrc?.videoSrc) {
238
- mediaPreview.rcsVideoSrc = rcsVideoSrc.videoSrc;
239
- }
240
- // Also include thumbnailSrc separately if available
241
- if (rcsThumbnailSrc) {
242
- 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;
243
218
  }
244
- }
245
-
246
- // Build content object
247
- // Reference: getRcsPreview (line 2091-2092) uses templateTitle and templateDesc for ALL cases
248
- // templateTitle is used for rich_card/carousel title, empty for text_message
249
- // templateDesc is used for ALL types (text message body or rich card description)
250
- // For UnifiedPreview, we map templateTitle -> templateHeader and templateDesc -> templateMessage
251
- const contentObj = {
252
- // Map templateTitle to templateHeader and templateDesc to templateMessage
253
- templateHeader: templateTitle,
254
- templateMessage: templateDesc,
255
- ...mediaPreview,
256
- ...(suggestions.length > 0 && {
257
- suggestions: suggestions,
258
- }),
259
- };
219
+ lastTagSchemaQueryKeyRef.current = key;
220
+ globalActions.fetchSchemaForEntity(query);
221
+ },
222
+ [globalActions],
223
+ );
260
224
 
261
- return contentObj;
262
- }, [
263
- templateMediaType,
264
- templateTitle,
265
- templateDesc,
266
- rcsImageSrc,
267
- rcsVideoSrc,
268
- rcsThumbnailSrc,
269
- suggestions,
270
- selectedDimension,
271
- ]);
225
+ // TestAndPreviewSlidebox state
226
+ const [showTestAndPreviewSlidebox, setShowTestAndPreviewSlidebox] = useState(false);
272
227
 
273
228
  // Handle Test and Preview button click
274
229
  const handleTestAndPreview = useCallback(() => {
275
230
  setShowTestAndPreviewSlidebox(true);
276
- setIsTestAndPreviewMode(true);
277
231
  if (propsHandleTestAndPreview) {
278
232
  propsHandleTestAndPreview();
279
233
  }
@@ -282,23 +236,24 @@ export const Rcs = (props) => {
282
236
  // Handle close Test and Preview slidebox
283
237
  const handleCloseTestAndPreview = useCallback(() => {
284
238
  setShowTestAndPreviewSlidebox(false);
285
- setIsTestAndPreviewMode(false);
286
239
  if (propsHandleCloseTestAndPreview) {
287
240
  propsHandleCloseTestAndPreview();
288
241
  }
289
242
  }, [propsHandleCloseTestAndPreview]);
290
243
 
291
- // Helper to get RCS orientation from selectedDimension
292
- const getRcsOrientation = () => {
293
- const isMediaTypeImage = templateMediaType === RCS_MEDIA_TYPES.IMAGE;
294
- if (isMediaTypeImage) {
295
- return RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
296
- }
297
- // For video
298
- return RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL;
299
- };
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
+ }, []);
300
252
 
253
+ /** RCS template save / edit API: `rcsContent.accountId` must stay `sourceAccountIdentifier` (pairs with accessToken). */
301
254
  const [accountId, setAccountId] = useState('');
255
+ /** WeCRM list row `id` — only for CommonTestAndPreview → createMessageMeta payload, not for template save. */
256
+ const [wecrmAccountId, setWecrmAccountId] = useState('');
302
257
  const [accessToken, setAccessToken] = useState('');
303
258
  const [hostName, setHostName] = useState('');
304
259
  const [accountName, setAccountName] = useState('');
@@ -306,14 +261,23 @@ export const Rcs = (props) => {
306
261
  const accountObj = accountData.selectedRcsAccount || {};
307
262
  if (!isEmpty(accountObj)) {
308
263
  const {
264
+ id: wecrmId,
309
265
  sourceAccountIdentifier = '',
310
266
  configs = {},
311
267
  } = accountObj;
312
-
313
268
  setAccountId(sourceAccountIdentifier);
269
+ setWecrmAccountId(
270
+ wecrmId != null && String(wecrmId).trim() !== '' ? String(wecrmId) : '',
271
+ );
314
272
  setAccessToken(configs.accessToken || '');
315
273
  setHostName(accountObj.hostName || '');
316
274
  setAccountName(accountObj.name || '');
275
+ } else {
276
+ setAccountId('');
277
+ setWecrmAccountId('');
278
+ setAccessToken('');
279
+ setHostName('');
280
+ setAccountName('');
317
281
  }
318
282
  }, [accountData.selectedRcsAccount]);
319
283
 
@@ -331,10 +295,7 @@ export const Rcs = (props) => {
331
295
  label: formatMessage(messages.mediaVideo),
332
296
  },
333
297
  ];
334
- const { accessibleFeatures = [] } = currentOrgDetails || {};
335
- const isAiContentBotDisabled = accessibleFeatures?.includes(
336
- AI_CONTENT_BOT_DISABLED
337
- );
298
+ const aiContentBotDisabled = isAiContentBotDisabled();
338
299
 
339
300
  const updateButtonChange = (data, index) => {
340
301
  if (data && data.text) {
@@ -372,7 +333,9 @@ export const Rcs = (props) => {
372
333
  if (isFullMode) return;
373
334
  if (loadingTags || !tags || tags.length === 0) return;
374
335
  const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
375
- 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 '')
376
339
  if (!resolved) {
377
340
  if (type === TITLE_TEXT) setTemplateTitleError(false);
378
341
  if (type === MESSAGE_TEXT) setTemplateDescError(false);
@@ -394,23 +357,15 @@ export const Rcs = (props) => {
394
357
  const validationResponse =
395
358
  validateTags({
396
359
  content: contentForValidation,
397
- tagsParam: tags,
398
- injectedTagsParams: injectedTags,
399
- location,
400
- tagModule: getDefaultTags,
401
- eventContextTags,
402
- isFullMode,
403
- }) || {};
404
- const unsupportedTagsLengthCheck =
405
- validationResponse?.unsupportedTags?.length > 0;
406
- const errorMsg =
407
- (unsupportedTagsLengthCheck &&
408
- formatMessage(globalMessages.unsupportedTagsValidationError, {
409
- unsupportedTags: validationResponse.unsupportedTags,
410
- })) ||
411
- (validationResponse.isBraceError &&
412
- formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
413
- false;
360
+ tagsParam: tags,
361
+ location,
362
+ tagModule: getDefaultTags,
363
+ isFullMode,
364
+ }) || {};
365
+ const errorMsg =
366
+ (validationResponse?.isBraceError &&
367
+ formatMessage(globalMessages.unbalanacedCurlyBraces)) ||
368
+ false;
414
369
  if (type === TITLE_TEXT) setTemplateTitleError(errorMsg);
415
370
  if (type === MESSAGE_TEXT) setTemplateDescError(errorMsg);
416
371
  };
@@ -425,13 +380,41 @@ export const Rcs = (props) => {
425
380
 
426
381
  const getVarNameFromToken = (token = '') => token.replace(/^\{\{|\}\}$/g, '');
427
382
 
428
- 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) => {
429
404
  if (!str) return '';
430
- const arr = splitTemplateVarString(str);
405
+ const arr = splitTemplateVarStringRcs(str);
406
+ let varOrdinal = 0;
431
407
  return arr.map((elem) => {
432
408
  if (rcsVarTestRegex.test(elem)) {
433
409
  const key = getVarNameFromToken(elem);
434
- 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
+ );
435
418
  if (isNil(v) || String(v)?.trim?.() === '') return elem;
436
419
  return String(v);
437
420
  }
@@ -439,107 +422,154 @@ export const Rcs = (props) => {
439
422
  }).join('');
440
423
  };
441
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;
442
434
 
443
- useEffect(() => {
444
- if (isFullMode || isEditFlow) return;
445
- const tokens = [
446
- ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
447
- ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
448
- ];
449
- if (!tokens.length) return;
450
- setCardVarMapped((prev) => {
451
- const next = { ...(prev || {}) };
452
- let changed = false;
453
- tokens.forEach((t) => {
454
- const name = getVarNameFromToken(t);
455
- if (name && !Object.prototype.hasOwnProperty.call(next, name)) {
456
- next[name] = '';
457
- changed = true;
458
- }
459
- });
460
- return changed ? next : prev;
461
- });
462
- }, [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;
463
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
+ ]);
484
+
485
+ const testAndPreviewContent = useMemo(() => getTemplateContent(), [getTemplateContent]);
464
486
 
465
- const RcsLabel = styled.div`
466
- display: flex;
467
- margin-top: 20px;
468
- `;
469
487
  const paramObj = params || {};
470
488
  useEffect(() => {
471
- const { id } = paramObj;
472
- if (id && isFullMode) {
473
- setSpin(true);
474
- actions.getTemplateDetails(id, setSpin);
475
- }
476
- return () => {
477
- actions.clearEditResponse();
478
- };
479
- }, [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]);
480
498
 
481
499
  useEffect(() => {
482
- if (!(isEditFlow || !isFullMode)) return;
483
-
484
- const initField = (targetString, currentVarMap, setVarMap, setUpdated) => {
485
- const arr = splitTemplateVarString(targetString);
486
- if (!arr?.length) {
487
- setVarMap({});
488
- setUpdated([]);
489
- 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;
490
526
  }
491
- const nextVarMap = {};
492
- const nextUpdated = [...arr];
493
- arr.forEach((elem, idx) => {
494
- // RCS placeholders are alphanumeric/underscore (e.g. {{user_name}}), not numeric-only
495
- if (rcsVarTestRegex.test(elem)) {
496
- const id = `${elem}_${idx}`;
497
- const varName = getVarNameFromToken(elem);
498
- const mappedValue = (cardVarMapped?.[varName] ?? '').toString();
499
- nextVarMap[id] = mappedValue;
500
- if (mappedValue !== '') {
501
- nextUpdated[idx] = mappedValue;
502
- } else {
503
- nextUpdated[idx] = elem;
504
- }
505
- }
506
- });
507
- setVarMap(nextVarMap);
508
- setUpdated(nextUpdated);
509
- };
527
+ });
528
+ setVarMap(nextVarMap);
529
+ };
510
530
 
511
- initField(templateTitle, titleVarMappedData, setTitleVarMappedData, setUpdatedTitleData);
512
- initField(templateDesc, descVarMappedData, setDescVarMappedData, setUpdatedDescData);
513
- }, [templateTitle, templateDesc, cardVarMapped, isEditFlow, isFullMode]);
514
-
515
- useEffect(() => {
516
- 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) {
517
537
  setRcsVideoSrc({});
518
538
  updateRcsImageSrc('');
519
539
  setUpdateRcsImageSrc('');
520
540
  updateRcsThumbnailSrc('');
521
541
  setAssetList({});
522
- }
523
- }, [templateMediaType]);
542
+ }
543
+ }, [templateMediaType]);
524
544
 
525
- const templateStatusHelper = (details) => {
526
- const status = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].Status', '');
527
- switch (status) {
528
- 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':
529
556
  setTemplateStatus(RCS_STATUSES.approved);
530
557
  break;
531
- case RCS_STATUSES.pending:
558
+ case 'pending':
532
559
  setTemplateStatus(RCS_STATUSES.pending);
533
560
  break;
534
- case RCS_STATUSES.awaitingApproval:
561
+ case 'awaitingapproval':
535
562
  setTemplateStatus(RCS_STATUSES.awaitingApproval);
536
563
  break;
537
- case RCS_STATUSES.unavailable:
564
+ case 'unavailable':
538
565
  setTemplateStatus(RCS_STATUSES.unavailable);
539
566
  break;
540
- case RCS_STATUSES.rejected:
567
+ case 'rejected':
541
568
  setTemplateStatus(RCS_STATUSES.rejected);
542
569
  break;
570
+ case 'created':
571
+ setTemplateStatus(RCS_STATUSES.created);
572
+ break;
543
573
  default:
544
574
  setTemplateStatus(status);
545
575
  break;
@@ -550,7 +580,6 @@ export const Rcs = (props) => {
550
580
  if (mediaType) {
551
581
  setTemplateMediaType(mediaType);
552
582
  }
553
- const tempOrientation = cardSettings.cardOrientation;
554
583
  const tempAlignment = cardSettings.mediaAlignment;
555
584
  const tempHeight = mediaData.height;
556
585
 
@@ -593,44 +622,197 @@ export const Rcs = (props) => {
593
622
  };
594
623
 
595
624
  useEffect(() => {
596
- const details = isFullMode ? rcsData?.templateDetails : templateData;
625
+ const details = rcsHydrationDetails;
597
626
  if (details && Object.keys(details).length > 0) {
598
- if (!isFullMode) {
599
- const tempCardVarMapped = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
600
- 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);
601
661
  }
602
- 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', '');
603
704
  if (mediaType === RCS_MEDIA_TYPES.NONE) {
604
705
  setTemplateType(contentType.text_message);
605
706
  } else {
606
707
  setTemplateType(contentType.rich_card);
607
708
  }
608
709
  setEditFlow(true);
609
- setTemplateName(details.name || '');
610
- const loadedTitle = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].title', '');
611
- const loadedDesc = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].description', '');
612
- const loadedMap = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].cardVarMapped', {});
613
- const normalizedTitle = (!isFullMode && !isEmpty(loadedMap)) ? getUnmappedDesc(loadedTitle, loadedMap) : loadedTitle;
614
- 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
+ });
615
720
  setTemplateTitle(normalizedTitle);
616
721
  setTemplateDesc(normalizedDesc);
617
- setSuggestions(get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].suggestions', []));
618
- templateStatusHelper(details);
619
- const mediaData = get(details, 'versions.base.content.RCS.rcsContent.cardContent[0].media', '');
620
- 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', '');
621
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
+ }
622
804
  }
623
- }, [rcsData, templateData, isFullMode, isEditFlow]);
624
-
805
+ }, [rcsHydrationDetails, isFullMode]);
625
806
 
626
807
  useEffect(() => {
627
808
  if (templateType === contentType.text_message) {
628
809
  setTemplateMediaType(RCS_MEDIA_TYPES.NONE);
629
- setTemplateTitle('');
630
- 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.
631
812
  if (!isEditFlow && isFullMode) {
813
+ setTemplateTitle('');
814
+ setTemplateTitleError('');
632
815
  setUpdateRcsImageSrc('');
633
- setUpdateRcsVideoSrc({});
634
816
  setRcsVideoSrc({});
635
817
  setSelectedDimension(RCS_IMAGE_DIMENSIONS.MEDIUM_HEIGHT.type);
636
818
  }
@@ -657,7 +839,8 @@ export const Rcs = (props) => {
657
839
  if (!showDltContainer) {
658
840
  const { type, module } = location.query || {};
659
841
  const isEmbedded = type === EMBEDDED;
660
- 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';
661
844
  const embedded = isEmbedded ? type : FULL;
662
845
  const query = {
663
846
  layout: SMS,
@@ -668,9 +851,9 @@ export const Rcs = (props) => {
668
851
  if (getDefaultTags) {
669
852
  query.context = getDefaultTags;
670
853
  }
671
- globalActions.fetchSchemaForEntity(query);
854
+ fetchTagSchemaIfNewQuery(query);
672
855
  }
673
- }, [showDltContainer]);
856
+ }, [showDltContainer, fetchTagSchemaIfNewQuery]);
674
857
 
675
858
  useEffect(() => {
676
859
  let tag = get(metaEntities, `tags.standard`, []);
@@ -693,39 +876,72 @@ export const Rcs = (props) => {
693
876
  context,
694
877
  embedded,
695
878
  };
696
- globalActions.fetchSchemaForEntity(query);
879
+ if (getDefaultTags) {
880
+ query.context = getDefaultTags;
881
+ }
882
+ fetchTagSchemaIfNewQuery(query);
883
+ };
884
+
885
+ const replaceNumericPlaceholderWithTagInTemplate = (templateStr, numericVarName, tagName) => {
886
+ if (!templateStr || !numericVarName || !tagName) return templateStr;
887
+ const re = buildRcsNumericMustachePlaceholderRegex(numericVarName);
888
+ return templateStr.replace(re, `{{${tagName}}}`);
697
889
  };
698
890
 
699
- const onTagSelect = (data, areaId) => {
891
+ const onTagSelect = (data, areaId, field) => {
700
892
  if (!areaId) return;
701
893
  const sep = areaId.lastIndexOf('_');
702
894
  if (sep === -1) return;
703
- const numId = Number(areaId.slice(sep + 1));
704
- if (isNaN(numId)) return;
895
+ const slotSuffix = areaId.slice(sep + 1);
896
+ if (slotSuffix === '' || isNaN(Number(slotSuffix))) return;
705
897
  const token = areaId.slice(0, sep);
706
898
  const variableName = getVarNameFromToken(token);
707
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
+
708
905
  setCardVarMapped((prev) => {
709
906
  const base = (prev?.[variableName] ?? '').toString();
710
907
  const nextVal = `${base}{{${data}}}`;
711
- return {
712
- ...(prev || {}),
713
- [variableName]: nextVal,
714
- };
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;
715
921
  });
716
- };
717
-
718
- const onTitleTagSelect = (data) => onTagSelect(data, titleTextAreaId);
719
-
720
- const onDescTagSelect = (data) => onTagSelect(data, descTextAreaId);
721
922
 
722
- const onTagSelectFallback = (data) => {
723
- const tempMsg = `${fallbackMessage}{{${data}}}`;
724
- const error = fallbackMessageErrorHandler(tempMsg);
725
- setFallbackMessage(tempMsg);
726
- 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
+ }
727
940
  };
728
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);
729
945
 
730
946
  //removing optout tag for rcs
731
947
  const getRcsTags = () => {
@@ -738,15 +954,14 @@ export const Rcs = (props) => {
738
954
  };
739
955
  // tag Code end
740
956
 
741
- const renderLabel = (value, showLabel, desc) => {
742
- const isTemplateApproved = (templateStatus === RCS_STATUSES.approved);
957
+ const renderLabel = (value, desc) => {
743
958
  return (
744
959
  <>
745
- <RcsLabel>
960
+ <div className="rcs-form-section-heading">
746
961
  <CapHeading type="h4">{formatMessage(messages[value])}</CapHeading>
747
- </RcsLabel>
962
+ </div>
748
963
  {desc && (
749
- <CapLabel type="label3" style={{ marginBottom: '17px' }}>
964
+ <CapLabel type="label3" className="rcs-form-field-caption">
750
965
  {formatMessage(messages[desc])}
751
966
  </CapLabel>
752
967
  )}
@@ -832,64 +1047,6 @@ export const Rcs = (props) => {
832
1047
  setTemplateDescError(error);
833
1048
  };
834
1049
 
835
-
836
- const templateDescErrorHandler = (value) => {
837
- let errorMessage = false;
838
- const { unsupportedTags, isBraceError } = validateTags({
839
- content: value,
840
- tagsParam: tags,
841
- injectedTagsParams: injectedTags,
842
- location,
843
- tagModule: getDefaultTags,
844
- isFullMode,
845
- }) || {};
846
-
847
- const maxLength = templateType === contentType.text_message
848
- ? RCS_TEXT_MESSAGE_MAX_LENGTH
849
- : RCS_RICH_CARD_MAX_LENGTH;
850
-
851
- if (value === '' && isMediaTypeText) {
852
- errorMessage = formatMessage(messages.emptyTemplateDescErrorMessage);
853
- } else if (value?.length > maxLength) {
854
- errorMessage = formatMessage(messages.templateMessageLengthError);
855
- }
856
-
857
- if (isBraceError) {
858
- errorMessage = formatMessage(globalMessages.unbalanacedCurlyBraces);
859
- }
860
- return errorMessage;
861
- };
862
-
863
-
864
- const onFallbackMessageChange = ({ target: { value } }) => {
865
- const error = fallbackMessageErrorHandler(value);
866
- setFallbackMessage(value);
867
- setFallbackMessageError(error);
868
- };
869
-
870
- const fallbackMessageErrorHandler = (value) => {
871
- let errorMessage = false;
872
- const { unsupportedTags } = validateTags({
873
- content: value,
874
- tagsParam: tags,
875
- injectedTagsParams: injectedTags,
876
- location,
877
- tagModule: getDefaultTags,
878
- isFullMode,
879
- }) || {};
880
- if (value?.length > FALLBACK_MESSAGE_MAX_LENGTH) {
881
- errorMessage = formatMessage(messages.fallbackMsgLenError);
882
- } else if (unsupportedTags?.length > 0) {
883
- errorMessage = formatMessage(
884
- globalMessages.unsupportedTagsValidationError,
885
- {
886
- unsupportedTags,
887
- },
888
- );
889
- }
890
- return errorMessage;
891
- };
892
-
893
1050
  // Check for forbidden characters: square brackets [] and single curly braces {}
894
1051
  const forbiddenCharactersValidation = (value) => {
895
1052
  if (!value) return false;
@@ -940,39 +1097,12 @@ export const Rcs = (props) => {
940
1097
  }
941
1098
  return false;
942
1099
  };
943
-
944
- const templateHeaderErrorHandler = (value) => {
945
- let errorMessage = false;
946
- if (value?.length > TEMPLATE_HEADER_MAX_LENGTH) {
947
- errorMessage = formatMessage(messages.templateHeaderLengthError);
948
- } else {
949
- errorMessage = variableErrorHandling(value);
950
- }
951
- return errorMessage;
952
- };
953
-
954
-
955
- const templateMessageErrorHandler = (value) => {
956
- let errorMessage = false;
957
- if (value === '') {
958
- errorMessage = formatMessage(messages.emptyTemplateMessageErrorMessage);
959
- } else if (
960
- value?.length
961
- > TEMPLATE_MESSAGE_MAX_LENGTH
962
- ) {
963
- errorMessage = formatMessage(messages.templateMessageLengthError);
964
- } else {
965
- errorMessage = variableErrorHandling(value);
966
- }
967
- return errorMessage;
968
- };
969
-
970
1100
 
971
1101
  const onMessageAddVar = () => {
972
- onAddVar(MESSAGE_TEXT, templateDesc, rcsVarRegex);
1102
+ onAddVar(templateDesc);
973
1103
  };
974
1104
 
975
- const onAddVar = (type, messageContent, regex) => {
1105
+ const onAddVar = (messageContent) => {
976
1106
  // Always append the next variable at the end, like WhatsApp
977
1107
  const existingVars = messageContent.match(/\{\{(\d+)\}\}/g) || [];
978
1108
  const existingNumbers = existingVars.map(v => parseInt(v.match(/\d+/)[0], 10));
@@ -1012,66 +1142,6 @@ const onTitleAddVar = () => {
1012
1142
  setTemplateTitleError(error);
1013
1143
  };
1014
1144
 
1015
-
1016
- const splitTemplateVarString = (str) => {
1017
- if (!str) return [];
1018
- const validVarArr = str.match(rcsVarRegex) || [];
1019
- const templateVarArray = [];
1020
- let content = str;
1021
- while (content?.length !== 0) {
1022
- const index = content.indexOf(validVarArr?.[0]);
1023
- if (index !== -1) {
1024
- templateVarArray.push(content.substring(0, index));
1025
- templateVarArray.push(validVarArr?.[0]);
1026
- content = content.substring(index + validVarArr?.[0]?.length, content?.length);
1027
- validVarArr?.shift();
1028
- } else {
1029
- templateVarArray.push(content);
1030
- break;
1031
- }
1032
- }
1033
- return templateVarArray.filter(Boolean);
1034
- };
1035
-
1036
- const textAreaValue = (idValue, type) => {
1037
- if (idValue >= 0) {
1038
- const templateStr = type === TITLE_TEXT ? templateTitle : templateDesc;
1039
- const templateArr = splitTemplateVarString(templateStr);
1040
- const token = templateArr?.[idValue] || "";
1041
- if (token && rcsVarTestRegex.test(token)) {
1042
- const varName = getVarNameFromToken(token);
1043
- return (cardVarMapped?.[varName] ?? '').toString();
1044
- }
1045
- return "";
1046
- }
1047
- return "";
1048
- };
1049
-
1050
- const textAreaValueChange = (e, type) => {
1051
- const value = e?.target?.value ?? '';
1052
- const id = e?.target?.id || e?.currentTarget?.id || '';
1053
- if (!id) return;
1054
- const sep = id.lastIndexOf('_');
1055
- if (sep === -1) return;
1056
- const isInvalidValue = value?.trim() === "";
1057
- const token = id.slice(0, sep);
1058
- const variableName = getVarNameFromToken(token);
1059
-
1060
- if (variableName) {
1061
- setCardVarMapped((prev) => ({
1062
- ...prev,
1063
- [variableName]: isInvalidValue ? "" : value,
1064
- }));
1065
- }
1066
- };
1067
-
1068
- const setTextAreaId = (e, type) => {
1069
- const id = e?.target?.id || e?.currentTarget?.id || '';
1070
- if (!id) return;
1071
- if (type === TITLE_TEXT) setTitleTextAreaId(id);
1072
- else setDescTextAreaId(id);
1073
- };
1074
-
1075
1145
  const renderButtonComponent = () => {
1076
1146
  return (
1077
1147
  <>
@@ -1102,39 +1172,56 @@ const splitTemplateVarString = (str) => {
1102
1172
  );
1103
1173
  };
1104
1174
 
1105
- const renderedRCSEditMessage = (descArray, type) => {
1106
- const renderArray = [];
1107
- if (descArray?.length) {
1108
- descArray.forEach((elem, index) => {
1109
- if (rcsVarTestRegex.test(elem)) {
1110
- // Variable input
1111
- renderArray.push(
1112
- <TextArea
1113
- id={`${elem}_${index}`}
1114
- key={`${elem}_${index}`}
1115
- placeholder={`enter the value for ${elem}`}
1116
- autosize={{ minRows: 1, maxRows: 3 }}
1117
- onChange={e => textAreaValueChange(e, type)}
1118
- value={textAreaValue(index, type)}
1119
- onFocus={(e) => setTextAreaId(e, type)}
1120
- />
1121
- );
1122
- } else if (elem) {
1123
- // Static text
1124
- renderArray.push(
1125
- <TextArea
1126
- key={`static_${index}`}
1127
- value={elem}
1128
- autosize={{ minRows: 1, maxRows: 3 }}
1129
- disabled
1130
- className="rcs-edit-template-message-static-textarea"
1131
- style={{ background: '#fafafa', color: '#888' }}
1132
- />
1133
- );
1134
- }
1135
- });
1136
- }
1137
- 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
+ });
1138
1225
  };
1139
1226
 
1140
1227
  const renderTextComponent = () => {
@@ -1178,10 +1265,19 @@ const splitTemplateVarString = (str) => {
1178
1265
  </>
1179
1266
  }
1180
1267
  />
1181
- <div className="rcs_text_area_wrapper">
1182
1268
  {(isEditFlow || !isFullMode) ? (
1183
- 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
+ />
1184
1279
  ) : (
1280
+ <div className="rcs_text_area_wrapper">
1185
1281
  <CapInput
1186
1282
  className={`rcs-template-title-input ${
1187
1283
  !isTemplateApproved ? "rcs-edit-disabled" : ""
@@ -1194,8 +1290,8 @@ const splitTemplateVarString = (str) => {
1194
1290
  errorMessage={templateTitleError}
1195
1291
  disabled={isEditFlow || !isFullMode}
1196
1292
  />
1293
+ </div>
1197
1294
  )}
1198
- </div>
1199
1295
  {(isEditFlow || !isFullMode) && templateTitleError && (
1200
1296
  <CapError className="rcs-template-title-error">
1201
1297
  {templateTitleError}
@@ -1243,7 +1339,18 @@ const splitTemplateVarString = (str) => {
1243
1339
  <CapRow className="rcs-create-template-message-input">
1244
1340
  <div className="rcs_text_area_wrapper">
1245
1341
  {(isEditFlow || !isFullMode)
1246
- ? 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
+ )
1247
1354
  : (
1248
1355
  <>
1249
1356
  <TextArea
@@ -1265,7 +1372,7 @@ const splitTemplateVarString = (str) => {
1265
1372
  data-testid="rcs_text_area"
1266
1373
  disabled={(isEditFlow || !isFullMode)}
1267
1374
  />
1268
- {!isAiContentBotDisabled && !isEditFlow && (
1375
+ {!aiContentBotDisabled && !isEditFlow && (
1269
1376
  <CapAskAira.ContentGenerationBot
1270
1377
  text={templateDesc || ""}
1271
1378
  setText={(text) => onTemplateDescChange({ target: { value: text } })}
@@ -1287,7 +1394,9 @@ const splitTemplateVarString = (str) => {
1287
1394
  {templateDescError}
1288
1395
  </CapError>
1289
1396
  )}
1290
- {!isEditFlow && isFullMode && renderDescriptionCharacterCount()}
1397
+ {(isEditFlow || !isFullMode)
1398
+ ? renderDescriptionCharacterCount('rcs-character-count rcs-character-count--compact')
1399
+ : (!isEditFlow && isFullMode && renderDescriptionCharacterCount())}
1291
1400
  {!isFullMode && hasTag() && (
1292
1401
  <CapAlert
1293
1402
  message={
@@ -1307,18 +1416,6 @@ const splitTemplateVarString = (str) => {
1307
1416
  );
1308
1417
  };
1309
1418
 
1310
-
1311
- const fallbackSmsLength = () => (
1312
- <CapLabel type="label1" className="fallback-sms-length">
1313
- {formatMessage(messages.totalCharacters, {
1314
- smsCount: Math.ceil(
1315
- fallbackMessage?.length / FALLBACK_MESSAGE_MAX_LENGTH,
1316
- ),
1317
- number: fallbackMessage?.length,
1318
- })}
1319
- </CapLabel>
1320
- );
1321
-
1322
1419
  // Get character count for title (rich card only)
1323
1420
  const getTitleCharacterCount = () => {
1324
1421
  if (templateType === contentType.text_message) return 0;
@@ -1378,7 +1475,7 @@ const splitTemplateVarString = (str) => {
1378
1475
  const hasTag = () => {
1379
1476
  // Check cardVarMapped values for tags
1380
1477
  if (cardVarMapped && Object.keys(cardVarMapped).length > 0) {
1381
- const hasTagInMapped = Object.values(cardVarMapped).some(value =>
1478
+ const hasTagInMapped = Object.values(cardVarMapped).some((value) =>
1382
1479
  isTagIncluded(value)
1383
1480
  );
1384
1481
  if (hasTagInMapped) return true;
@@ -1396,14 +1493,6 @@ const splitTemplateVarString = (str) => {
1396
1493
  return false;
1397
1494
  };
1398
1495
 
1399
- //adding creative dlt fallback sms handlers
1400
- const addDltMsgHandler = () => {
1401
- setShowDltContainer(true);
1402
- setDltMode(RCS_DLT_MODE.TEMPLATES);
1403
- setDltEditData({});
1404
- setFallbackMessage('');
1405
- };
1406
-
1407
1496
  const closeDltContainerHandler = () => {
1408
1497
  setShowDltContainer(false);
1409
1498
  setDltMode('');
@@ -1426,68 +1515,17 @@ const splitTemplateVarString = (str) => {
1426
1515
  const fallMsg = get(tempData, `versions.base.updated-sms-editor`, []).join(
1427
1516
  '',
1428
1517
  );
1518
+ const templateNameFromDlt = get(dltEditData, 'name', '')
1519
+ || get(tempData, 'versions.base.name', '')
1520
+ || '';
1429
1521
  closeDltContainerHandler();
1430
1522
  setDltEditData(tempData);
1431
- setFallbackMessage(fallMsg);
1432
- setFallbackMessageError(fallMsg?.length > FALLBACK_MESSAGE_MAX_LENGTH);
1433
- setShowDltCard(true);
1434
- };
1435
-
1436
- const rcsDltCardDeleteHandler = () => {
1437
- closeDltContainerHandler();
1438
- setDltEditData({});
1439
- setFallbackMessage('');
1440
- setFallbackMessageError(false);
1441
- setShowDltCard(false);
1442
- };
1443
-
1444
- const dltFallbackListingPreviewhandler = (data) => {
1445
- const {
1446
- 'updated-sms-editor': updatedSmsEditor = [],
1447
- 'sms-editor': smsEditor = '',
1448
- } = data.versions.base || {};
1449
- setFallbackPreviewmode(true);
1450
- setDltPreviewData(
1451
- updatedSmsEditor === '' ? smsEditor : updatedSmsEditor.join(''),
1452
- );
1453
- };
1454
-
1455
- const getDltContentCardList = (content, channel) => {
1456
- const extra = [
1457
- <CapIcon
1458
- type="edit"
1459
- style={{ marginRight: '8px' }}
1460
- onClick={() => rcsDltEditSelectHandler(dltEditData)}
1461
- />,
1462
- <CapDropdown
1463
- overlay={(
1464
- <CapMenu>
1465
- <>
1466
- <CapMenu.Item
1467
- className="ant-dropdown-menu-item"
1468
- onClick={() => setFallbackPreviewmode(true)}
1469
- >
1470
- {formatMessage(globalMessages.preview)}
1471
- </CapMenu.Item>
1472
- <CapMenu.Item
1473
- className="ant-dropdown-menu-item"
1474
- onClick={rcsDltCardDeleteHandler}
1475
- >
1476
- {formatMessage(globalMessages.delete)}
1477
- </CapMenu.Item>
1478
- </>
1479
- </CapMenu>
1480
- )}
1481
- >
1482
- <CapIcon type="more" />
1483
- </CapDropdown>,
1484
- ];
1485
- return {
1486
- title: channel,
1487
- content,
1488
- cardType: channel,
1489
- extra,
1490
- };
1523
+ const unicodeFromDlt = get(tempData, 'versions.base.unicode-validity');
1524
+ setSmsFallbackData({
1525
+ templateName: templateNameFromDlt,
1526
+ content: fallMsg,
1527
+ ...(typeof unicodeFromDlt === 'boolean' ? { unicodeValidity: unicodeFromDlt } : {}),
1528
+ });
1491
1529
  };
1492
1530
 
1493
1531
  const getDltSlideBoxContent = () => {
@@ -1514,7 +1552,6 @@ const splitTemplateVarString = (str) => {
1514
1552
  isFullMode={isFullMode}
1515
1553
  isDltFromRcs
1516
1554
  onSelectTemplate={rcsDltEditSelectHandler}
1517
- handlePeviewTemplate={dltFallbackListingPreviewhandler}
1518
1555
  />
1519
1556
  );
1520
1557
  } else if (dltMode === RCS_DLT_MODE.EDIT) {
@@ -1535,147 +1572,32 @@ const splitTemplateVarString = (str) => {
1535
1572
  return { dltHeader, dltContent };
1536
1573
  };
1537
1574
 
1538
- const renderFallBackSmsComponent = () => {
1539
- // Completely disable fallback functionality when DLT is disabled
1540
- return null;
1541
-
1542
- // const contentArr = [];
1543
- // const showAddCreativeBtnForDlt = isDltEnabled && !showDltCard;
1544
- // const showCardForDlt = isDltEnabled && showDltCard;
1545
- // const showNonDltFallbackComp = !showAddCreativeBtnForDlt && !showCardForDlt;
1546
- // //pushing common fallback sms headings
1547
- // contentArr.push(
1548
- // <CapRow
1549
- // style={{
1550
- // marginBottom: isDltEnabled ? '20px' : '10px',
1551
- // }}
1552
- // >
1553
- // <CapHeader
1554
- // title={(
1555
- // <CapRow type="flex">
1556
- // <CapHeading type="h4">
1557
- // {formatMessage(messages.fallbackLabel)}
1558
- // </CapHeading>
1559
- // <CapTooltipWithInfo
1560
- // placement="right"
1561
- // infoIconProps={{
1562
- // style: { marginLeft: '4px', marginTop: '3px' },
1563
- // }}
1564
- // title={formatMessage(messages.fallbackToolTip)}
1565
- // />
1566
- // </CapRow>
1567
- // )}
1568
- // description={formatMessage(messages.fallbackDesc)}
1569
- // suffix={
1570
- // isDltEnabled ? null : (
1571
- // <CapButton
1572
- // type="flat"
1573
- // className="fallback-preview-btn"
1574
- // prefix={<CapIcon type="eye" />}
1575
- // style={{ color: CAP_SECONDARY.base }}
1576
- // onClick={() => setFallbackPreviewmode(true)}
1577
- // disabled={fallbackMessage === '' || fallbackMessageError}
1578
- // >
1579
- // {formatMessage(globalMessages.preview)}
1580
- // </CapButton>
1581
- // )
1582
- // }
1583
- // />
1584
- // </CapRow>,
1585
- // );
1586
-
1587
- //dlt is enabled, and dlt content is not yet added, show button to add dlt creative
1588
- // showAddCreativeBtnForDlt
1589
- // && contentArr.push(
1590
- // <CapCard className="rcs-dlt-fallback-card">
1591
- // <CapRow type="flex" justify="center" align="middle">
1592
- // <CapColumn span={10}>
1593
- // <CapImage src={addCreativesIcon} />
1594
- // </CapColumn>
1595
- // <CapColumn span={14}>
1596
- // <CapButton
1597
- // className="add-dlt-btn"
1598
- // type="secondary"
1599
- // onClick={addDltMsgHandler}
1600
- // >
1601
- // {formatMessage(messages.addSmsCreative)}
1602
- // </CapButton>
1603
- // </CapColumn>
1604
- // </CapRow>
1605
- // </CapCard>,
1606
- // );
1607
-
1608
- // //dlt is enabled and dlt content is added, show it in a card
1609
- // showCardForDlt
1610
- // && contentArr.push(
1611
- // <CapCustomCardList
1612
- // cardList={[getDltContentCardList(fallbackMessage, SMS)]}
1613
- // className="rcs-dlt-card"
1614
- // />,
1615
- // fallbackMessageError && (
1616
- // <CapError className="rcs-fallback-len-error">
1617
- // {formatMessage(messages.fallbackMsgLenError)}
1618
- // </CapError>
1619
- // ),
1620
- // );
1621
-
1622
- // //dlt is not enabled, show non dlt text area
1623
- // showNonDltFallbackComp
1624
- // && contentArr.push(
1625
- // <>
1626
- // <CapRow>
1627
- // <CapHeader
1628
- // title={(
1629
- // <CapHeading type="h4">
1630
- // {formatMessage(messages.fallbackTextAreaLabel)}
1631
- // </CapHeading>
1632
- // )}
1633
- // suffix={(
1634
- // <TagList
1635
- // label={formatMessage(globalMessages.addLabels)}
1636
- // onTagSelect={onTagSelectFallback}
1637
- // location={location}
1638
- // tags={tags || []}
1639
- // onContextChange={handleOnTagsContextChange}
1640
- // injectedTags={injectedTags || {}}
1641
- // selectedOfferDetails={selectedOfferDetails}
1642
- // />
1643
- // )}
1644
- // />
1645
- // </CapRow>
1646
- // <div className="rcs_fallback_msg_textarea_wrapper">
1647
- // <TextArea
1648
- // id="rcs_fallback_message_textarea"
1649
- // autosize={{ minRows: 3, maxRows: 5 }}
1650
- // placeholder={formatMessage(messages.fallbackMsgPlaceholder)}
1651
- // onChange={onFallbackMessageChange}
1652
- // errorMessage={fallbackMessageError}
1653
- // value={fallbackMessage || ""}
1654
- // />
1655
- // {!isAiContentBotDisabled && (
1656
- // <CapAskAira.ContentGenerationBot
1657
- // text={fallbackMessage || ""}
1658
- // setText={(text) => {
1659
- // onFallbackMessageChange({ target: { value: text } });
1660
- // }}
1661
- // iconPlacement="float-br"
1662
- // rootStyle={{
1663
- // bottom: "0.5rem",
1664
- // right: "0.5rem",
1665
- // position: "absolute",
1666
- // }}
1667
- // />
1668
- // )}
1669
- // </div>
1670
- // <CapRow>{fallbackSmsLength()}</CapRow>
1671
- // </>
1672
- // );
1673
-
1674
- // return <>{contentArr}</>;
1675
- };
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
+ );
1676
1599
 
1677
1600
  const uploadRcsImage = useCallback((file, type, fileParams, index) => {
1678
- setImageError(null);
1679
1601
  const isRcsThumbnail = index === 1;
1680
1602
  actions.uploadRcsAsset(file, type, {
1681
1603
  isRcsThumbnail,
@@ -1687,7 +1609,6 @@ const splitTemplateVarString = (str) => {
1687
1609
 
1688
1610
  const setUpdateRcsImageSrc = useCallback(
1689
1611
  (val) => {
1690
- setAssetListImage(val);
1691
1612
  updateRcsImageSrc(val);
1692
1613
  actions.clearRcsMediaAsset(0);
1693
1614
  },
@@ -1717,7 +1638,6 @@ const splitTemplateVarString = (str) => {
1717
1638
 
1718
1639
 
1719
1640
  const uploadRcsVideo = (file, type, fileParams) => {
1720
- setImageError(null);
1721
1641
  actions.uploadRcsAsset(file, type, {
1722
1642
  ...fileParams,
1723
1643
  type: 'video',
@@ -1726,9 +1646,6 @@ const splitTemplateVarString = (str) => {
1726
1646
  });
1727
1647
  };
1728
1648
 
1729
- const updateRcsVideoSrc = (val) => {
1730
- setRcsVideoSrc(val);
1731
- };
1732
1649
  const setUpdateRcsVideoSrc = useCallback((index, val) => {
1733
1650
  setRcsVideoSrc(val);
1734
1651
  setAssetList(val);
@@ -1757,7 +1674,7 @@ const splitTemplateVarString = (str) => {
1757
1674
  <>
1758
1675
  <CapHeading type="h4" className="rcs-image-dimensions-label">Upload Thumbnail</CapHeading>
1759
1676
  <CapImageUpload
1760
- style={{ paddingTop: '20px' }}
1677
+ className="cap-custom-image-upload rcs-image-upload--top-spacing"
1761
1678
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1762
1679
  imgWidth={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].width}
1763
1680
  imgHeight={RCS_VIDEO_THUMBNAIL_DIMENSIONS[currentDimension].height}
@@ -1769,7 +1686,6 @@ const splitTemplateVarString = (str) => {
1769
1686
  updateOnReUpload={updateOnRcsThumbnailReUpload}
1770
1687
  minImgSize={RCS_THUMBNAIL_MIN_SIZE}
1771
1688
  index={1}
1772
- className="cap-custom-image-upload"
1773
1689
  key={`rcs-uploaded-image-${currentDimension}`}
1774
1690
  imageData={thumbnailData}
1775
1691
  channel={RCS}
@@ -1800,7 +1716,7 @@ const splitTemplateVarString = (str) => {
1800
1716
  value: dim.type,
1801
1717
  label: `${dim.label}`
1802
1718
  }))}
1803
- style={{ marginBottom: '20px' }}
1719
+ className="rcs-dimension-select--bottom-spacing"
1804
1720
  />
1805
1721
  </>
1806
1722
  )}
@@ -1814,7 +1730,6 @@ const splitTemplateVarString = (str) => {
1814
1730
  </div>
1815
1731
  ) : (
1816
1732
  <CapImageUpload
1817
- style={{ paddingTop: '20px' }}
1818
1733
  allowedExtensionsRegex={ALLOWED_IMAGE_EXTENSIONS_REGEX}
1819
1734
  imgWidth={RCS_IMAGE_DIMENSIONS[selectedDimension].width}
1820
1735
  imgHeight={RCS_IMAGE_DIMENSIONS[selectedDimension].height}
@@ -1825,7 +1740,7 @@ const splitTemplateVarString = (str) => {
1825
1740
  updateImageSrc={setUpdateRcsImageSrc}
1826
1741
  updateOnReUpload={updateOnRcsImageReUpload}
1827
1742
  index={0}
1828
- className="cap-custom-image-upload"
1743
+ className="cap-custom-image-upload rcs-image-upload--top-spacing"
1829
1744
  key={`rcs-uploaded-image-${selectedDimension}`}
1830
1745
  imageData={rcsData}
1831
1746
  channel={RCS}
@@ -1855,7 +1770,7 @@ const splitTemplateVarString = (str) => {
1855
1770
  value: dim.type,
1856
1771
  label: `${dim.label}`
1857
1772
  }))}
1858
- style={{ marginBottom: '20px' }}
1773
+ className="rcs-dimension-select--bottom-spacing"
1859
1774
  />
1860
1775
  )}
1861
1776
  {(isEditFlow || !isFullMode) ? (
@@ -1915,8 +1830,16 @@ const splitTemplateVarString = (str) => {
1915
1830
  const getRcsPreview = () => {
1916
1831
 
1917
1832
  const dimensionObj = RCS_IMAGE_DIMENSIONS[selectedDimension];
1918
- const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1919
- 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;
1920
1843
  return (
1921
1844
  <UnifiedPreview
1922
1845
  channel={RCS}
@@ -1939,51 +1862,65 @@ const splitTemplateVarString = (str) => {
1939
1862
  );
1940
1863
  };
1941
1864
 
1942
- const getUnmappedDesc = (str, mapping) => {
1943
- if (!str) return '';
1944
- if (!mapping || Object.keys(mapping).length === 0) return str;
1945
- let result = str;
1946
- const replacements = [];
1947
- Object.entries(mapping).forEach(([key, value]) => {
1948
- const raw = (value ?? '').toString();
1949
- if (!raw || raw?.trim?.() === '') return;
1950
- const braced = /^\{\{[\s\S]*\}\}$/.test(raw) ? raw : `{{${raw}}}`;
1951
- replacements.push({ key, needle: raw });
1952
- if (braced !== raw) replacements.push({ key, needle: braced });
1953
- });
1954
- const seen = new Set();
1955
- const uniq = replacements
1956
- .filter(({ key, needle }) => {
1957
- const id = `${key}::${needle}`;
1958
- if (seen.has(id)) return false;
1959
- seen.add(id);
1960
- return true;
1961
- })
1962
- .sort((a, b) => (b.needle.length - a.needle.length));
1963
-
1964
- uniq.forEach(({ key, needle }) => {
1965
- if (!needle) return;
1966
- const escaped = needle.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
1967
- const regex = new RegExp(escaped, 'g');
1968
- result = result.replace(regex, `{{${key}}}`);
1969
- });
1970
- return result;
1971
- };
1972
-
1973
1865
  const createPayload = () => {
1974
- const base = get(dltEditData, `versions.base`, {});
1975
- const {
1976
- template_id: templateId = '',
1977
- template_name = '',
1978
- 'sms-editor': template = '',
1979
- header: registeredSenderIds = [],
1980
- } = base;
1981
- const resolvedTitle = !isFullMode ? resolveTemplateWithMap(templateTitle) : templateTitle;
1982
- const resolvedDesc = !isFullMode ? resolveTemplateWithMap(templateDesc) : templateDesc;
1866
+ const isSlotMappingMode = isEditFlow || !isFullMode;
1983
1867
  const alignment = isMediaTypeImage
1984
1868
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.alignment
1985
1869
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.alignment;
1986
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
+
1987
1924
  const payload = {
1988
1925
  name: templateName,
1989
1926
  versions: {
@@ -1995,12 +1932,14 @@ const splitTemplateVarString = (str) => {
1995
1932
  cardSettings: {
1996
1933
  cardOrientation: isMediaTypeImage ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.orientation || VERTICAL : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.orientation || VERTICAL,
1997
1934
  ...(alignment && { mediaAlignment: alignment }),
1998
- cardWidth: SMALL,
1935
+ cardWidth: cardWidthFromSelection,
1999
1936
  },
2000
1937
  cardContent: [
2001
1938
  {
2002
- title: resolvedTitle,
2003
- 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,
2004
1943
  mediaType: templateMediaType,
2005
1944
  ...(!isMediaTypeText && {media: {
2006
1945
  mediaUrl: rcsImageSrc || rcsVideoSrc.videoSrc || '',
@@ -2009,23 +1948,30 @@ const splitTemplateVarString = (str) => {
2009
1948
  ? RCS_IMAGE_DIMENSIONS[selectedDimension]?.heightType || MEDIUM
2010
1949
  : RCS_VIDEO_THUMBNAIL_DIMENSIONS[selectedDimension]?.heightType || MEDIUM,
2011
1950
  }}),
2012
- ...(!isFullMode && (() => {
2013
- const tokens = [
2014
- ...(templateTitle ? (templateTitle.match(rcsVarRegex) || []) : []),
2015
- ...(templateDesc ? (templateDesc.match(rcsVarRegex) || []) : []),
1951
+ ...(isSlotMappingMode && (() => {
1952
+ const templateVarTokens = [
1953
+ ...(templateTitle?.match(rcsVarRegex) ?? []),
1954
+ ...(templateDesc?.match(rcsVarRegex) ?? []),
2016
1955
  ];
2017
- const allowedKeys = tokens
2018
- .map((t) => getVarNameFromToken(t))
2019
- .filter(Boolean);
2020
- const nextMap = {};
2021
- allowedKeys.forEach((k) => {
2022
- if (Object.prototype.hasOwnProperty.call(cardVarMapped || {}, k)) {
2023
- nextMap[k] = cardVarMapped[k];
2024
- } else {
2025
- 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;
2026
1972
  }
2027
1973
  });
2028
- return { cardVarMapped: nextMap };
1974
+ return { cardVarMapped: persistedSlotVarMap };
2029
1975
  })()),
2030
1976
  ...(suggestions.length > 0 && { suggestions }),
2031
1977
  }
@@ -2033,17 +1979,29 @@ const splitTemplateVarString = (str) => {
2033
1979
  contentType: isFullMode ? templateType : RICHCARD,
2034
1980
  ...(isFullMode && {accountId:accountId, accessToken: accessToken, accountName: accountName, hostName: hostName}),
2035
1981
  },
2036
- smsFallBackContent: {
2037
- message: fallbackMessage,
2038
- ...(isDltEnabled && {
2039
- templateConfigs: {
2040
- templateId,
2041
- templateName: template_name,
2042
- template,
2043
- 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
+ }),
2044
2002
  },
2045
- }),
2046
- },
2003
+ };
2004
+ })()),
2047
2005
  },
2048
2006
  },
2049
2007
  },
@@ -2053,6 +2011,84 @@ const splitTemplateVarString = (str) => {
2053
2011
  return payload;
2054
2012
  };
2055
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
+
2056
2092
  const actionCallback = ({ errorMessage, resp }, isEdit) => {
2057
2093
  // eslint-disable-next-line no-undef
2058
2094
  const error = errorMessage?.message || errorMessage;
@@ -2082,6 +2118,9 @@ const splitTemplateVarString = (str) => {
2082
2118
  _id: params?.id,
2083
2119
  validity: true,
2084
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,
2085
2124
  };
2086
2125
  getFormData(formDataParams);
2087
2126
  };
@@ -2095,6 +2134,7 @@ const splitTemplateVarString = (str) => {
2095
2134
  actionCallback({ resp, errorMessage });
2096
2135
  setSpin(false); // Always turn off spinner
2097
2136
  if (!errorMessage) {
2137
+ setTemplateStatus(RCS_STATUSES.pending);
2098
2138
  onCreateComplete();
2099
2139
  }
2100
2140
  });
@@ -2106,6 +2146,64 @@ const splitTemplateVarString = (str) => {
2106
2146
  }
2107
2147
  };
2108
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
+
2109
2207
  const isDisableDone = () => {
2110
2208
  if(isEditFlow){
2111
2209
  return false;
@@ -2116,40 +2214,16 @@ const splitTemplateVarString = (str) => {
2116
2214
  }
2117
2215
  }
2118
2216
 
2119
- if(!isFullMode){
2120
- const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2121
- const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2122
- const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2123
-
2124
- if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2125
- return true;
2126
- }
2127
-
2128
- const hasEmptyMapping =
2129
- cardVarMapped &&
2130
- Object.keys(cardVarMapped).length > 0 &&
2131
- Object.entries(cardVarMapped).some(([_, v]) => {
2132
- if (typeof v !== 'string') return !v; // null/undefined
2133
- return v.trim() === ''; // empty string
2134
- });
2135
-
2136
- if (hasEmptyMapping) {
2137
- return true;
2138
- }
2139
-
2140
- const anyMissing = allVars.some(name => {
2141
- const v = cardVarMapped?.[name];
2142
- if (typeof v !== 'string') return !v;
2143
- return v.trim() === '';
2144
- });
2145
- if (anyMissing) {
2146
- return true;
2147
- }
2217
+ if (isLibraryCampaignCardVarMappingIncomplete()) {
2218
+ return true;
2148
2219
  }
2149
2220
 
2150
- if (isMediaTypeText && templateDesc.trim() === '') {
2221
+ if (smsFallbackBlocksDone()) {
2151
2222
  return true;
2223
+ }
2152
2224
 
2225
+ if (isMediaTypeText && templateDesc.trim() === '') {
2226
+ return true;
2153
2227
  }
2154
2228
  if (isMediaTypeImage && (rcsImageSrc === '' || templateTitle === '' || templateDesc === '' )) {
2155
2229
  return true;
@@ -2167,53 +2241,36 @@ const splitTemplateVarString = (str) => {
2167
2241
  return true;
2168
2242
  }
2169
2243
  }
2170
- 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
+ ) {
2171
2251
  return true;
2172
2252
  }
2173
2253
  return false;
2174
2254
  };
2175
2255
 
2176
2256
  const isEditDisableDone = () => {
2177
-
2178
2257
  if (templateStatus !== RCS_STATUSES.approved) {
2179
2258
  return true;
2180
2259
  }
2181
2260
 
2182
- if (!isFullMode) {
2183
- if (templateName.trim() === '' || templateNameError) {
2184
- return true;
2185
- }
2261
+ // if (!isFullMode) {
2262
+ // if (templateName.trim() === '' || templateNameError) {
2263
+ // return true;
2264
+ // }
2265
+ // }
2266
+ if (isLibraryCampaignCardVarMappingIncomplete()) {
2267
+ return true;
2186
2268
  }
2187
- if(!isFullMode){
2188
- const titleVars = splitTemplateVarString(templateTitle).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2189
- const descVars = splitTemplateVarString(templateDesc).filter(elem => rcsVarTestRegex.test(elem)).map(v => v.replace(/^\{\{|\}\}$/g, ''));
2190
- const allVars = Array.from(new Set([ ...titleVars, ...descVars ]));
2191
-
2192
- if (allVars.length > 0 && isEmpty(cardVarMapped)) {
2193
- return true;
2194
- }
2195
-
2196
- const hasEmptyMapping =
2197
- cardVarMapped &&
2198
- Object.keys(cardVarMapped).length > 0 &&
2199
- Object.entries(cardVarMapped).some(([_, v]) => {
2200
- if (typeof v !== 'string') return !v; // null/undefined
2201
- return v.trim() === ''; // empty string
2202
- });
2203
2269
 
2204
- if (hasEmptyMapping) {
2205
- return true;
2206
- }
2207
-
2208
- const anyMissing = allVars.some(name => {
2209
- const v = cardVarMapped?.[name];
2210
- if (typeof v !== 'string') return !v;
2211
- return v.trim() === '';
2212
- });
2213
- if (anyMissing) {
2214
- return true;
2215
- }
2270
+ if (smsFallbackBlocksDone()) {
2271
+ return true;
2216
2272
  }
2273
+
2217
2274
  if (isMediaTypeText && templateDesc.trim() === '') {
2218
2275
  return true;
2219
2276
  }
@@ -2232,7 +2289,13 @@ const splitTemplateVarString = (str) => {
2232
2289
  return true;
2233
2290
  }
2234
2291
  }
2235
- 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
+ ) {
2236
2299
  return true;
2237
2300
  }
2238
2301
  return false;
@@ -2282,52 +2345,56 @@ const splitTemplateVarString = (str) => {
2282
2345
  };
2283
2346
 
2284
2347
  const getMainContent = () => {
2285
- if (showDltContainer && !fallbackPreviewmode) {
2286
- const dltSlideBoxContent = showDltContainer && getDltSlideBoxContent();
2287
- const { dltHeader = '', dltContent = '' } = dltSlideBoxContent;
2288
- return (
2289
- <CapSlideBox
2290
- show={showDltContainer}
2291
- header={dltHeader}
2292
- content={dltContent}
2293
- handleClose={closeDltContainerHandler}
2294
- size="size-xl"
2295
- />
2296
- );
2297
- }
2348
+ // Slideboxes are rendered outside the page-level spinner to avoid
2349
+ // stacking/blur issues during initial loads.
2350
+ if (showDltContainer) return null;
2298
2351
 
2299
2352
  return (
2300
2353
  <>
2301
- {templateStatus !== '' && (<CapRow className="template-status-container">
2302
- <CapLabel type="label2">
2303
- {formatMessage(messages.templateStatusLabel)}
2304
- </CapLabel>
2305
-
2306
- {templateStatus && (
2307
- <CapAlert
2308
- message={getTemplateStatusMessage()}
2309
- type={getTemplateStatusType(templateStatus)}
2310
- />
2311
- )}
2312
- </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>
2313
2369
  )}
2314
- <CapRow className="cap-rcs-creatives">
2370
+ <CapRow className={`cap-rcs-creatives ${isEditLike ? 'rcs-edit-mode' : ''}`}>
2315
2371
  <CapColumn span={14}>
2316
2372
  {/* template name */}
2317
2373
  {isFullMode && (
2318
- <CapInput
2319
- id="rcs_template_name_input"
2320
- data-testid="template_name"
2321
- onChange={onTemplateNameChange}
2322
- errorMessage={templateNameError}
2323
- placeholder={formatMessage(
2324
- globalMessages.templateNamePlaceholder,
2325
- )}
2326
- value={templateName || ''}
2327
- size="default"
2328
- label={formatMessage(globalMessages.creativeNameLabel)}
2329
- disabled={(isEditFlow || !isFullMode)}
2330
- />
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
+ )
2331
2398
  )}
2332
2399
  {renderLabel('templateTypeLabel')}
2333
2400
  <CapRadioGroup
@@ -2355,7 +2422,7 @@ const splitTemplateVarString = (str) => {
2355
2422
  </>
2356
2423
  )}
2357
2424
  {renderTextComponent()}
2358
- <CapDivider style={{ margin: `${CAP_SPACE_28} 0` }} />
2425
+ <CapDivider className="rcs-fallback-section-divider" />
2359
2426
  {renderFallBackSmsComponent()}
2360
2427
  <div className="rcs-scroll-div" />
2361
2428
  </CapColumn>
@@ -2367,7 +2434,8 @@ const splitTemplateVarString = (str) => {
2367
2434
 
2368
2435
 
2369
2436
  <div className="rcs-footer">
2370
- {!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 && (
2371
2439
  <>
2372
2440
  <div className="button-disabled-tooltip-wrapper">
2373
2441
  <CapButton
@@ -2388,7 +2456,6 @@ const splitTemplateVarString = (str) => {
2388
2456
  className="rcs-test-preview-btn"
2389
2457
  type="secondary"
2390
2458
  disabled={true}
2391
- style={{ marginLeft: "8px" }}
2392
2459
  >
2393
2460
  <FormattedMessage {...creativesMessages.testAndPreview} />
2394
2461
  </CapButton>
@@ -2421,51 +2488,6 @@ const splitTemplateVarString = (str) => {
2421
2488
  </>
2422
2489
  )}
2423
2490
  </div>
2424
-
2425
-
2426
- {fallbackPreviewmode && (
2427
- <CapSlideBox
2428
- className="rcs-fallback-preview"
2429
- show={fallbackPreviewmode}
2430
- header={(
2431
- <CapHeading type="h7" style={{ color: CAP_G01 }}>
2432
- {formatMessage(messages.fallbackPreviewtitle)}
2433
- </CapHeading>
2434
- )}
2435
- content={(
2436
- <>
2437
- <UnifiedPreview
2438
- channel={RCS}
2439
- content={{
2440
- rcsPreviewContent: {
2441
- rcsDesc: tempMsg,
2442
- },
2443
- }}
2444
- device={ANDROID}
2445
- showDeviceToggle={false}
2446
- showHeader={false}
2447
- formatMessage={formatMessage}
2448
- />
2449
- <CapHeading
2450
- type="h3"
2451
- style={{ textAlign: 'center' }}
2452
- className="margin-t-16"
2453
- >
2454
- {formatMessage(messages.totalCharacters, {
2455
- smsCount: Math.ceil(
2456
- tempMsg?.length / FALLBACK_MESSAGE_MAX_LENGTH,
2457
- ),
2458
- number: tempMsg.length,
2459
- })}
2460
- </CapHeading>
2461
- </>
2462
- )}
2463
- handleClose={() => {
2464
- setFallbackPreviewmode(false);
2465
- setDltPreviewData('');
2466
- }}
2467
- />
2468
- )}
2469
2491
  </>
2470
2492
  );
2471
2493
  };
@@ -2474,12 +2496,38 @@ const splitTemplateVarString = (str) => {
2474
2496
  <CapSpin spinning={loadingTags || spin}>
2475
2497
  {getMainContent()}
2476
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
+
2477
2513
  <TestAndPreviewSlidebox
2478
2514
  show={propsShowTestAndPreviewSlidebox || showTestAndPreviewSlidebox}
2479
2515
  onClose={handleCloseTestAndPreview}
2480
- formData={null} // RCS doesn't use formData structure like SMS
2481
- content={getTemplateContent()}
2516
+ formData={testPreviewFormData}
2517
+ content={testAndPreviewContent}
2482
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}
2483
2531
  />
2484
2532
  </>
2485
2533
  );
@@ -2491,7 +2539,6 @@ const mapStateToProps = createStructuredSelector({
2491
2539
  metaEntities: makeSelectMetaEntities(),
2492
2540
  loadingTags: isLoadingMetaEntities(),
2493
2541
  injectedTags: setInjectedTags(),
2494
- currentOrgDetails: selectCurrentOrgDetails(),
2495
2542
  });
2496
2543
 
2497
2544
  const mapDispatchToProps = (dispatch) => ({