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
@@ -50,9 +50,9 @@ import { makeSelectMetaEntities, selectCurrentOrgDetails, selectLiquidStateDetai
50
50
  import * as actions from "../../v2Containers/Cap/actions";
51
51
  import './_formBuilder.scss';
52
52
  import {updateCharCount, checkUnicode} from "../../utils/smsCharCountV2";
53
- import { checkSupport, extractNames, preprocessHtml, validateIfTagClosed, isInsideLiquidBlock} from '../../utils/tagValidations';
53
+ import { preprocessHtml, validateTagsCore, hasUnsubscribeTag } from '../../utils/tagValidations';
54
54
  import { containsBase64Images } from '../../utils/content';
55
- import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS,AI_CONTENT_BOT_DISABLED, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
55
+ import { SMS, MOBILE_PUSH, LINE, ENABLE_AI_SUGGESTIONS, EMAIL, LIQUID_SUPPORTED_CHANNELS, INAPP } from '../../v2Containers/CreativesContainer/constants';
56
56
  import globalMessages from '../../v2Containers/Cap/messages';
57
57
  import { convert } from 'html-to-text';
58
58
  import { OUTBOUND, ADD_LANGUAGE, UPLOAD, USE_EDITOR, COPY_PRIMARY_LANGUAGE, GLOBAL_CONVERT_OPTIONS } from './constants';
@@ -60,10 +60,10 @@ import { GET_TRANSLATION_MAPPED } from '../../constants/unified';
60
60
  import moment from 'moment';
61
61
  import { CUSTOMER_BARCODE_TAG , COPY_OF, ENTRY_TRIGGER_TAG_REGEX, SKIP_TAGS_REGEX_GROUPS} from '../../constants/unified';
62
62
  import { REQUEST } from '../../v2Containers/Cap/constants'
63
- import { hasLiquidSupportFeature, isEmailUnsubscribeTagMandatory } from '../../utils/common';
63
+ import { isEmailUnsubscribeTagOptional, isAiContentBotDisabled } from '../../utils/common';
64
64
  import { isUrl } from '../../v2Containers/Line/Container/Wrapper/utils';
65
65
  import { bindActionCreators } from 'redux';
66
- import { getChannelData, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
66
+ import { getChannelData, hasPersonalizationTags, validateLiquidTemplateContent, validateMobilePushContent } from '../../utils/commonUtils';
67
67
  const TabPane = Tabs.TabPane;
68
68
  const {Column} = Table;
69
69
  const {TextArea} = CapInput;
@@ -72,10 +72,8 @@ const {CapRadioGroup} = CapRadio;
72
72
 
73
73
  const tagsTypes = {
74
74
  MISSING_TAGS: 'missingTags',
75
- UNSUPPORTED_TAGS: 'unsupportedTags',
76
75
  };
77
76
  const errorMessageForTags = {
78
- UNSUPPORTED_TAG_ERROR: 'unsupportedTagsError',
79
77
  MISSING_TAG_ERROR: 'missingTagsError',
80
78
  GENERIC_VALIDATION_ERROR: 'genericValidationError',
81
79
  TAG_BRACKET_COUNT_MISMATCH_ERROR: 'tagBracketCountMismatchError'
@@ -138,7 +136,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
138
136
  this.handleSetRadioValue = this.handleSetRadioValue.bind(this);
139
137
  this.formElements = [];
140
138
  // Check if the liquid flow feature is supported and the channel is in the supported list.
141
- this.liquidFlow = this.isLiquidFlowSupported.bind(this);
139
+ this.isLiquidFlowSupportedByChannel = this.isLiquidFlowSupportedByChannel.bind(this);
142
140
  this.onSubmitWrapper = this.onSubmitWrapper.bind(this);
143
141
 
144
142
  // Performance optimization: Debounced functions for high-frequency updates
@@ -330,8 +328,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
330
328
  return updatedFormData;
331
329
  }
332
330
 
333
- isLiquidFlowSupported = () => {
334
- return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()) && hasLiquidSupportFeature());
331
+ isLiquidFlowSupportedByChannel = () => {
332
+ return Boolean(LIQUID_SUPPORTED_CHANNELS.includes(this.props?.schema?.channel?.toUpperCase()));
335
333
  }
336
334
 
337
335
  componentWillMount() {
@@ -714,7 +712,9 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
714
712
  if (channel && channel.toUpperCase() === SMS) {
715
713
  for (let count = 0; count < this.state.tabCount; count += 1) {
716
714
  if (_.isEmpty(errorData[count])) {
717
- return;
715
+ // Do not return early. An empty tab object can appear transiently and returning here
716
+ // prevents onFormValidityChange from firing, which makes Done appear unresponsive.
717
+ errorData[count] = {};
718
718
  }
719
719
  const index = count + 1;
720
720
  if (!this.state.formData[count]) {
@@ -726,17 +726,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
726
726
 
727
727
  let tagValidationResponse = false;
728
728
  if (content) {
729
- tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
729
+ tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
730
730
  }
731
-
732
- if (tagValidationResponse.valid) {
731
+
732
+ const tagResult = tagValidationResponse && typeof tagValidationResponse === 'object'
733
+ ? tagValidationResponse
734
+ : { valid: false, missingTags: [], isBraceError: false };
735
+ if (tagResult.valid) {
733
736
  errorData[count][`sms-editor${index > 1 ? index : ''}`] = false;
734
737
  } else {
735
- errorData[count]['invalid-tags'] = tagValidationResponse.unsupportedTags;
736
- const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
737
- const { missingTags, unsupportedTags, isBraceError} = tagValidationResponse;
738
- errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : ( unsupportedTags && unsupportedTags.length ? UNSUPPORTED_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR));
739
- errorData[count][`bracket-error`] = tagValidationResponse.isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
738
+ const { MISSING_TAG_ERROR, GENERIC_VALIDATION_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags || {};
739
+ const { missingTags, isBraceError } = tagResult;
740
+ errorData[count][`sms-editor${index > 1 ? index : ''}`] = missingTags && missingTags.length ? MISSING_TAG_ERROR : (isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : GENERIC_VALIDATION_ERROR);
741
+ errorData[count][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
740
742
  isValid = false;
741
743
  }
742
744
  if(content !== '' && (ifUnicode && !unicodeCheck)) {
@@ -764,7 +766,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
764
766
  if (this.state.formData['message-editor'] !== undefined ) {
765
767
  const content = this.state.formData['0']['message-editor'] || '';
766
768
 
767
- const tagValidationResponse = this.validateTags((content), tags, injectedTags, false, this.props?.isFullMode);
769
+ const tagValidationResponse = this.validateTags((content), tags, false, this.props?.isFullMode);
768
770
 
769
771
  if (tagValidationResponse.valid) {
770
772
  errorData = {
@@ -847,7 +849,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
847
849
  errorData[index] = true;
848
850
  isValid = false;
849
851
  } else {
850
- const tagValidationResponse = this.validateTags(content, tags, injectedTags, false, this.props?.isFullMode);
852
+ const tagValidationResponse = this.validateTags(content, tags, false, this.props?.isFullMode);
851
853
 
852
854
  if (tagValidationResponse.valid) {
853
855
  errorData[index] = false;
@@ -903,21 +905,23 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
903
905
  if (errorData[parseInt(index)]) {
904
906
  if (message) {
905
907
  message = message.trim();
906
-
907
908
  if (message === "") {
908
909
  errorData[parseInt(index)][`message-editor${selector}`] = true;
909
910
  isValid = false;
910
911
  isCurrentTabValid = false;
912
+ } else if (this.props.restrictPersonalization && hasPersonalizationTags(message)) {
913
+ errorData[parseInt(index)][`message-editor${selector}`] = true;
914
+ isValid = false;
915
+ isCurrentTabValid = false;
911
916
  } else {
912
917
  errorData[parseInt(index)][`message-editor${selector}`] = false;
913
- const tagValidationResponse = this.validateTags(message, tags, injectedTags, false, this.props?.isFullMode);
918
+ const tagValidationResponse = this.validateTags(message, tags, false, this.props?.isFullMode);
914
919
 
915
920
  if (tagValidationResponse.valid) {
916
921
  errorData[parseInt(index)][`message-editor${selector}`] = false;
917
922
  } else {
918
- const {isBraceError} = tagValidationResponse;
923
+ const { isBraceError } = tagValidationResponse;
919
924
  errorData[parseInt(index)][`message-editor${selector}`] = isBraceError ? TAG_BRACKET_COUNT_MISMATCH_ERROR : true;
920
- errorData[parseInt(index)]['invalid-tags'] = tagValidationResponse.unsupportedTags;
921
925
  errorData[parseInt(index)][`bracket-error`] = isBraceError && TAG_BRACKET_COUNT_MISMATCH_ERROR;
922
926
  isValid = false;
923
927
  }
@@ -937,9 +941,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
937
941
  errorData[parseInt(index)][`message-title${selector}`] = true;
938
942
  isValid = false;
939
943
  isCurrentTabValid = false;
944
+ } else if (this.props.restrictPersonalization && hasPersonalizationTags(title)) {
945
+ errorData[parseInt(index)][`message-title${selector}`] = true;
946
+ isValid = false;
947
+ isCurrentTabValid = false;
940
948
  } else {
941
949
  errorData[parseInt(index)][`message-title${selector}`] = false;
942
- const tagValidationResponse = this.validateTags(title, tags, injectedTags, false, this.props?.isFullMode);
950
+ const tagValidationResponse = this.validateTags(title, tags, false, this.props?.isFullMode);
943
951
 
944
952
  if (tagValidationResponse.valid) {
945
953
  errorData[parseInt(index)][`message-title${selector}`] = false;
@@ -1193,8 +1201,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1193
1201
  if (!content) {
1194
1202
  return false;
1195
1203
  }
1196
- const tagValidationResponse = this.validateTags(content, tags, injectedTags, isEmail, this.props?.isFullMode);
1197
-
1204
+ const tagValidationResponse = this.validateTags(content, tags, isEmail, this.props?.isFullMode);
1198
1205
  // Check for base64 images in email content
1199
1206
  isEmail && containsBase64Images({content, callback:()=>{
1200
1207
  tagValidationResponse.valid = false;
@@ -1210,23 +1217,20 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1210
1217
  errorData[index][currentLang]['template-content'] = true;
1211
1218
  isValid = false;
1212
1219
  isLiquidValid = false;
1213
- if ((showMessages && !isNaN(index)) || this.liquidFlow()) {
1214
- if (tagValidationResponse?.missingTags?.length > 0 || tagValidationResponse?.unsupportedTags?.length > 0) {
1220
+ if ((showMessages && !isNaN(index)) || this.isLiquidFlowSupportedByChannel()) {
1221
+ if (tagValidationResponse?.missingTags?.length > 0) {
1215
1222
  errorString += `${this.props.intl.formatMessage(messages.contentNotValidLanguage)} ${currentLang}\n`;
1216
1223
  }
1217
1224
  if (tagValidationResponse?.missingTags?.length > 0) {
1218
1225
  errorString += `${this.props.intl.formatMessage(messages.missingTags)} ${tagValidationResponse.missingTags.toString()}\n`;
1219
1226
  }
1220
- if (tagValidationResponse?.unsupportedTags?.length > 0) {
1221
- errorString += `${this.props.intl.formatMessage(messages.unsupportedTags)} ${tagValidationResponse.unsupportedTags.toString()}\n`;
1222
- }
1223
1227
  if (tagValidationResponse?.isBraceError){
1224
1228
  errorString += this.props.intl.formatMessage(globalMessages.unbalanacedCurlyBraces);
1225
1229
  }
1226
1230
  if (tagValidationResponse?.isContentEmpty) {
1227
1231
  errorString += this.props.intl.formatMessage(messages.emailBodyEmptyError);
1228
1232
  // Adds a bypass for cases where content is initially empty in the creation flow.
1229
- if(this.liquidFlow()){
1233
+ if(this.isLiquidFlowSupportedByChannel()){
1230
1234
  errorString = "";
1231
1235
  isLiquidValid = true;
1232
1236
  }
@@ -1238,7 +1242,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1238
1242
  }
1239
1243
  }
1240
1244
  if (errorString) {
1241
- if (this.liquidFlow()) {
1245
+ if (this.isLiquidFlowSupportedByChannel()) {
1242
1246
  this.setState(
1243
1247
  (prevState) => ({
1244
1248
  liquidErrorMessage: {
@@ -1247,10 +1251,14 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1247
1251
  },
1248
1252
  }),
1249
1253
  () => {
1250
- // Callback after the state is updated
1251
1254
  this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
1252
1255
  }
1253
1256
  );
1257
+ // Footer shows the error; skip notification for Email CK/BEE (non-HTML) flow to avoid duplicate feedback
1258
+ const isEmailChannel = this.props?.schema?.channel?.toUpperCase() === EMAIL;
1259
+ if (!isEmailChannel) {
1260
+ this.openNotificationWithIcon('error', errorString, 'email-validation-error');
1261
+ }
1254
1262
  } else {
1255
1263
  this.openNotificationWithIcon('error', errorString, "email-validation-error");
1256
1264
  }
@@ -1264,7 +1272,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1264
1272
  });
1265
1273
  }
1266
1274
 
1267
- const isTemplateValid = this.liquidFlow() ? isLiquidValid : isValid;
1275
+ const isTemplateValid = this.isLiquidFlowSupportedByChannel() ? isLiquidValid : isValid;
1268
1276
  //Updating the state with the error data
1269
1277
  this.setState((prevState) => ({
1270
1278
  isFormValid: isTemplateValid,
@@ -1323,55 +1331,59 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1323
1331
  }
1324
1332
  onSubmitWrapper = (args) => {
1325
1333
  const {singleTab = null} = args || {};
1326
- if (this.liquidFlow()) {
1334
+ // Liquid validation (extractTags + Aira) only in library mode
1335
+ const runLiquidValidation = this.isLiquidFlowSupportedByChannel() && !this.props.isFullMode;
1336
+ if (runLiquidValidation) {
1327
1337
  // For MPUSH, we need to validate both Android and iOS content separately
1328
1338
  if (this.props.channel === MOBILE_PUSH || this.props?.schema?.channel?.toUpperCase() === MOBILE_PUSH) {
1329
1339
  this.validateFormBuilderMPush(this.state.formData, singleTab);
1330
1340
  return;
1331
1341
  }
1332
-
1333
- // For other channels (EMAIL, SMS, INAPP)
1334
- const content = getChannelData(this.props.schema.channel || this.props.channel, this.state.formData, this.props.baseLanguage);
1335
-
1336
- // Set up callbacks for error and success handling
1337
- const onError = ({ standardErrors, liquidErrors }) => {
1338
- this.setState(
1339
- (prevState) => ({
1340
- liquidErrorMessage: {
1341
- ...prevState.liquidErrorMessage,
1342
- STANDARD_ERROR_MSG: standardErrors,
1343
- LIQUID_ERROR_MSG: liquidErrors,
1344
- },
1345
- }),
1346
- () => {
1347
- this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1348
- this.props.stopValidation();
1349
- }
1350
- );
1351
- };
1352
-
1353
- const onSuccess = (contentToSubmit) => {
1354
- const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
1355
- if(channel === EMAIL) {
1356
- const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
1357
- this.handleLiquidTemplateSubmit(content);
1358
- } else {
1359
- this.handleLiquidTemplateSubmit(contentToSubmit);
1342
+
1343
+ // For other channels (EMAIL, SMS, INAPP): only call extractTags if there are no brace/empty errors already.
1344
+ // Run sync validation first; if it fails, block and show errors without calling the API.
1345
+ this.validateForm(null, null, true, false, () => {
1346
+ if (!this.state.isFormValid) {
1347
+ this.props.stopValidation();
1348
+ return;
1360
1349
  }
1361
- };
1362
-
1363
- // Call the common validation function
1364
- validateLiquidTemplateContent(content, {
1365
- getLiquidTags: this.props.actions.getLiquidTags,
1366
- formatMessage: this.props.intl.formatMessage,
1367
- messages,
1368
- onError,
1369
- onSuccess,
1370
- tagLookupMap: this.props?.metaEntities?.tagLookupMap,
1371
- eventContextTags: this.props?.eventContextTags,
1372
- isLiquidFlow: this.liquidFlow(),
1373
- forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
1374
- skipTags: this.skipTags.bind(this)
1350
+ const content = getChannelData(this.props.schema.channel || this.props.channel, this.state.formData, this.props.baseLanguage);
1351
+
1352
+ const onError = ({ standardErrors, liquidErrors }) => {
1353
+ this.setState(
1354
+ (prevState) => ({
1355
+ liquidErrorMessage: {
1356
+ ...prevState.liquidErrorMessage,
1357
+ STANDARD_ERROR_MSG: standardErrors,
1358
+ LIQUID_ERROR_MSG: liquidErrors,
1359
+ },
1360
+ }),
1361
+ () => {
1362
+ this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage);
1363
+ this.props.stopValidation();
1364
+ this.props.onFormValidityChange(false, this.state.errorData);
1365
+ }
1366
+ );
1367
+ };
1368
+
1369
+ const onSuccess = (contentToSubmit) => {
1370
+ const channel = this.props.channel || this.props?.schema?.channel?.toUpperCase();
1371
+ if(channel === EMAIL) {
1372
+ const content = this.state.formData?.base?.[this.props.baseLanguage]?.["template-content"] || "";
1373
+ this.handleLiquidTemplateSubmit(content);
1374
+ } else {
1375
+ this.handleLiquidTemplateSubmit(contentToSubmit);
1376
+ }
1377
+ };
1378
+
1379
+ validateLiquidTemplateContent(content, {
1380
+ getLiquidTags: this.props.actions.getLiquidTags,
1381
+ formatMessage: this.props.intl.formatMessage,
1382
+ messages,
1383
+ onError,
1384
+ onSuccess,
1385
+ skipTags: this.skipTags.bind(this)
1386
+ });
1375
1387
  });
1376
1388
  } else {
1377
1389
  this.props.onSubmit(this.state.formData);
@@ -1398,7 +1410,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1398
1410
 
1399
1411
  // Set up callbacks for error and success handling
1400
1412
  const onLiquidError = ({ standardErrors, liquidErrors }) => {
1401
-
1402
1413
  this.setState(
1403
1414
  (prevState) => ({
1404
1415
  liquidErrorMessage: {
@@ -1410,6 +1421,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1410
1421
  () => {
1411
1422
  this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.state.currentTab);
1412
1423
  this.props.stopValidation();
1424
+ // Block save: tell parent form is invalid so Done/submit is blocked
1425
+ this.props.onFormValidityChange(false, this.state.errorData);
1413
1426
  }
1414
1427
  );
1415
1428
  };
@@ -1439,13 +1452,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1439
1452
  getLiquidTags: this.props.actions.getLiquidTags,
1440
1453
  formatMessage: this.props.intl.formatMessage,
1441
1454
  messages: messages,
1442
- tagLookupMap: this.props?.metaEntities?.tagLookupMap,
1443
- eventContextTags: this.props?.eventContextTags,
1444
- isLiquidFlow: this.liquidFlow(), // Use the method instead of props
1445
- forwardedTags: this.props?.isLoyaltyModule ? this.props?.forwardedTags : {},
1446
- skipTags: this.skipTags.bind(this),
1447
- extractNames,
1448
- checkSupport,
1449
1455
  singleTab: singleTab?.toUpperCase(),
1450
1456
  });
1451
1457
  }
@@ -1500,102 +1506,40 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
1500
1506
  });
1501
1507
  }
1502
1508
 
1503
- validateTags(content, tagsParam, injectedTagsParams, isEmail = false, isFullMode = this.props?.isFullMode) {
1504
- const type = (this.props.location && this.props.location.query.type) ? this.props.location.query.type : 'full';
1509
+ validateTags(content, tagsParam, isEmail = false, isFullMode = this.props?.isFullMode) {
1505
1510
  let currentModule = this.props.location.query.module ? this.props.location.query.module : 'default';
1506
1511
  if (this.props.tagModule) {
1507
1512
  currentModule = this.props.tagModule;
1508
1513
  }
1509
1514
  const tags = tagsParam ? tagsParam : this.props.tags;
1510
- const injectedTags = this.transformInjectedTags(injectedTagsParams ? injectedTagsParams : this.props.injectedTags);
1511
- const excludedTags = ['user_id_b64', 'outbox_id_b64'];
1512
-
1513
-
1514
- const response = {};
1515
- response.valid = true;
1516
- response.missingTags = [];
1517
- response.unsupportedTags = [];
1518
- response.isBraceError = false;
1519
- response.isContentEmpty = false;
1520
- const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content ;
1521
- if(tags && tags.length && !isFullMode) {
1522
- _.forEach(tags, (tag) => {
1523
- _.forEach(tag.definition.supportedModules, (module) => {
1524
- if (module.mandatory && (currentModule === module.context)) {
1525
- if (content.indexOf(`{{${tag.definition.value}}}`) === -1) {
1526
- response.valid = false;
1527
- response.missingTags.push(tag.definition.value);
1528
- }
1529
- }
1530
- });
1531
- });
1532
- const regex = /{{[(A-Z\w+(\s\w+)*$\(\)@!#$%^&*~.,/\\]+}}/g;
1533
- let match = regex.exec(content);
1534
- const regexImgSrc=/<img[^>]*\bsrc\s*=\s*"[^"]*{{customer_barcode}}[^"]*"/;
1535
- let matchImg = regexImgSrc.exec(content);
1536
- const regexCustomerBarcode = /{{customer_barcode}}(?![^<]*>)/g;
1537
- let matchCustomerBarcode = regexCustomerBarcode.exec(content);
1538
- // \S matches anything other than a space, a tab, a newline, or a carriage return.
1539
- const validString= /\S/.test(contentForValidation);
1540
- if (isEmailUnsubscribeTagMandatory() && isEmail && this.props?.moduleType === OUTBOUND) {
1541
- const missingTagIndex = response?.missingTags?.indexOf("unsubscribe");
1542
- if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
1543
- response?.missingTags?.splice(missingTagIndex, 1);
1544
- }
1545
- if (validString) {
1546
- response.valid = true;
1547
- } else {
1548
- response.isContentEmpty = true;
1549
- }
1550
- }
1551
- while (match !== null ) {
1552
- const tagValue = match[0].substring(this.indexOfEnd(match[0], '{{'), match[0].indexOf('}}'));
1553
- const tagIndex = match?.index;
1554
- match = regex.exec(content);
1555
- let ifSupported = false;
1556
- _.forEach(tags, (tag) => {
1557
- if (tag.definition.value === tagValue) {
1558
- ifSupported = true;
1559
- }
1560
- if(tagValue === CUSTOMER_BARCODE_TAG && (matchImg === null || matchCustomerBarcode !== null)){
1561
- ifSupported = false;
1562
- }
1563
- });
1564
- const ifSkipped = this.skipTags(tagValue);
1565
- if (ifSkipped) {
1566
- ifSupported = true;
1567
- let isUnsubscribeSkipped = tagValue.indexOf("unsubscribe") != -1 ;
1568
- if (isUnsubscribeSkipped) {
1569
- const missingTagIndex = response.missingTags.indexOf("unsubscribe");
1570
- if(missingTagIndex != -1) { //skip regex tags for mandatory tags also
1571
- response.missingTags.splice(missingTagIndex, 1);
1572
- }
1573
- }
1574
- }
1575
-
1576
- // Event Context Tags support
1577
- this.props?.eventContextTags?.forEach((tag) => {
1578
- if (tagValue === tag?.tagName) {
1579
- ifSupported = true;
1580
- }
1581
- });
1582
-
1583
- ifSupported = ifSupported || this.checkIfSupportedTag(tagValue, injectedTags);
1584
- // Only add to unsupportedTags if not inside a {% ... %} block and not in liquid flow
1585
- // Tags inside {% %} blocks can contain any dynamic tags and should not be validated
1586
- if (!ifSupported && !this.liquidFlow() && !isInsideLiquidBlock(content, tagIndex)) {
1587
- response.unsupportedTags.push(tagValue);
1588
- response.valid = false;
1589
- }
1515
+ const contentForValidation = isEmail ? convert(content, GLOBAL_CONVERT_OPTIONS) : content;
1516
+ const isOutboundModule = (currentModule || '').toUpperCase() === OUTBOUND;
1517
+
1518
+ const initialMissingTags = (tags && tags.length && !isFullMode && isEmail && isOutboundModule && !isEmailUnsubscribeTagOptional() && !hasUnsubscribeTag(content))
1519
+ ? ['unsubscribe']
1520
+ : [];
1521
+
1522
+ const response = validateTagsCore({
1523
+ contentForBraceCheck: contentForValidation,
1524
+ contentForUnsubscribeScan: content,
1525
+ tags,
1526
+ currentModule,
1527
+ isFullMode,
1528
+ initialMissingTags, // [] or ['unsubscribe']; core uses this instead of definition-based when provided
1529
+ skipTagsFn: this.skipTags.bind(this),
1530
+ includeIsContentEmpty: true,
1531
+ });
1590
1532
 
1591
- if (response?.unsupportedTags?.length == 0 && response?.missingTags?.length == 0 ) {
1592
- response.valid = true;
1593
- }
1533
+ // When unsubscribe tag is optional (isEmailUnsubscribeTagOptional): do not enforce unsubscribe (defensive splice); set isContentEmpty only when content is empty. Do not override response.valid so brace/format errors are preserved.
1534
+ const validString = /\S/.test(contentForValidation);
1535
+ if (isEmailUnsubscribeTagOptional() && isEmail && isOutboundModule) {
1536
+ const missingTagIndex = response.missingTags.indexOf('unsubscribe');
1537
+ if (missingTagIndex !== -1) {
1538
+ response.missingTags.splice(missingTagIndex, 1);
1539
+ }
1540
+ if (!validString) {
1541
+ response.isContentEmpty = true;
1594
1542
  }
1595
- }
1596
- if(!validateIfTagClosed(contentForValidation)){
1597
- response.isBraceError = true;
1598
- response.valid = false;
1599
1543
  }
1600
1544
  return response;
1601
1545
  }
@@ -2829,36 +2773,30 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2829
2773
 
2830
2774
 
2831
2775
  getMissingOrUnsupportedTagsName = (content = '', type) => {
2832
- const { MISSING_TAGS, UNSUPPORTED_TAGS} = tagsTypes;
2776
+ const { MISSING_TAGS } = tagsTypes;
2833
2777
  const tagValidationResponse = this.validateTags(content);
2834
- if (type && (type === MISSING_TAGS || type === UNSUPPORTED_TAGS)) {
2835
- return tagValidationResponse[type].join(', ').toString();
2778
+ if (type && type === MISSING_TAGS) {
2779
+ return (tagValidationResponse[type] || []).join(', ').toString();
2836
2780
  }
2837
2781
  return null;
2838
2782
  };
2839
2783
 
2840
2784
  renderTextAreaContent = (styling, columns, val, isVersionEnable, rows, cols, offset = 0) => {
2841
2785
  const { checkValidation, errorData, currentTab, formData } = this.state;
2842
- const { MISSING_TAGS, UNSUPPORTED_TAGS } = tagsTypes;
2786
+ const { MISSING_TAGS } = tagsTypes;
2843
2787
  const errorType = (isVersionEnable ? errorData[`${currentTab - 1}`][val.id] : errorData[val.id]);
2844
2788
  const ifError = checkValidation && errorType;
2845
2789
  const messageContent = isVersionEnable ? formData[`${currentTab - 1}`][val.id] : formData[val.id];
2846
- const { MISSING_TAG_ERROR, UNSUPPORTED_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
2790
+ const { MISSING_TAG_ERROR, TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
2847
2791
  const { formatMessage } = this.props.intl;
2848
2792
 
2849
- const { accessibleFeatures = [] } = this.props.currentOrgDetails || {};
2850
- const isAiContentBotDisabled = accessibleFeatures?.includes(
2851
- AI_CONTENT_BOT_DISABLED
2852
- );
2793
+ const aiContentBotDisabled = isAiContentBotDisabled();
2853
2794
 
2854
2795
  let errorMessageText = false;
2855
2796
  switch (errorType) {
2856
2797
  case MISSING_TAG_ERROR:
2857
2798
  errorMessageText = formatMessage(messages.missingTagsValidationError, {missingTags: this.getMissingOrUnsupportedTagsName(messageContent, MISSING_TAGS)});
2858
2799
  break;
2859
- case UNSUPPORTED_TAG_ERROR:
2860
- errorMessageText = formatMessage(messages.unsupportedTagsValidationError, {unsupportedTags: this.getMissingOrUnsupportedTagsName(messageContent, UNSUPPORTED_TAGS)});
2861
- break;
2862
2800
  case TAG_BRACKET_COUNT_MISMATCH_ERROR:
2863
2801
  errorMessageText = formatMessage(globalMessages.unbalanacedCurlyBraces);
2864
2802
  break;
@@ -2868,8 +2806,17 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2868
2806
  default:
2869
2807
  break;
2870
2808
  }
2809
+
2810
+ if (this.props.restrictPersonalization && hasPersonalizationTags(messageContent)) {
2811
+ errorMessageText = formatMessage(messages.personalizationTagsErrorMessage);
2812
+ }
2813
+ // Empty/required error: only show after user has triggered validation (ifError / "Done").
2814
+ // All other errors (brace, personalization, missing tags, generic): show in real time while typing.
2815
+ const isContentEmpty = !messageContent || !/\S/.test(String(messageContent).trim());
2816
+ const isEmptyError = errorType && isContentEmpty;
2817
+ const showError = errorType && (isEmptyError ? ifError : true);
2871
2818
  const prevErrorMessage = this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.[0];
2872
- if (prevErrorMessage !== errorMessageText && errorMessageText && this.liquidFlow()) {
2819
+ if (prevErrorMessage !== errorMessageText && errorMessageText && this.isLiquidFlowSupportedByChannel()) {
2873
2820
  this.setState((prevState) => ({
2874
2821
  liquidErrorMessage: {
2875
2822
  ...prevState.liquidErrorMessage,
@@ -2880,15 +2827,25 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2880
2827
  this.props.showLiquidErrorInFooter(this.state.liquidErrorMessage, this.props.channel === SMS? null: this.state.currentTab);
2881
2828
  });
2882
2829
  }
2883
- else{
2830
+ // Always render the textarea regardless of the liquid error state update above.
2831
+ // Previously the textarea was inside the `else` branch, so it was not rendered during the
2832
+ // one render cycle when the liquid error message was first set – causing the input to lose
2833
+ // focus whenever a brace/tag error first appeared.
2884
2834
  if (styling === 'semantic') {
2885
2835
  columns.push(
2886
2836
  <CapColumn key="input" span={val.width} offset={offset}>
2887
2837
  <TextArea
2888
2838
  id={val.id}
2889
2839
  placeholder={val.placeholder ? val.placeholder : ''}
2890
- className={`${ifError ? 'error-form-builder' : ''}`}
2891
- errorMessage={errorMessageText && !this.liquidFlow() ? errorMessageText : ''}
2840
+ className={`${showError ? 'error-form-builder' : ''}`}
2841
+ errorMessage={
2842
+ showError && errorMessageText && (
2843
+ !this.isLiquidFlowSupportedByChannel() ||
2844
+ [MOBILE_PUSH, INAPP].includes(this.props.schema?.channel?.toUpperCase())
2845
+ )
2846
+ ? errorMessageText
2847
+ : ''
2848
+ }
2892
2849
  label={val.label}
2893
2850
  autosize={val.autosize ? val.autosizeParams : false}
2894
2851
  onChange={(e) => this.updateFormData(e.target.value, val)}
@@ -2901,7 +2858,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2901
2858
  cols={cols}
2902
2859
  />
2903
2860
  {[SMS, MOBILE_PUSH].includes(this.props.schema?.channel)
2904
- && !isAiContentBotDisabled
2861
+ && !aiContentBotDisabled
2905
2862
  && (
2906
2863
  <CapAskAira.ContentGenerationBot
2907
2864
  text={messageContent || ""}
@@ -2919,7 +2876,6 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
2919
2876
  </CapColumn>
2920
2877
  );
2921
2878
  }
2922
- }
2923
2879
  };
2924
2880
 
2925
2881
 
@@ -3033,6 +2989,8 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3033
2989
  userLocale={this.props.userLocale}
3034
2990
  selectedOfferDetails={this.props.selectedOfferDetails}
3035
2991
  eventContextTags={this.props?.eventContextTags}
2992
+ restrictPersonalization={this.props.restrictPersonalization}
2993
+ waitEventContextTags={this.props?.waitEventContextTags}
3036
2994
  />
3037
2995
  </CapColumn>
3038
2996
  );
@@ -3062,11 +3020,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3062
3020
  userLocale={this.props.userLocale}
3063
3021
  selectedOfferDetails={this.props.selectedOfferDetails}
3064
3022
  eventContextTags={this.props?.eventContextTags}
3023
+ waitEventContextTags={this.props?.waitEventContextTags}
3065
3024
  moduleFilterEnabled={this.props.location && this.props.location.query && this.props.location.query.type !== 'embedded'}
3066
3025
  containerStyle={val.style || {}}
3067
3026
  inputProps={val.inputProps || {}}
3068
3027
  showInput={val.showInput !== false}
3069
3028
  showTagList={val.showTagList !== false}
3029
+ restrictPersonalization={this.props.restrictPersonalization}
3070
3030
  />
3071
3031
  </CapColumn>
3072
3032
  );
@@ -3436,15 +3396,19 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3436
3396
  switch (type) {
3437
3397
  case "input":
3438
3398
  const errorType = isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id];
3439
- const { accessibleFeatures = [] } = this.props.currentOrgDetails || {};
3440
- const isAiContentBotDisabled = accessibleFeatures?.includes(
3441
- AI_CONTENT_BOT_DISABLED
3442
- );
3399
+ const aiContentBotDisabled = isAiContentBotDisabled();
3443
3400
  ifError = this.state.checkValidation && (isVersionEnable ? this.state.errorData[`${this.state.currentTab - 1}`][val.id] : this.state.errorData[val.id]);
3444
3401
  const { TAG_BRACKET_COUNT_MISMATCH_ERROR } = errorMessageForTags;
3445
3402
  const { formatMessage } = this.props.intl;
3446
- let errorMessageText = errorType === TAG_BRACKET_COUNT_MISMATCH_ERROR ? formatMessage(globalMessages.unbalanacedCurlyBraces) :(val.errorMessage && ifError ? val.errorMessage : '');
3447
3403
  const value = isVersionEnable ? this.state.formData[`${this.state.currentTab - 1}`][val.id] : this.state.formData[val.id];
3404
+ // Show personalization error for title field inline (same as message textarea) when restrictPersonalization is true
3405
+ const hasPersonalizationError = this.props.restrictPersonalization && hasPersonalizationTags(value);
3406
+ if (hasPersonalizationError) {
3407
+ ifError = true;
3408
+ }
3409
+ let errorMessageText = hasPersonalizationError
3410
+ ? formatMessage(messages.personalizationTagsErrorMessage)
3411
+ : (errorType === TAG_BRACKET_COUNT_MISMATCH_ERROR ? formatMessage(globalMessages.unbalanacedCurlyBraces) : (val.errorMessage && ifError ? val.errorMessage : ''));
3448
3412
  if (styling === 'semantic') {
3449
3413
  columns.push(
3450
3414
  <CapColumn key={val.id} span={val.width} offset={val.offset} style={val.style || {}}>
@@ -3465,7 +3429,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3465
3429
  size={val.size || "default"}
3466
3430
  />
3467
3431
  {this.props.schema?.channel === EMAIL &&
3468
- !isAiContentBotDisabled && (
3432
+ !aiContentBotDisabled && (
3469
3433
  <CapAskAira.ContentGenerationBot
3470
3434
  text={value || ""}
3471
3435
  setText={this.handleSetText.bind(this, val)}
@@ -3695,6 +3659,11 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3695
3659
  selectedOfferDetails={this.props.selectedOfferDetails}
3696
3660
  channel={channel}
3697
3661
  eventContextTags={this.props?.eventContextTags}
3662
+ restrictPersonalization={this.props.restrictPersonalization}
3663
+ waitEventContextTags={this.props?.waitEventContextTags}
3664
+ getPopupContainer={this.props.tagListGetPopupContainer}
3665
+ popoverOverlayStyle={this.props.tagListPopoverOverlayStyle}
3666
+ popoverOverlayClassName={this.props.tagListPopoverOverlayClassName}
3698
3667
  />
3699
3668
  </CapColumn>
3700
3669
  );
@@ -3741,11 +3710,13 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3741
3710
  userLocale={this.state.translationLang}
3742
3711
  selectedOfferDetails={this.props.selectedOfferDetails}
3743
3712
  eventContextTags={this.props?.eventContextTags}
3713
+ waitEventContextTags={this.props?.waitEventContextTags}
3744
3714
  moduleFilterEnabled={moduleFilterEnabledForCapTagList}
3745
3715
  containerStyle={val.style || {}}
3746
3716
  inputProps={val.inputProps || {}}
3747
3717
  showInput={val.showInput !== false}
3748
3718
  showTagList={val.showTagList !== false}
3719
+ restrictPersonalization={this.props.restrictPersonalization}
3749
3720
  />
3750
3721
  </CapColumn>
3751
3722
  );
@@ -3971,7 +3942,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
3971
3942
  <CapColumn
3972
3943
  style={val.colStyle ? val.colStyle : {border : ""}}
3973
3944
  span={val.width}
3974
- className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"} `}
3945
+ className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}
3975
3946
  >
3976
3947
  <CKEditor
3977
3948
  id={val.id}
@@ -4015,7 +3986,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
4015
3986
  isModuleFilterEnabled = this.props.isFullMode;
4016
3987
  }
4017
3988
  columns.push(
4018
- <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={`${(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.liquidFlow() && "error-boundary"}`}>
3989
+ <CapColumn style={val.colStyle ? val.colStyle : {}} span={val.width} className={(this.state.liquidErrorMessage?.LIQUID_ERROR_MSG?.length || this.state.liquidErrorMessage?.STANDARD_ERROR_MSG?.length) && this.isLiquidFlowSupportedByChannel() ? "error-boundary" : ""}>
4019
3990
  <BeeEditor
4020
3991
  uid={uuid}
4021
3992
  tokenData={beeToken}
@@ -4034,6 +4005,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
4034
4005
  onContextChange={this.props.onContextChange}
4035
4006
  moduleFilterEnabled={isModuleFilterEnabled}
4036
4007
  eventContextTags={this.props?.eventContextTags}
4008
+ waitEventContextTags={this.props?.waitEventContextTags}
4037
4009
  />
4038
4010
  </CapColumn>
4039
4011
  );
@@ -4301,7 +4273,7 @@ class FormBuilder extends React.Component { // eslint-disable-line react/prefer-
4301
4273
 
4302
4274
 
4303
4275
  return (
4304
- <CapSpin spinning={Boolean((this.liquidFlow() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
4276
+ <CapSpin spinning={Boolean((this.isLiquidFlowSupportedByChannel() && this.props.liquidExtractionInProgress) || this.props.metaDataStatus === REQUEST)} tip={this.props.intl.formatMessage(messages.liquidSpinText)} >
4305
4277
  <CapRow>
4306
4278
  {this.props.schema && this.renderForm()}
4307
4279
  <SlideBox
@@ -4336,6 +4308,7 @@ FormBuilder.defaultProps = {
4336
4308
  userLocale: localStorage.getItem('jlocale') || 'en',
4337
4309
  showLiquidErrorInFooter: () => {},
4338
4310
  metaDataStatus: "",
4311
+ waitEventContextTags: {},
4339
4312
  isTestAndPreviewMode: false, // Default to false to maintain existing behavior
4340
4313
  };
4341
4314
 
@@ -4384,11 +4357,16 @@ FormBuilder.propTypes = {
4384
4357
  type: PropTypes.string.isRequired,
4385
4358
  isEmailLoading: PropTypes.bool.isRequired,
4386
4359
  moduleType: PropTypes.string.isRequired,
4387
- showLiquidErrorInFooter: PropTypes.bool.isRequired,
4360
+ showLiquidErrorInFooter: PropTypes.func.isRequired,
4388
4361
  eventContextTags: PropTypes.array.isRequired,
4362
+ waitEventContextTags: PropTypes.object,
4389
4363
  forwardedTags: PropTypes.object.isRequired,
4390
4364
  isLoyaltyModule: PropTypes.bool.isRequired,
4391
4365
  isTestAndPreviewMode: PropTypes.bool, // Add new prop type
4366
+ restrictPersonalization: PropTypes.bool,
4367
+ tagListGetPopupContainer: PropTypes.func,
4368
+ tagListPopoverOverlayStyle: PropTypes.object,
4369
+ tagListPopoverOverlayClassName: PropTypes.string,
4392
4370
  };
4393
4371
 
4394
4372
  const mapStateToProps = createStructuredSelector({