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,11 +1,27 @@
1
1
  import React from 'react';
2
2
  import {CapIcon, CapImage, CapLabel, CapDivider } from '@capillarytech/cap-ui-library';
3
- import { RCS } from './constants';
3
+ import {
4
+ RCS,
5
+ RCS_MEDIA_TYPES,
6
+ RCS_NUMERIC_VAR_NAME_REGEX,
7
+ RCS_REGEX_META_CHARS_PATTERN,
8
+ RCS_STRIP_MUSTACHE_DELIMITERS_REGEX,
9
+ } from './constants';
4
10
  import './index.scss';
5
11
  // import { formatMessage } from '../../../utils/intl';
6
12
  import messages from './messages';
7
- import { STATUS_OPTIONS, RCS_BUTTON_TYPES, RCS_STATUSES, RCS_MEDIA_TYPES } from './constants';
8
-
13
+ import {
14
+ STATUS_OPTIONS,
15
+ RCS_BUTTON_TYPES,
16
+ RCS_STATUSES,
17
+ rcsVarRegex,
18
+ rcsVarTestRegex,
19
+ } from './constants';
20
+ import {
21
+ splitTemplateVarString,
22
+ COMBINED_SMS_TEMPLATE_VAR_REGEX,
23
+ isAnyTemplateVarToken,
24
+ } from '../../utils/templateVarUtils';
9
25
 
10
26
  export const getRcsStatusType = (status) => {
11
27
  switch (status) {
@@ -33,6 +49,334 @@ export const getTemplateStatusType = (templateStatus) => {
33
49
  }
34
50
  };
35
51
 
52
+ /**
53
+ * Global RegExp matching `{{numericVarName}}` in RCS template strings.
54
+ * `numericVarName` is escaped for regex metacharacters.
55
+ */
56
+ export function buildRcsNumericMustachePlaceholderRegex(numericVarName) {
57
+ const escaped = String(numericVarName).replace(RCS_REGEX_META_CHARS_PATTERN, '\\$&');
58
+ return new RegExp(`\\{\\{${escaped}\\}\\}`, 'g');
59
+ }
60
+
61
+ export function normalizeCardVarMapped(rawCardVarMapped, orderedTagNames) {
62
+ if (!rawCardVarMapped || typeof rawCardVarMapped !== 'object') return {};
63
+ const normalizedMap = {};
64
+ const templateVarNamesInOrder = Array.isArray(orderedTagNames) ? orderedTagNames : null;
65
+ const hasOrderedSlots =
66
+ Boolean(templateVarNamesInOrder?.length);
67
+ Object.entries(rawCardVarMapped).forEach(([entryKey, entryValue]) => {
68
+ const trimmedValue = entryValue == null ? '' : String(entryValue).trim();
69
+ const entryKeyIsNumericSlot = RCS_NUMERIC_VAR_NAME_REGEX.test(String(entryKey));
70
+ const mustacheInnerMatch = trimmedValue.match(/^\{\{([^}]+)\}\}$/);
71
+ const innerFromMustache =
72
+ mustacheInnerMatch?.[1] != null ? String(mustacheInnerMatch[1]).trim() : null;
73
+
74
+ if (innerFromMustache !== null && entryKeyIsNumericSlot) {
75
+ const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
76
+ const expectedVarForSlot =
77
+ hasOrderedSlots
78
+ && slotIndexZeroBased >= 0
79
+ && slotIndexZeroBased < templateVarNamesInOrder.length
80
+ ? templateVarNamesInOrder[slotIndexZeroBased]
81
+ : null;
82
+ const innerMatchesSlotToken =
83
+ expectedVarForSlot != null && innerFromMustache === expectedVarForSlot;
84
+ const legacyUnorderedPlaceholder = !hasOrderedSlots;
85
+ /* Library: slot "1" + {{user_id_b64}} when token is user_id_b64 → empty semantic. With ordered
86
+ * slots, only clear when inner matches that slot's template token; else keep (e.g. {{1}}+{{FirstName}}). */
87
+ const clearNumericSlotMustacheAsUnfilled =
88
+ !RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)
89
+ && (legacyUnorderedPlaceholder || innerMatchesSlotToken);
90
+ if (clearNumericSlotMustacheAsUnfilled) {
91
+ const outputKey = innerFromMustache;
92
+ const existingValue = normalizedMap[outputKey];
93
+ if (existingValue != null && String(existingValue).trim() !== '') {
94
+ return;
95
+ }
96
+ normalizedMap[outputKey] = '';
97
+ return;
98
+ }
99
+ if (RCS_NUMERIC_VAR_NAME_REGEX.test(innerFromMustache)) {
100
+ const outputKey = innerFromMustache;
101
+ const existingValue = normalizedMap[outputKey];
102
+ if (existingValue != null && String(existingValue).trim() !== '') {
103
+ return;
104
+ }
105
+ normalizedMap[outputKey] = '';
106
+ return;
107
+ }
108
+ }
109
+
110
+ if (innerFromMustache !== null && !entryKeyIsNumericSlot) {
111
+ if (innerFromMustache === String(entryKey)) {
112
+ const existingValue = normalizedMap[entryKey];
113
+ if (existingValue != null && String(existingValue).trim() !== '') {
114
+ return;
115
+ }
116
+ normalizedMap[entryKey] = '';
117
+ return;
118
+ }
119
+ }
120
+
121
+ if (entryKeyIsNumericSlot && templateVarNamesInOrder?.length) {
122
+ const slotIndexZeroBased = parseInt(String(entryKey), 10) - 1;
123
+ if (slotIndexZeroBased >= 0 && slotIndexZeroBased < templateVarNamesInOrder.length) {
124
+ normalizedMap[templateVarNamesInOrder[slotIndexZeroBased]] = trimmedValue;
125
+ return;
126
+ }
127
+ }
128
+ normalizedMap[entryKey] = trimmedValue;
129
+ });
130
+ return normalizedMap;
131
+ }
132
+
133
+ /**
134
+ * Rebuild `cardVarMapped` so keys match the current title/description tokens (title tokens first,
135
+ * then description), in order. Values are taken from the matching key, else from legacy slot
136
+ * `1`, `2`, … by index. If there are no `{{...}}` tokens, returns a shallow clone of `raw`.
137
+ */
138
+ export function coalesceCardVarMappedToTemplate(
139
+ sourceCardVarMap,
140
+ templateTitle,
141
+ templateDesc,
142
+ rcsVarRegex,
143
+ ) {
144
+ const getVarNameFromToken = (token = '') => token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
145
+ const templateVarTokens = [
146
+ ...(templateTitle?.match(rcsVarRegex) ?? []),
147
+ ...(templateDesc?.match(rcsVarRegex) ?? []),
148
+ ];
149
+ const lookupSourceMap =
150
+ sourceCardVarMap != null && typeof sourceCardVarMap === 'object' ? sourceCardVarMap : {};
151
+ if (!templateVarTokens.length) {
152
+ return { ...lookupSourceMap };
153
+ }
154
+ const coalescedMap = { ...lookupSourceMap };
155
+ const seenSemanticVarNames = new Set();
156
+ templateVarTokens.forEach((token, slotIndexZeroBased) => {
157
+ const semanticVarName = getVarNameFromToken(token);
158
+ if (!semanticVarName) return;
159
+ const numericSlotKey = String(slotIndexZeroBased + 1);
160
+ let valueFromSource = lookupSourceMap[numericSlotKey];
161
+ if (valueFromSource === undefined || valueFromSource === null) {
162
+ valueFromSource = lookupSourceMap[semanticVarName];
163
+ }
164
+ if (valueFromSource === undefined || valueFromSource === null) {
165
+ valueFromSource = lookupSourceMap[String(slotIndexZeroBased + 1)];
166
+ }
167
+ if (valueFromSource === undefined || valueFromSource === null) {
168
+ valueFromSource = lookupSourceMap[slotIndexZeroBased + 1];
169
+ }
170
+ const trimmedSlotValue = valueFromSource == null ? '' : String(valueFromSource).trim();
171
+ coalescedMap[numericSlotKey] = trimmedSlotValue;
172
+ if (!seenSemanticVarNames.has(semanticVarName)) {
173
+ seenSemanticVarNames.add(semanticVarName);
174
+ coalescedMap[semanticVarName] = trimmedSlotValue;
175
+ }
176
+ });
177
+ return coalescedMap;
178
+ }
179
+
180
+ /**
181
+ * Resolve the personalization value for a variable slot — aligned with createPayload:
182
+ * per-slot numeric keys `1`, `2`, … win over legacy semantic keys when both exist (duplicate
183
+ * `{{name}}` in title+desc). If semantic is explicitly cleared to '', that still wins over a
184
+ * stale numeric value (see tests) — except in embedded library / journey mode (`isLibraryMode`).
185
+ *
186
+ * In library mode, campaign payloads often set semantic keys to '' while numeric slot `1`,`2`,…
187
+ * still holds the value selected in the library; prefer that so VarSegment prepopulates.
188
+ *
189
+ * When a numeric slot is present but only whitespace / empty (common after hydration), do not
190
+ * treat it as authoritative — fall through to the semantic key so preview and payload match the
191
+ * tag the user selected (e.g. `1: ''` but `promotion_points: '{{newTag}}'`).
192
+ */
193
+ export function resolveCardVarMappedSlotValue(
194
+ cardVarMapped,
195
+ varName,
196
+ globalSlotIndexZeroBased,
197
+ isLibraryMode = false,
198
+ ) {
199
+ const varMap = cardVarMapped ?? {};
200
+ const slotKey = String(globalSlotIndexZeroBased + 1);
201
+ const semanticEmpty =
202
+ Object.prototype.hasOwnProperty.call(varMap, varName)
203
+ && String(varMap[varName] ?? '') === '';
204
+ const slotNonEmpty =
205
+ Object.prototype.hasOwnProperty.call(varMap, slotKey)
206
+ && String(varMap[slotKey] ?? '').trim() !== '';
207
+
208
+ if (semanticEmpty && !(isLibraryMode && slotNonEmpty)) {
209
+ return '';
210
+ }
211
+ let numericSlotValue = '';
212
+ if (Object.prototype.hasOwnProperty.call(varMap, slotKey)) {
213
+ numericSlotValue = String(varMap[slotKey] ?? '');
214
+ } else if (Object.prototype.hasOwnProperty.call(varMap, globalSlotIndexZeroBased + 1)) {
215
+ numericSlotValue = String(varMap[globalSlotIndexZeroBased + 1] ?? '');
216
+ }
217
+ if (numericSlotValue.trim() !== '') {
218
+ return numericSlotValue;
219
+ }
220
+ if (Object.prototype.hasOwnProperty.call(varMap, varName)) {
221
+ return String(varMap[varName] ?? '');
222
+ }
223
+ return '';
224
+ }
225
+
226
+ /** Text-only RCS card: editor shows a single “Text message” field (description); title row is hidden. */
227
+ export function isRcsTextOnlyCardMediaType(mediaType) {
228
+ return (
229
+ mediaType === RCS_MEDIA_TYPES.NONE
230
+ || String(mediaType || '').toUpperCase() === 'TEXT'
231
+ );
232
+ }
233
+
234
+ /**
235
+ * Resolve RCS card title/description for TemplatePreview (e.g. campaign slidebox preview).
236
+ * Mirrors `resolveTemplateWithMap` in the Rcs editor: title vars use global slots 0..n-1, then description.
237
+ * For text-only cards (`textOnlyCard`), ignore persisted `title` and resolve description from slot 0 — matches
238
+ * the editor where only the message body is shown.
239
+ */
240
+ export function resolveRcsCardPreviewStrings(
241
+ title,
242
+ description,
243
+ cardVarMapped,
244
+ isLibraryMode = false,
245
+ textOnlyCard = false,
246
+ ) {
247
+ const splitTemplateVarStringRcs = (str) => splitTemplateVarString(str, rcsVarRegex);
248
+ const getVarNameFromToken = (token = '') =>
249
+ token.replace(RCS_STRIP_MUSTACHE_DELIMITERS_REGEX, '');
250
+
251
+ const resolveTemplateWithMap = (str = '', slotOffset = 0) => {
252
+ if (!str) return '';
253
+ const arr = splitTemplateVarStringRcs(str);
254
+ let varOrdinal = 0;
255
+ return arr
256
+ .map((elem) => {
257
+ if (rcsVarTestRegex.test(elem)) {
258
+ const key = getVarNameFromToken(elem);
259
+ const globalSlot = slotOffset + varOrdinal;
260
+ varOrdinal += 1;
261
+ const v = resolveCardVarMappedSlotValue(
262
+ cardVarMapped,
263
+ key,
264
+ globalSlot,
265
+ isLibraryMode,
266
+ );
267
+ if (v == null || String(v).trim() === '') return elem;
268
+ return String(v);
269
+ }
270
+ return elem;
271
+ })
272
+ .join('');
273
+ };
274
+
275
+ const effectiveTitle = textOnlyCard ? '' : String(title || '');
276
+ const titleVarCount = textOnlyCard
277
+ ? 0
278
+ : (effectiveTitle.match(rcsVarRegex) || []).length;
279
+ return {
280
+ rcsTitle: textOnlyCard ? '' : resolveTemplateWithMap(effectiveTitle, 0),
281
+ rcsDesc: resolveTemplateWithMap(String(description || ''), titleVarCount),
282
+ };
283
+ }
284
+
285
+ /**
286
+ * Campaign consumer payload: replace each card's `title` / `description` with VarSegment-resolved
287
+ * tag strings (same rules as {@link resolveRcsCardPreviewStrings}). Root `rcsCardVarMapped` merges
288
+ * with per-card `cardVarMapped`; `cardVarMapped` on each card is left unchanged for round-trip.
289
+ */
290
+ export function mapRcsCardContentForConsumerWithResolvedTags(
291
+ cardContentArray,
292
+ rootRcsCardVarMapped,
293
+ isFullMode,
294
+ ) {
295
+ const rootRecord =
296
+ rootRcsCardVarMapped != null && typeof rootRcsCardVarMapped === 'object'
297
+ ? rootRcsCardVarMapped
298
+ : {};
299
+ const list = Array.isArray(cardContentArray) ? cardContentArray : [];
300
+ const isLibraryMode = isFullMode !== true;
301
+ return list.map((card) => {
302
+ if (!card || typeof card !== 'object') return card;
303
+ const nested =
304
+ card.cardVarMapped != null && typeof card.cardVarMapped === 'object'
305
+ ? card.cardVarMapped
306
+ : {};
307
+ const merged = { ...rootRecord, ...nested };
308
+ const textOnly = isRcsTextOnlyCardMediaType(card.mediaType);
309
+ const { rcsTitle, rcsDesc } = resolveRcsCardPreviewStrings(
310
+ card.title ?? '',
311
+ card.description ?? '',
312
+ merged,
313
+ isLibraryMode,
314
+ textOnly,
315
+ );
316
+ return {
317
+ ...card,
318
+ title: rcsTitle,
319
+ description: rcsDesc,
320
+ };
321
+ });
322
+ }
323
+
324
+ /**
325
+ * Before save: strip only legacy numeric self-placeholders (`{{1}}`, `{{2}}`, …) mistakenly stored as
326
+ * slot values. Preserve semantic tokens like `{{FirstName}}` from TagList — those are valid mappings.
327
+ */
328
+ export function sanitizeCardVarMappedValue(val) {
329
+ if (val == null) return '';
330
+ const trimmedDisplayString = String(val).trim();
331
+ if (/^\{\{\d+\}\}$/.test(trimmedDisplayString)) return '';
332
+ return String(val);
333
+ }
334
+
335
+ /**
336
+ * Same completion rule as SmsTraiEdit RCS fallback — used by `isDisableDone` from
337
+ * `smsFallbackData.rcsSmsFallbackVarMapped` + template string.
338
+ * Every variable token (DLT {#…#} or mustache {{…}}) must have a non-empty trimmed value in the map.
339
+ *
340
+ * Slot keys are usually `${token}_${segmentIndex}` (same as VarSegmentMessageEditor). Persisted / API
341
+ * payloads may use `${token}_${varOrdinal}` with a 1-based occurrence index (see SmsTraiEdit init).
342
+ * We try segment index first, then ordinal — so e.g. template `{#var#}` (segment index 0) still matches
343
+ * map `{#var#}_1`.
344
+ *
345
+ * @param {string} templateText
346
+ * @param {Record<string, string>} [varSlotValueMap={}]
347
+ * @returns {boolean}
348
+ */
349
+ export function areAllRcsSmsFallbackVarSlotsFilled(templateText, varSlotValueMap = {}) {
350
+ if (!templateText || typeof templateText !== 'string') return true;
351
+ const segments = splitTemplateVarString(templateText, COMBINED_SMS_TEMPLATE_VAR_REGEX);
352
+ const hasVarToken = segments.some(
353
+ (segment) =>
354
+ typeof segment === 'string'
355
+ && isAnyTemplateVarToken(segment),
356
+ );
357
+ if (!hasVarToken) return true;
358
+ let varOrdinal = 0;
359
+ return segments.every((segment, segmentIndex) => {
360
+ if (
361
+ typeof segment !== 'string'
362
+ || !isAnyTemplateVarToken(segment)
363
+ ) return true;
364
+ varOrdinal += 1;
365
+ const indexKey = `${segment}_${segmentIndex}`;
366
+ const ordinalKey = `${segment}_${varOrdinal}`;
367
+ let mappedSlotValue;
368
+ if (Object.prototype.hasOwnProperty.call(varSlotValueMap, indexKey)) {
369
+ mappedSlotValue = varSlotValueMap[indexKey];
370
+ } else if (Object.prototype.hasOwnProperty.call(varSlotValueMap, ordinalKey)) {
371
+ mappedSlotValue = varSlotValueMap[ordinalKey];
372
+ } else {
373
+ mappedSlotValue = undefined;
374
+ }
375
+ if (mappedSlotValue == null) return false;
376
+ return String(mappedSlotValue).trim() !== '';
377
+ });
378
+ }
379
+
36
380
  export const getRCSContent = (template) => {
37
381
  const renderRcsSuggestionsPreview = (rcsSuggestions) => {
38
382
  const renderArray = [];
@@ -84,8 +428,10 @@ export const getRCSContent = (template) => {
84
428
  media = {},
85
429
  description,
86
430
  title,
431
+ mediaType,
87
432
  suggestions = [],
88
433
  } = cardContent[0];
434
+ const isTextOnlyCard = isRcsTextOnlyCardMediaType(mediaType);
89
435
  const mediaPreview = media?.thumbnailUrl ? media.thumbnailUrl : media.mediaUrl;
90
436
  return (
91
437
  <div className="cap-rcs-creatives">
@@ -95,13 +441,15 @@ export const getRCSContent = (template) => {
95
441
  className="rcs-listing-image"
96
442
  />
97
443
  )}
98
- <CapLabel
99
- type="label19"
100
- className="rcs-listing-content title"
101
- fontWeight="bold"
102
- >
103
- {title}
104
- </CapLabel>
444
+ {!isTextOnlyCard && (
445
+ <CapLabel
446
+ type="label19"
447
+ className="rcs-listing-content title"
448
+ fontWeight="bold"
449
+ >
450
+ {title}
451
+ </CapLabel>
452
+ )}
105
453
  <CapLabel type="label19" className="rcs-listing-content desc">
106
454
  {description}
107
455
  </CapLabel>
@@ -33,6 +33,10 @@ import injectReducer from '../../../utils/injectReducer';
33
33
  import v2SmsCreateReducer from './reducer';
34
34
  import * as globalActions from '../../Cap/actions';
35
35
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
36
+ import {
37
+ getSmsEmbeddedFooterValidity,
38
+ getSmsMessageFromFormData,
39
+ } from '../smsFormDataHelpers';
36
40
 
37
41
  export class Create extends React.Component { // eslint-disable-line react/prefer-stateless-function
38
42
  constructor(props) {
@@ -51,6 +55,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
51
55
  modalContent: {title: "Alert", body: "Do you really want to delete this version?", type: 'confirm', id: 'sms-version-modal'},
52
56
  showTestAndPreviewSlidebox: false,
53
57
  isTestAndPreviewMode: false,
58
+ pendingGetFormData: false,
54
59
  };
55
60
  this.saveFormData = this.saveFormData.bind(this);
56
61
  this.onFormDataChange = this.onFormDataChange.bind(this);
@@ -140,8 +145,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
140
145
  }
141
146
 
142
147
  componentWillReceiveProps(nextProps) {
143
- if (nextProps.location.query.module === 'library' && nextProps.isGetFormData) {
144
- nextProps.getFormSubscriptionData(this.getFormData());
148
+ // Only trigger on actual Done click (isGetFormData false -> true). Prevents auto-submit after user fixes brace error.
149
+ if (!nextProps.isFullMode && nextProps.isGetFormData && !this.props.isGetFormData) {
150
+ this.setState({ startValidation: true, pendingGetFormData: true });
145
151
  } else if (nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
146
152
  this.startValidation();
147
153
  }
@@ -157,7 +163,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
157
163
  layout: 'SMS',
158
164
  type: 'TAG',
159
165
  context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
160
- embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
166
+ embedded: this.props.forceFullTagContext
167
+ ? 'full'
168
+ : (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
161
169
  };
162
170
  if (this.props.getDefaultTags) {
163
171
  query.context = this.props.getDefaultTags;
@@ -170,7 +178,20 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
170
178
  }
171
179
  }
172
180
 
181
+ componentDidUpdate() {
182
+ if (!this.props.embeddedSmsFallback || typeof this.props.onEmbeddedSmsFooterValidity !== 'function') {
183
+ return;
184
+ }
185
+ // Report validity on every update. The reducer in SmsFallback bails out (returns same
186
+ // state reference) when the value is unchanged, so no re-render loop is triggered.
187
+ // Calling unconditionally handles both mutation-based and reference-based FormBuilder updates.
188
+ this.props.onEmbeddedSmsFooterValidity(getSmsEmbeddedFooterValidity(this.state.formData, this.state.tabCount));
189
+ }
190
+
173
191
  componentWillUnmount() {
192
+ if (this.pendingGetFormDataTimeout) {
193
+ clearTimeout(this.pendingGetFormDataTimeout);
194
+ }
174
195
  if (this.props.setIsLoadingContent) {
175
196
  this.props.setIsLoadingContent(true); // setting isLoading of CreativesContainer so that slidebox foot can be hidden till content is loaded
176
197
  }
@@ -199,6 +220,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
199
220
  if (currentTab) {
200
221
  this.setState({currentTab});
201
222
  }
223
+ // Clear footer validation errors on input change so they refresh on next validation
224
+ if (this.props.showLiquidErrorInFooter) {
225
+ this.props.showLiquidErrorInFooter({ STANDARD_ERROR_MSG: [], LIQUID_ERROR_MSG: [] });
226
+ }
202
227
  }
203
228
 
204
229
  onTagSelect(data, currentTab) {
@@ -266,6 +291,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
266
291
  }
267
292
  const result = {};
268
293
  result.base = baseData;
294
+ /* Root field used by FormBuilder; embedded getFormSubscriptionData reads value.base */
295
+ if (formData['template-name'] !== undefined) {
296
+ result.base['template-name'] = formData['template-name'];
297
+ }
269
298
  if (this.state.isValid) {
270
299
  const msgObj = charCount.updateCharCount(baseData['sms-editor']);
271
300
  result.base.msg_count = msgObj.msgCount;
@@ -349,7 +378,25 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
349
378
  }
350
379
 
351
380
  setFormValidity(isFormValid, errorData) {
352
- this.setState({isFormValid, errorData});
381
+ this.setState({ isFormValid, errorData }, () => {
382
+ if (this.state.pendingGetFormData && !isFormValid) {
383
+ this.setState({ pendingGetFormData: false, startValidation: false });
384
+ // Reset parent's Done state so next Done click is a fresh attempt
385
+ if (this.props.onValidationFail) {
386
+ this.props.onValidationFail();
387
+ }
388
+ return;
389
+ }
390
+ // In library mode with SMS, submit only when FormBuilder calls onSubmit (after liquid validation).
391
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData && this.props.isFullMode) {
392
+ if (this.pendingGetFormDataTimeout) {
393
+ clearTimeout(this.pendingGetFormDataTimeout);
394
+ this.pendingGetFormDataTimeout = null;
395
+ }
396
+ this.props.getFormSubscriptionData(this.getFormData());
397
+ this.setState({ pendingGetFormData: false, startValidation: false });
398
+ }
399
+ });
353
400
  }
354
401
 
355
402
  injectMessages(elem) {
@@ -854,7 +901,9 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
854
901
  layout: 'SMS',
855
902
  type: 'TAG',
856
903
  context: (data && data.toLowerCase() === 'all') ? 'default' : (data && data.toLowerCase()),
857
- embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
904
+ embedded: this.props.forceFullTagContext
905
+ ? 'full'
906
+ : (this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full'),
858
907
  };
859
908
  this.props.globalActions.fetchSchemaForEntity(query);
860
909
  }
@@ -862,10 +911,22 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
862
911
  removeStandAlone() {
863
912
  const schema = _.cloneDeep(this.state.schema);
864
913
  const childSections = _.get(schema, 'standalone.sections[0].childSections');
914
+ if (!childSections || childSections.length <= 2) {
915
+ return;
916
+ }
917
+ /* In-form Save / Test row removed for embedded SMS; slidebox footer (SlideBoxFooter) provides actions — see CreativesContainer. */
865
918
  childSections.splice(2, 1);
866
- const fields = _.get(childSections, '[1].childSections[0].childSections');//removing template name section
867
- fields.splice(0, 1);
868
- _.set(childSections, '[1].childSections[0].childSections', fields);
919
+ /*
920
+ * Creatives library also drops the standalone template-name block because `SlideBoxHeader` shows the name.
921
+ * RCS SMS fallback uses the same slidebox footer but no template-name header — keep Creative name in the form.
922
+ */
923
+ if (!this.props.embeddedSmsFallback) {
924
+ const fields = _.get(childSections, '[1].childSections[0].childSections');
925
+ if (fields && fields.length > 0) {
926
+ fields.splice(0, 1);
927
+ _.set(childSections, '[1].childSections[0].childSections', fields);
928
+ }
929
+ }
869
930
  _.set(schema, 'standalone.sections[0].childSections', childSections);
870
931
  this.setState({ schema, loadingStatus: this.state.loadingStatus + 1 });
871
932
  }
@@ -911,37 +972,8 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
911
972
  this.setState({startValidation: false});
912
973
  }
913
974
 
914
- getTemplateContent = () => {
915
- // Get SMS content from formData
916
- if (!this.state.formData || !Array.isArray(this.state.formData) || this.state.formData.length === 0) {
917
- return '';
918
- }
919
- const currentTabData = this.state.formData[this.state.currentTab - 1];
920
- if (!currentTabData) {
921
- return '';
922
- }
923
-
924
- // PRIORITY 1: Check direct path first (most common for SMS)
925
- // This handles: formData[0]['sms-editor']
926
- if (currentTabData['sms-editor']) {
927
- return currentTabData['sms-editor'];
928
- }
929
-
930
- // PRIORITY 2: Check activeTab structure (for versioned templates)
931
- // This handles: formData[0][activeTab]['sms-editor']
932
- const activeTab = currentTabData?.activeTab || 'base';
933
- if (currentTabData[activeTab]?.['sms-editor']) {
934
- return currentTabData[activeTab]['sms-editor'];
935
- }
936
-
937
- // PRIORITY 3: Check base explicitly (fallback)
938
- // This handles: formData[0]['base']['sms-editor']
939
- if (currentTabData['base']?.['sms-editor']) {
940
- return currentTabData['base']['sms-editor'];
941
- }
942
-
943
- return '';
944
- }
975
+ getTemplateContent = () =>
976
+ getSmsMessageFromFormData(this.state.formData, this.state.currentTab);
945
977
 
946
978
  handleTestAndPreview = () => {
947
979
  // If parent is managing state (props exist), call parent handler
@@ -970,6 +1002,44 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
970
1002
  }
971
1003
 
972
1004
  saveFormData() {
1005
+ /*
1006
+ * RCS SMS fallback slidebox (embeddedSmsFallback): parent passes isFullMode from RCS, but we must not
1007
+ * call createTemplate — that spins CapSpin on createTemplateInProgress and is not the embedded contract.
1008
+ * Same as library: hand off form payload via getFormSubscriptionData.
1009
+ */
1010
+ if (this.props.embeddedSmsFallback && this.props.getFormSubscriptionData) {
1011
+ const { isTemplateNameEmpty, isMessageEmpty } = getSmsEmbeddedFooterValidity(
1012
+ this.state.formData,
1013
+ this.state.tabCount,
1014
+ );
1015
+ if (isTemplateNameEmpty || isMessageEmpty) {
1016
+ this.setState({ startValidation: true, pendingGetFormData: false });
1017
+ if (this.props.onValidationFail) {
1018
+ this.props.onValidationFail();
1019
+ }
1020
+ return;
1021
+ }
1022
+ const payload = this.getFormData();
1023
+ if (!payload.validity) {
1024
+ if (this.props.onValidationFail) {
1025
+ this.props.onValidationFail();
1026
+ }
1027
+ this.setState({ pendingGetFormData: false, startValidation: false });
1028
+ return;
1029
+ }
1030
+ this.props.getFormSubscriptionData(payload);
1031
+ this.setState({ pendingGetFormData: false, startValidation: false });
1032
+ return;
1033
+ }
1034
+ // In library mode: FormBuilder calls onSubmit only after liquid validation succeeds.
1035
+ // Submit to parent here so the slidebox can close with valid data.
1036
+ if (!this.props.isFullMode) {
1037
+ if (this.state.pendingGetFormData && this.props.getFormSubscriptionData) {
1038
+ this.props.getFormSubscriptionData(this.getFormData());
1039
+ this.setState({ pendingGetFormData: false, startValidation: false });
1040
+ }
1041
+ return;
1042
+ }
973
1043
  //Logic to save in db etc
974
1044
  const formData = _.cloneDeep(this.state.formData);
975
1045
  const obj = {};
@@ -1060,6 +1130,10 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1060
1130
  onTestContentClicked={this.props.onTestContentClicked}
1061
1131
  onPreviewContentClicked={this.props.onPreviewContentClicked}
1062
1132
  eventContextTags={this.props?.eventContextTags}
1133
+ waitEventContextTags={this.props?.waitEventContextTags}
1134
+ tagListGetPopupContainer={this.props.tagListGetPopupContainer}
1135
+ tagListPopoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
1136
+ tagListPopoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
1063
1137
  />
1064
1138
  </CapColumn>
1065
1139
  </CapRow>
@@ -1072,6 +1146,7 @@ export class Create extends React.Component { // eslint-disable-line react/prefe
1072
1146
  formData={this.state.formData}
1073
1147
  content={this.getTemplateContent()}
1074
1148
  currentChannel={SMS}
1149
+ smsRegister={this.props.smsRegister}
1075
1150
  />
1076
1151
  </div>
1077
1152
  );
@@ -1096,10 +1171,18 @@ Create.propTypes = {
1096
1171
  isLoadingMetaEntities: PropTypes.bool,
1097
1172
  selectedOfferDetails: PropTypes.array,
1098
1173
  eventContextTags: PropTypes.array,
1174
+ waitEventContextTags: PropTypes.object,
1099
1175
  showTestAndPreviewSlidebox: PropTypes.bool,
1100
1176
  handleTestAndPreview: PropTypes.func,
1101
1177
  handleCloseTestAndPreview: PropTypes.func,
1102
1178
  isTestAndPreviewMode: PropTypes.bool,
1179
+ smsRegister: PropTypes.any,
1180
+ forceFullTagContext: PropTypes.bool,
1181
+ embeddedSmsFallback: PropTypes.bool,
1182
+ onEmbeddedSmsFooterValidity: PropTypes.func,
1183
+ tagListGetPopupContainer: PropTypes.func,
1184
+ tagListPopoverOverlayStyle: PropTypes.object,
1185
+ tagListPopoverOverlayClassName: PropTypes.string,
1103
1186
  };
1104
1187
 
1105
1188
  const mapStateToProps = createStructuredSelector({
@@ -90,6 +90,10 @@ export default defineMessages({
90
90
  id: 'creatives.containersV2.Create.validationError',
91
91
  defaultMessage: 'Validation error',
92
92
  },
93
+ "unbalancedCurlyBraces": {
94
+ id: 'creatives.containersV2.Create.unbalancedCurlyBraces',
95
+ defaultMessage: 'Please close all curly braces in the message.',
96
+ },
93
97
  "smsTemplateCreatedSuccess": {
94
98
  id: 'creatives.containersV2.Create.smsTemplateCreatedSuccess',
95
99
  defaultMessage: 'SMS Template Created Successfully',