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
@@ -39,7 +39,8 @@ import { v2MobilePushEditSagas } from './sagas';
39
39
  import v2MobilePushEditReducer from './reducer';
40
40
  import * as globalActions from '../../Cap/actions';
41
41
  import { MAPP_SDK } from './constants';
42
- import { isEmbeddedEditOrPreview } from '../../../utils/commonUtils';
42
+ import { checkForPersonalizationTokens, isEmbeddedEditOrPreview, validateMobilePushContent } from '../../../utils/commonUtils';
43
+ import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
43
44
  import { EMBEDDED } from '../../Whatsapp/constants';
44
45
  import { OUTBOUND } from '../../../v2Components/FormBuilder/constants';
45
46
  import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox';
@@ -47,6 +48,17 @@ import TestAndPreviewSlidebox from '../../../v2Components/TestAndPreviewSlidebox
47
48
  const PrefixWrapper = styled.div`
48
49
  margin-right: 16px;
49
50
  `;
51
+
52
+ const safeParseDeeplinkConfig = (value) => {
53
+ if (!value || typeof value !== 'string') return [];
54
+ try {
55
+ const parsed = JSON.parse(value);
56
+ return Array.isArray(parsed) ? parsed : [];
57
+ } catch {
58
+ return [];
59
+ }
60
+ };
61
+
50
62
  export class Edit extends React.Component { // eslint-disable-line react/prefer-stateless-function
51
63
  constructor(props) {
52
64
  super(props);
@@ -57,6 +69,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
57
69
  schema: {},
58
70
  currentTab: 1,
59
71
  editData: {},
72
+ errorData: [],
60
73
  loading: false,
61
74
  isFormValid: true,
62
75
  injectedTags: {},
@@ -68,6 +81,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
68
81
  this.getPrimaryCtaFields = getPrimaryCtaFields.bind(this);
69
82
  this.getSecondaryCtaFields = getSecondaryCtaFields.bind(this);
70
83
  this.getLinkTypeFields = getLinkTypeFields.bind(this);
84
+ // Guard: only one initial meta/TAG fetch (getTags can be invoked from multiple code paths)
85
+ this.hasFetchedInitialTagsRef = false;
86
+ // Guard: avoid duplicate fetch when multiple TagList instances trigger same context
87
+ this.lastFetchedTagContextRef = null;
71
88
  }
72
89
  componentWillMount() {
73
90
  this.props.actions.getWeCrmAccounts("mobilepush");
@@ -92,11 +109,12 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
92
109
  if (!_.isEmpty(this.state.schema)) {
93
110
  this.injectEvents(this.state.schema);
94
111
  }
95
- if (this.props.location.query && (typeof this.props.location.query.module === 'undefined' || this.props.isFullMode === false)) { // when this containers is imported from creatives library
96
- const templateId = get(this, "props.params.id");
97
- if (templateId) {
112
+ const templateId = get(this, "props.params.id");
113
+ const hasTemplateData = !_.isEmpty(this.props.templateData);
114
+ if (this.props.location?.query && (templateId || hasTemplateData)) {
115
+ if (templateId && templateId !== 'temp') {
98
116
  this.props.actions.getTemplateDetails(templateId);
99
- } else {
117
+ } else if (hasTemplateData) {
100
118
  this.props.actions.setTemplateDetails(this.props.templateData);
101
119
  }
102
120
  }
@@ -109,8 +127,47 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
109
127
  }
110
128
  }
111
129
  componentWillReceiveProps(nextProps) {
112
- if (nextProps.isGetFormData && !this.props.isFullMode) {
113
- nextProps.getFormLibraryData(this.getFormData());
130
+ if (nextProps.params?.id !== this.props.params?.id) {
131
+ this.hasFetchedInitialTagsRef = false;
132
+ this.lastFetchedTagContextRef = null;
133
+ }
134
+ // Library mode: on Done click (transition to isGetFormData), run extractTags only when no existing validation errors (braces, personalization, etc.)
135
+ if (nextProps.isGetFormData && !this.props.isGetFormData && !nextProps.isFullMode) {
136
+ // If form already has validation errors (braces, personalization tags, etc.), do not call extractTags; just fail
137
+ if (!this.state.isFormValid) {
138
+ nextProps.onValidationFail?.();
139
+ return;
140
+ }
141
+ if (nextProps.getLiquidTags && nextProps.showLiquidErrorInFooter) {
142
+ const formDataArr = [this.state.formData?.[0], this.state.formData?.[1]];
143
+ validateMobilePushContent(formDataArr, {
144
+ currentTab: this.state.currentTab,
145
+ getLiquidTags: nextProps.getLiquidTags,
146
+ formatMessage: this.props.intl.formatMessage,
147
+ messages: formBuilderMessages,
148
+ onError: (err) => {
149
+ const { standardErrors = [], liquidErrors = [] } = err;
150
+ // _validatePlatformSpecificContent passes { standardErrors: { ANDROID, IOS, generic }, liquidErrors: { ... } }; footer expects arrays
151
+ const toArray = (v) => (Array.isArray(v) ? v : (v && typeof v === 'object' ? [].concat(...Object.values(v)) : []));
152
+ const STANDARD_ERROR_MSG = toArray(standardErrors);
153
+ const LIQUID_ERROR_MSG = toArray(liquidErrors);
154
+ nextProps.showLiquidErrorInFooter(
155
+ { STANDARD_ERROR_MSG, LIQUID_ERROR_MSG },
156
+ this.state.currentTab
157
+ );
158
+ // Only treat as validation failure when there are actual errors; skip when helper called onError with empty reset payload
159
+ const hasErrors = STANDARD_ERROR_MSG.length > 0 || LIQUID_ERROR_MSG.length > 0;
160
+ if (hasErrors) {
161
+ nextProps.onValidationFail?.();
162
+ }
163
+ },
164
+ onSuccess: () => {
165
+ nextProps.getFormLibraryData(this.getFormData());
166
+ },
167
+ });
168
+ } else {
169
+ nextProps.getFormLibraryData(this.getFormData());
170
+ }
114
171
  } else if (nextProps.isGetFormData && this.props.isGetFormData !== nextProps.isGetFormData && this.props.isFullMode && !this.props.Create.createTemplateInProgress) {
115
172
  this.startValidation();
116
173
  }
@@ -167,11 +224,15 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
167
224
  };
168
225
  this.props.actions.getMobilepushTemplatesList('mobilepush', params);
169
226
  }
170
- if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema) && _.isEmpty(this.state.formData)) {
171
- this.setState({fullSchema: nextProps.metaEntities.layouts[0].definition, schema: (nextProps.location.query.module === 'loyalty') ? nextProps.metaEntities.layouts[0].definition.textSchema : {}}, () => {
172
- this.handleEditSchemaOnPropsChange(nextProps, selectedWeChatAccount);
227
+ if (nextProps.metaEntities && nextProps.metaEntities.layouts && nextProps.metaEntities.layouts.length > 0 && _.isEmpty(this.state.fullSchema)) {
228
+ const definition = nextProps.metaEntities.layouts[0].definition;
229
+ const initialSchema = definition?.textSchema || definition?.imageSchema || {};
230
+ this.setState({fullSchema: definition, schema: initialSchema}, () => {
231
+ // Use this.props (latest) in callback to avoid race: templateDetails may have arrived by now
232
+ const latestSelectedAccount = this.getSelectedWeChatAccountFromProps(this.props);
233
+ this.handleEditSchemaOnPropsChange(this.props, latestSelectedAccount);
173
234
  const templateId = get(this, "props.params.id");
174
- if (nextProps.location.query.module !== 'loyalty' && templateId && templateId !== 'temp') {
235
+ if (this.props.location.query.module !== 'loyalty' && templateId && templateId !== 'temp') {
175
236
  this.props.actions.getTemplateDetails(templateId);
176
237
  }
177
238
  if (queryType === EMBEDDED && templateId === 'temp' && _.isEmpty(this.state.formData)) { // when his.props.params.id is temp that means mobile push template content will be passed from post message from parent with startTemplateCreation action
@@ -222,6 +283,15 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
222
283
  onFormDataChange = (formData, tabCount, currentTab, inputField) => {
223
284
  const newFormData = _.cloneDeep(formData);
224
285
  const {templateCta} = this.state;
286
+
287
+ // Check for personalization tokens if restriction is enabled and notify parent
288
+ if (this.props.restrictPersonalization) {
289
+ const hasTokens = checkForPersonalizationTokens(newFormData);
290
+ if (this.props.onPersonalizationTokensChange) {
291
+ this.props.onPersonalizationTokensChange(hasTokens);
292
+ }
293
+ }
294
+
225
295
  if (!_.isEmpty(templateCta) && this.state.currentTab === 2) {
226
296
  newFormData[this.state.currentTab - 1][`secondary-cta-${this.state.currentTab - 1}-label`] = get(templateCta, 'name');
227
297
  newFormData[this.state.currentTab - 1][`secondary-cta-${this.state.currentTab - 1}-action`] = get(templateCta, 'ctaTemplateDetails[0].buttonText');
@@ -230,7 +300,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
230
300
  newFormData[this.state.currentTab - 1][`secondary-cta-${this.state.currentTab - 1}-action2`] = get(templateCta, 'ctaTemplateDetails[1].buttonText');
231
301
  }
232
302
  }
233
- if (newFormData["mobilepush-accounts"] !== this.state.formData["mobilepush-accounts"]) {
303
+ const newAccount = newFormData["mobilepush-accounts"];
304
+ const currentAccount = this.state.formData["mobilepush-accounts"];
305
+ const isExplicitAccountChange = newAccount !== undefined && newAccount !== currentAccount;
306
+ if (isExplicitAccountChange) {
234
307
  this.setMobilePushAccountOptions(this.props.Edit.weCrmAccounts, newFormData["mobilepush-accounts"]);
235
308
  delete newFormData['mobilepush-template'];
236
309
  delete newFormData['template-name'];
@@ -240,6 +313,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
240
313
  if (!_.isEmpty(newFormData[1])) {
241
314
  delete newFormData[1];
242
315
  }
316
+ } else if (newAccount === undefined && currentAccount !== undefined) {
317
+ newFormData["mobilepush-accounts"] = currentAccount;
243
318
  }
244
319
  if (this.state.isSchemaChanged) {
245
320
  this.setState({isSchemaChanged: false});
@@ -271,7 +346,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
271
346
  if (isEmbeddedEditOrPreview(queryType, mode)) {
272
347
  selectedWeChatAccount = !_.isEmpty(editSelectedWeChatAccount)
273
348
  ? editSelectedWeChatAccount
274
- : templateSelectedWeChatAccount;
349
+ : (templateSelectedWeChatAccount || {});
275
350
  } else if (!_.isEmpty(templateSelectedWeChatAccount)) {
276
351
  selectedWeChatAccount = templateSelectedWeChatAccount;
277
352
  }
@@ -285,8 +360,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
285
360
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
286
361
  const id = field.id;
287
362
  const fieldIndex = findIndex(inputFields, {identifier: id});
288
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
289
- const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
363
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
364
+ const deepLinkOptions = _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
290
365
  // let inputId = deeplinkValue && deeplinkValue.toLowerCase() === "deeplink" ? `${id}-select` : `${id}-text`;
291
366
  // if (field.id === "cta-deeplink-secondary-cta-1" && (tabIndex > 1 || this.state.currentTab > 1)) {
292
367
  // inputId = `${inputId}${tabIndex || this.state.currentTab}`;
@@ -657,8 +732,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
657
732
 
658
733
  getLinkName = (link) => {
659
734
  const selectedWeChatAccount = this.getWeChatAccount();
660
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
661
- const deepLinkOptions = _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (l) => l.link === link);
735
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
736
+ const deepLinkOptions = _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (l) => l.link === link);
662
737
  if (deepLinkOptions[0]) {
663
738
  return deepLinkOptions[0].name;
664
739
  }
@@ -667,32 +742,22 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
667
742
  // eslint-disable-next-line react/sort-comp
668
743
  showError = () => {
669
744
  const {intl} = this.props;
670
- const {errorData} = this.state;
745
+ const {errorData, schema} = this.state;
671
746
  const errorMessage = {key: 'validation-error', message: intl.formatMessage(messages.validationError)};
672
- let isTagErrorExist = false;
673
747
  if (!_.isEmpty(this.state.formData) && !this.state.isFormValid) {
674
748
  let tab = this.state.currentTab;
675
- const isAndroidInvalid = Object.values(errorData[0]).includes(true);
676
- const isIosInvalid = Object.values(errorData[1]).includes(true);
749
+ const isAndroidInvalid = Object.values(errorData?.[0] || {}).includes(true);
750
+ const isIosInvalid = Object.values(errorData?.[1] || {}).includes(true);
751
+ const isIosTabVisible = get(schema, 'containers[0].panes[1].isSupported', true) !== false;
677
752
  if (isAndroidInvalid) {
678
753
  tab = 1;
679
754
  errorMessage.description = intl.formatMessage(messages.invalidAndroidMessage);
680
- const invalidTags = errorData[0]['invalid-tags'];
681
- if (!_.isEmpty(invalidTags)) {
682
- isTagErrorExist = true;
683
- errorMessage.description = `${intl.formatMessage(messages.invalidAndroidMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
684
- }
685
- } else if (isIosInvalid) {
755
+ } else if (isIosInvalid && isIosTabVisible) {
686
756
  tab = 2;
687
757
  errorMessage.description = intl.formatMessage(messages.invalidIosMessage);
688
- const invalidTags = errorData[1]['invalid-tags'];
689
- if (!_.isEmpty(invalidTags)) {
690
- isTagErrorExist = true;
691
- errorMessage.description = `${intl.formatMessage(messages.invalidIosMessage)} ${intl.formatMessage(messages.invalidTags)}: ${invalidTags.join(',')} `;
692
- }
693
758
  }
694
759
 
695
- if (tab !== this.state.currentTab || isTagErrorExist) {
760
+ if (tab !== this.state.currentTab) {
696
761
  CapNotification.error(errorMessage);
697
762
  }
698
763
  }
@@ -719,6 +784,27 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
719
784
  parent.postMessage(JSON.stringify(response), '*');
720
785
  };
721
786
 
787
+ /**
788
+ * Compute selectedWeChatAccount from props (used so we can call with latest props
789
+ * in setState callback to avoid stale closure and intermittent empty form).
790
+ */
791
+ getSelectedWeChatAccountFromProps = (props) => {
792
+ const queryType = String(get(props, 'location.query.type', ''))?.toLowerCase();
793
+ const creativesMode = String(get(props, 'creativesMode', ''))?.toLowerCase();
794
+ const { Edit: EditProps, Templates } = props || {};
795
+ const { selectedWeChatAccount: editSelectedWeChatAccount } = EditProps || {};
796
+ const { Templates: nextTemplates } = props || {};
797
+ if (isEmbeddedEditOrPreview(queryType, creativesMode)) {
798
+ return !_.isEmpty(editSelectedWeChatAccount)
799
+ ? editSelectedWeChatAccount
800
+ : nextTemplates?.selectedWeChatAccount;
801
+ }
802
+ if (!_.isEmpty(Templates?.selectedWeChatAccount)) {
803
+ return Templates?.selectedWeChatAccount;
804
+ }
805
+ return undefined;
806
+ };
807
+
722
808
  getFormData = (e) => {
723
809
  const response = {
724
810
  action: "getFormData",
@@ -735,12 +821,16 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
735
821
 
736
822
 
737
823
  setEditState(data, schema) {
824
+ if (!data?.versions?.base || typeof data.versions.base !== 'object') return;
738
825
  const tabCount = Object.keys(data.versions.base).length;
739
826
  const formData = {};
740
827
  if (this.props.location.query.type === 'embedded' && this.props.location.query.module === "loyalty") {
741
828
  formData["mobilepush-accounts"] = this.state.formData["mobilepush-accounts"];
742
829
  formData['mobilepush-template'] = this.state.formData['mobilepush-template'];
743
830
  }
831
+ if (data.definition?.accountId) {
832
+ formData['mobilepush-accounts'] = data.definition.accountId;
833
+ }
744
834
  formData['template-name'] = data.name;
745
835
  const androidData = data.versions.base.ANDROID;
746
836
  const iosData = data.versions.base.IOS;
@@ -968,15 +1058,19 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
968
1058
  });
969
1059
  };
970
1060
  getTags = () => {
1061
+ if (this.hasFetchedInitialTagsRef) {
1062
+ return;
1063
+ }
1064
+ this.hasFetchedInitialTagsRef = true;
1065
+ const context = this.props.getDefaultTags
1066
+ || (this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default');
1067
+ this.lastFetchedTagContextRef = typeof context === 'string' ? context.toLowerCase() : context;
971
1068
  const query = {
972
1069
  layout: 'mobilepush',
973
1070
  type: 'TAG',
974
- context: this.props.location.query.type === 'embedded' ? this.props.location.query.module : 'default',
1071
+ context: this.lastFetchedTagContextRef,
975
1072
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
976
1073
  };
977
- if (this.props.getDefaultTags) {
978
- query.context = this.props.getDefaultTags;
979
- }
980
1074
  this.props.globalActions.fetchSchemaForEntity(query);
981
1075
  }
982
1076
  setModalContent = (type) => {
@@ -1076,8 +1170,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1076
1170
  const currentTab = tab || this.state.currentTab;
1077
1171
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1078
1172
  const selectedWeChatAccount = this.getWeChatAccount();
1079
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1080
- const deepLinkOptions = _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
1173
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1174
+ const deepLinkOptions = _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) );
1081
1175
 
1082
1176
  const inputFields = get(schema, `containers[0].panes[${currentTab - 1}].sections[0].childSections[0].childSections[0].inputFields`);
1083
1177
  forEach(inputFields, (inputField, fieldIndex) => {
@@ -1228,8 +1322,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1228
1322
  delete formData[self.state.currentTab - 1][`${id.replace('-delete', '')}`];
1229
1323
  }
1230
1324
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1231
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1232
- const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1325
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1326
+ const configkeys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1233
1327
  if (configkeys.length) {
1234
1328
  const options = configkeys[0].keys;
1235
1329
  _.forEach(options, (opt) => {
@@ -1340,8 +1434,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1340
1434
  delete formData[self.state.currentTab - 1][`cta-deeplink-${id.replace('delete', 'text')}`];
1341
1435
  }
1342
1436
  if (child.inputFields[fieldIndex] && child.inputFields[fieldIndex].id === "cta-deeplink-select-section") {
1343
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1344
- const configkeys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1437
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1438
+ const configkeys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === selectedDeeplink) : [];
1345
1439
 
1346
1440
  if (configkeys.length) {
1347
1441
  const options = configkeys[0].keys;
@@ -1629,10 +1723,21 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1629
1723
  };
1630
1724
 
1631
1725
  saveFormData = (formData) => {
1726
+ // Check for personalization tokens if restriction is enabled
1727
+ if (this.props.restrictPersonalization) {
1728
+ const hasTokens = checkForPersonalizationTokens(formData);
1729
+ if (hasTokens) {
1730
+ const message = this.props.intl.formatMessage(messages.personalizationTokensErrorMessage);
1731
+ CapNotification.error({message, key: 'personalizationTokensError'});
1732
+ return;
1733
+ }
1734
+ }
1735
+
1632
1736
  const obj = this.getTransformedData(formData);
1633
1737
 
1634
1738
  this.props.actions.editTemplate(obj, this.onUpdateTemplateComplete);
1635
1739
  };
1740
+
1636
1741
  handleFrameTasks = (e) => {
1637
1742
  //
1638
1743
  const type = e.data;
@@ -1694,8 +1799,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1694
1799
  const {id} = field;
1695
1800
  const fieldIndex = findIndex(inputFields, {identifier: id});
1696
1801
  const formDataKey = id.replace("-show-keys", "");
1697
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1698
- let keys = selectedWeChatAccount ? _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === formData[currentTab - 1][formDataKey]) : [];
1802
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1803
+ let keys = selectedWeChatAccount ? _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === formData[currentTab - 1][formDataKey]) : [];
1699
1804
  if (keys[0]) {
1700
1805
  keys = keys[0].keys;
1701
1806
  }
@@ -1725,7 +1830,7 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1725
1830
  inputFields.splice(fieldIndex + index + 1, 0, row);
1726
1831
  });
1727
1832
  if (!tabIndex) { // removes the existing selected options keys so that newly selected options keys can be added.
1728
- const configkeys = _.filter(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === this.state.formData[currentTab - 1][field.id]);
1833
+ const configkeys = _.filter(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (dl) => dl.link === this.state.formData[currentTab - 1][field.id]);
1729
1834
  if (configkeys.length) {
1730
1835
  const options = configkeys[0].keys;
1731
1836
  inputFields = inputFields.slice(0, fieldIndex + 1).concat(inputFields.slice(fieldIndex + options.length + 1, inputFields.length));
@@ -1744,8 +1849,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1744
1849
  const selectedWeChatAccount = this.getWeChatAccount();
1745
1850
  const id = field.id;
1746
1851
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1747
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1748
- const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1852
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1853
+ const deepLinkOptions = selectedWeChatAccount ? _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount?.configs?.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1749
1854
  // const eventsMap = _.cloneDeep(this.state.eventsMap);
1750
1855
  const tabIndex = currentTab || this.state.currentTab;
1751
1856
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
@@ -1775,8 +1880,8 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1775
1880
  const formDataCopy = cloneDeep(formData);
1776
1881
  const schema = inputSchema ? _.cloneDeep(inputSchema) : _.cloneDeep(this.state.schema);
1777
1882
  const selectedWeChatAccount = this.getWeChatAccount();
1778
- const ck = selectedWeChatAccount.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1779
- const deepLinkOptions = selectedWeChatAccount ? _.map(JSON.parse(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1883
+ const ck = selectedWeChatAccount?.configs ? !!selectedWeChatAccount.configs.deeplink : false;
1884
+ const deepLinkOptions = selectedWeChatAccount ? _.map(safeParseDeeplinkConfig(ck ? selectedWeChatAccount.configs.deeplink : '[]'), (link) => ({label: link.name, value: link.link, title: link.link }) ) : [];
1780
1885
  const inputFields = get(schema, `containers[0].panes[${tabIndex - 1}].sections[0].childSections[0].childSections[0].inputFields`);
1781
1886
  const fieldIndex = findIndex(inputFields, {identifier: id});
1782
1887
  let newInputFields;
@@ -1967,10 +2072,15 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1967
2072
  this.setState({ schema });
1968
2073
  };
1969
2074
  handleOnTagsContextChange = (data) => {
2075
+ const context = (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase();
2076
+ if (this.lastFetchedTagContextRef === context) {
2077
+ return;
2078
+ }
2079
+ this.lastFetchedTagContextRef = context;
1970
2080
  const query = {
1971
2081
  layout: 'mobilepush',
1972
2082
  type: 'TAG',
1973
- context: (data || '').toLowerCase() === 'all' ? 'default' : (data || '').toLowerCase(),
2083
+ context,
1974
2084
  embedded: this.props.location.query.type === 'embedded' ? this.props.location.query.type : 'full',
1975
2085
  };
1976
2086
  this.props.globalActions.fetchSchemaForEntity(query);
@@ -1987,33 +2097,94 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
1987
2097
  }
1988
2098
 
1989
2099
  handleEditSchemaOnPropsChange = (nextProps, selectedWeChatAccount) => {
1990
- if (!_.isEmpty(nextProps.templateDetails) && _.isEmpty(this.state.editData) && !_.isEmpty(this.state.fullSchema) && selectedWeChatAccount && (this.props.location.query.type !== 'embedded' || this.props.isFullMode === false)) {
2100
+ const templateDetails = nextProps.templateDetails || nextProps.templateData;
2101
+ const queryType = String(get(this.props, 'location.query.type', ''))?.toLowerCase();
2102
+ const isEmbeddedLibrary = queryType === EMBEDDED && !nextProps.isFullMode;
2103
+ const canSetAccountFromTemplate =
2104
+ (_.isEmpty(selectedWeChatAccount) || !selectedWeChatAccount?.configs) &&
2105
+ templateDetails?.definition?.accountId &&
2106
+ nextProps.Edit?.weCrmAccounts?.length > 0;
2107
+ const hasAccountOrEmbedded =
2108
+ !_.isEmpty(selectedWeChatAccount) ||
2109
+ isEmbeddedLibrary ||
2110
+ canSetAccountFromTemplate ||
2111
+ !nextProps.isFullMode ||
2112
+ queryType === EMBEDDED;
2113
+ const hasTemplateDetails = !_.isEmpty(templateDetails);
2114
+ const hasEditData = !_.isEmpty(this.state.editData);
2115
+ const hasFullSchema = !_.isEmpty(this.state.fullSchema);
2116
+ const locationCheck = (this.props.location.query.type !== 'embedded' || this.props.isFullMode === false || queryType === EMBEDDED);
2117
+ const canPopulateForm =
2118
+ hasTemplateDetails &&
2119
+ !hasEditData &&
2120
+ hasFullSchema &&
2121
+ locationCheck &&
2122
+ hasAccountOrEmbedded;
2123
+ if (canPopulateForm) {
1991
2124
  this.props = nextProps;
1992
- const mode = nextProps.templateDetails.definition ? nextProps.templateDetails.definition.mode : nextProps.templateDetails.mode;
2125
+ if (canSetAccountFromTemplate) {
2126
+ this.setMobilePushAccountOptions(nextProps.Edit.weCrmAccounts, templateDetails.definition.accountId);
2127
+ }
2128
+ const mode = templateDetails.definition ? templateDetails.definition.mode : templateDetails.mode;
1993
2129
  const schema = mode === "text" ? this.state.fullSchema?.textSchema : this.state.fullSchema?.imageSchema;
1994
- const isAndroidSupported = get(this, "props.Templates.selectedWeChatAccount.configs.android") === '1';
1995
- const isIosSupported = get(this, "props.Templates.selectedWeChatAccount.configs.ios") === '1';
2130
+ if (!schema) return;
2131
+ const configs = nextProps.Templates?.selectedWeChatAccount?.configs || {};
2132
+ const hasExplicitConfigs = !_.isEmpty(configs) && ('android' in configs || 'ios' in configs);
2133
+ const isAndroidSupported = hasExplicitConfigs ? configs.android === '1' : true;
2134
+ const isIosSupported = hasExplicitConfigs ? configs.ios === '1' : true;
1996
2135
  if (!isAndroidSupported) {
1997
2136
  const androidField = get(schema, "containers[0].panes[0]");
1998
- androidField.isSupported = false;
1999
- this.setState({currentTab: 2});
2000
- set(schema, "containers[0].panes[0]", androidField);
2137
+ if (androidField) {
2138
+ androidField.isSupported = false;
2139
+ this.setState({currentTab: 2});
2140
+ set(schema, "containers[0].panes[0]", androidField);
2141
+ }
2001
2142
  }
2002
2143
  if (!isIosSupported) {
2003
2144
  const iosField = get(schema, "containers[0].panes[1]");
2004
- iosField.isSupported = false;
2005
-
2006
- set(schema, "containers[0].panes[1]", iosField);
2145
+ if (iosField) {
2146
+ iosField.isSupported = false;
2147
+ set(schema, "containers[0].panes[1]", iosField);
2148
+ }
2149
+ }
2150
+ const dataToUse = this.normalizeTemplateDataForEdit(templateDetails);
2151
+ if (dataToUse?.versions?.base) {
2152
+ this.setEditState(dataToUse, schema);
2153
+ this.getTags();
2154
+ } else {
2155
+ console.warn('[MobliPushEdit] Skipped setEditState - dataToUse.versions.base missing', { dataToUse });
2007
2156
  }
2008
- this.setEditState(nextProps.templateDetails, schema);
2009
- this.getTags();
2010
2157
  }
2158
+ };
2159
+
2160
+ normalizeTemplateDataForEdit = (data) => {
2161
+ if (!data) return null;
2162
+ if (data.versions?.base) return data;
2163
+ if (data.content?.versions?.base) {
2164
+ return { ...data, versions: data.content.versions };
2165
+ }
2166
+ const baseContent = data.content || {};
2167
+ if (baseContent.ANDROID || baseContent.IOS) {
2168
+ return {
2169
+ ...data,
2170
+ versions: { base: baseContent },
2171
+ name: data.name || data.templateName || '',
2172
+ definition: data.definition || {},
2173
+ mode: data.mode || data.definition?.mode || 'text',
2174
+ };
2175
+ }
2176
+ return data;
2011
2177
  }
2012
2178
 
2013
2179
  render() {
2180
+ const queryType = String(get(this.props, 'location.query.type', ''))?.toLowerCase();
2181
+ const isEmbeddedOrLibrary = queryType === 'embedded' || this.props.isFullMode === false;
2182
+ const hasFormContent = !_.isEmpty(this.state.formData) && !_.isEmpty(this.state.schema);
2183
+ const showFormBuilder = !this.props.isLoadingMetaEntities || hasFormContent;
2014
2184
  const schema = this.state.schema;
2015
2185
  const loadingContentEdit = this.state.loadingContentEdit || (this.props.location.query.module === 'dvs' && _.isEmpty(this.state.schema));
2016
- const spinning = get(this.props, "Edit.fetchingWeCrmAccounts") || this.props.isLoadingMetaEntities || loadingContentEdit || this.props.Edit.fetchingDefaultTemplates || this.props.Edit.assetUploading || this.state.loading || (this.props.Edit && (("getTemplateDetailsInProgress" in this.props.Edit && this.props.Edit.getTemplateDetailsInProgress) || ("editTemplateInProgress" in this.props.Edit && this.props.Edit.editTemplateInProgress)));
2186
+ const metaEntitiesBlockSpinner = hasFormContent;
2187
+ const spinning = get(this.props, "Edit.fetchingWeCrmAccounts") || (!metaEntitiesBlockSpinner && this.props.isLoadingMetaEntities) || loadingContentEdit || this.props.Edit.fetchingDefaultTemplates || this.props.Edit.assetUploading || this.state.loading || (this.props.Edit && (("getTemplateDetailsInProgress" in this.props.Edit && this.props.Edit.getTemplateDetailsInProgress) || ("editTemplateInProgress" in this.props.Edit && this.props.Edit.editTemplateInProgress)));
2017
2188
  let tags = this.props.metaEntities && this.props.metaEntities.tags ? this.props.metaEntities.tags.standard : [];
2018
2189
  if (this.props.supportedTags) {
2019
2190
  tags = this.props.supportedTags;
@@ -2023,8 +2194,11 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2023
2194
  <CapSpin spinning={spinning}>
2024
2195
  <CapRow>
2025
2196
  <CapColumn>
2026
- <FormBuilder
2027
- key={!_.isEmpty(schema) ? 'has-schema' : 'no-schema'}
2197
+ {(() => {
2198
+ if (isEmbeddedOrLibrary && !showFormBuilder && hasFormContent) {
2199
+ console.warn('[MobliPushEdit] FormBuilder HIDDEN (isLoadingMetaEntities=true) but formData+schema exist');
2200
+ }
2201
+ return showFormBuilder && <FormBuilder
2028
2202
  channel={MOBILE_PUSH}
2029
2203
  schema={schema}
2030
2204
  showLiquidErrorInFooter={this.props.showLiquidErrorInFooter}
@@ -2059,7 +2233,10 @@ export class Edit extends React.Component { // eslint-disable-line react/prefer-
2059
2233
  hideTestAndPreviewBtn={this.props.hideTestAndPreviewBtn}
2060
2234
  isFullMode={this.props.isFullMode}
2061
2235
  eventContextTags={this.props?.eventContextTags}
2062
- />
2236
+ waitEventContextTags={this.props?.waitEventContextTags}
2237
+ restrictPersonalization={this.props.restrictPersonalization}
2238
+ />;
2239
+ })()}
2063
2240
  </CapColumn>
2064
2241
  {this.props.iosCtasData && this.state.showIosCtaTable &&
2065
2242
  <CapSlideBox
@@ -2158,15 +2335,20 @@ Edit.propTypes = {
2158
2335
  getFormLibraryData: PropTypes.func,
2159
2336
  isGetFormData: PropTypes.bool,
2160
2337
  Create: PropTypes.object,
2161
- onValidationFail: PropTypes.bool,
2338
+ onValidationFail: PropTypes.func,
2162
2339
  onPreviewContentClicked: PropTypes.func,
2163
2340
  onTestContentClicked: PropTypes.func,
2164
2341
  creativesMode: PropTypes.string,
2165
2342
  eventContextTags: PropTypes.array,
2343
+ waitEventContextTags: PropTypes.object,
2344
+ getLiquidTags: PropTypes.func,
2166
2345
  showLiquidErrorInFooter: PropTypes.func,
2167
2346
  showTestAndPreviewSlidebox: PropTypes.bool,
2168
2347
  handleTestAndPreview: PropTypes.func,
2169
2348
  handleCloseTestAndPreview: PropTypes.func,
2349
+ restrictPersonalization: PropTypes.bool,
2350
+ isAnonymousType: PropTypes.bool,
2351
+ onPersonalizationTokensChange: PropTypes.func,
2170
2352
  };
2171
2353
 
2172
2354
  const mapStateToProps = createStructuredSelector({
@@ -334,4 +334,8 @@ export default defineMessages({
334
334
  id: 'creatives.containersV2.MobilePush.Edit.thisSectionDisabledHoverText',
335
335
  defaultMessage: 'This section is being revamped. Till then it will remain disabled.',
336
336
  },
337
+ "personalizationTokensErrorMessage": {
338
+ id: 'creatives.containersV2.MobilePush.Edit.personalizationTokensErrorMessage',
339
+ defaultMessage: 'Personalization tags are not supported for anonymous customers, please remove the tags.',
340
+ },
337
341
  });