@capillarytech/creatives-library 8.0.242-alpha.0 → 8.0.242-alpha.10

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 (216) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/sagas/__tests__/assetPolling.test.js +74 -3
  7. package/sagas/assetPolling.js +8 -1
  8. package/services/api.js +10 -5
  9. package/services/tests/api.test.js +18 -0
  10. package/translations/en.json +0 -1
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +14 -1
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/utils/transformerUtils.js +0 -42
  16. package/v2Components/CapDeviceContent/index.js +61 -56
  17. package/v2Components/CapImageUpload/constants.js +0 -2
  18. package/v2Components/CapImageUpload/index.js +14 -54
  19. package/v2Components/CapImageUpload/index.scss +1 -4
  20. package/v2Components/CapImageUpload/messages.js +0 -4
  21. package/v2Components/CapTagList/index.js +6 -1
  22. package/v2Components/CapTagListWithInput/index.js +5 -1
  23. package/v2Components/CapTagListWithInput/messages.js +1 -1
  24. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  25. package/v2Components/ErrorInfoNote/index.js +412 -72
  26. package/v2Components/ErrorInfoNote/messages.js +22 -0
  27. package/v2Components/ErrorInfoNote/style.scss +279 -2
  28. package/v2Components/HtmlEditor/HTMLEditor.js +220 -91
  29. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1132 -133
  30. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +17 -12
  31. package/v2Components/HtmlEditor/_htmlEditor.scss +107 -45
  32. package/v2Components/HtmlEditor/_index.lazy.scss +1 -1
  33. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +13 -101
  34. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -139
  35. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  36. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  37. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  43. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  44. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  45. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  46. package/v2Components/HtmlEditor/components/PreviewPane/index.js +10 -11
  47. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  48. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +70 -72
  49. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  50. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  51. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +362 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  53. package/v2Components/HtmlEditor/constants.js +29 -20
  54. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  55. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  56. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  57. package/v2Components/HtmlEditor/index.js +1 -1
  58. package/v2Components/HtmlEditor/messages.js +95 -85
  59. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +99 -101
  60. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  61. package/v2Components/HtmlEditor/utils/validationAdapter.js +34 -41
  62. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  63. package/v2Components/TemplatePreview/_templatePreview.scss +44 -24
  64. package/v2Components/TemplatePreview/index.js +47 -32
  65. package/v2Components/TemplatePreview/messages.js +4 -0
  66. package/v2Components/TestAndPreviewSlidebox/index.js +31 -25
  67. package/v2Containers/App/constants.js +0 -5
  68. package/v2Containers/BeeEditor/index.js +82 -80
  69. package/v2Containers/BeePopupEditor/constants.js +10 -0
  70. package/v2Containers/BeePopupEditor/index.js +193 -0
  71. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  72. package/v2Containers/Cap/tests/__snapshots__/index.test.js.snap +0 -1
  73. package/v2Containers/CreativesContainer/SlideBoxContent.js +148 -120
  74. package/v2Containers/CreativesContainer/SlideBoxFooter.js +9 -3
  75. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -2
  76. package/v2Containers/CreativesContainer/constants.js +1 -2
  77. package/v2Containers/CreativesContainer/index.js +173 -193
  78. package/v2Containers/CreativesContainer/messages.js +4 -4
  79. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  80. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +36 -0
  81. package/v2Containers/Email/actions.js +7 -0
  82. package/v2Containers/Email/constants.js +5 -1
  83. package/v2Containers/Email/index.js +13 -0
  84. package/v2Containers/Email/messages.js +32 -0
  85. package/v2Containers/Email/reducer.js +12 -1
  86. package/v2Containers/Email/sagas.js +41 -6
  87. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  88. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1046 -0
  89. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +193 -7
  90. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  91. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  92. package/v2Containers/EmailWrapper/constants.js +2 -0
  93. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +436 -67
  94. package/v2Containers/EmailWrapper/index.js +99 -23
  95. package/v2Containers/EmailWrapper/messages.js +61 -1
  96. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
  97. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  98. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  99. package/v2Containers/InApp/actions.js +7 -0
  100. package/v2Containers/InApp/constants.js +20 -4
  101. package/v2Containers/InApp/index.js +801 -357
  102. package/v2Containers/InApp/index.scss +4 -3
  103. package/v2Containers/InApp/messages.js +7 -3
  104. package/v2Containers/InApp/reducer.js +21 -3
  105. package/v2Containers/InApp/sagas.js +29 -9
  106. package/v2Containers/InApp/selectors.js +25 -5
  107. package/v2Containers/InApp/tests/index.test.js +154 -50
  108. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  109. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  110. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  111. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  112. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  113. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  114. package/v2Containers/InAppWrapper/constants.js +16 -0
  115. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  116. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  117. package/v2Containers/InAppWrapper/index.js +148 -0
  118. package/v2Containers/InAppWrapper/messages.js +49 -0
  119. package/v2Containers/InappAdvance/index.js +1099 -0
  120. package/v2Containers/InappAdvance/index.scss +10 -0
  121. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  122. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
  123. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
  124. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
  125. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
  126. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
  127. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
  128. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
  129. package/v2Containers/TagList/index.js +67 -1
  130. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  131. package/v2Containers/Templates/_templates.scss +56 -200
  132. package/v2Containers/Templates/actions.js +1 -2
  133. package/v2Containers/Templates/constants.js +0 -1
  134. package/v2Containers/Templates/index.js +124 -277
  135. package/v2Containers/Templates/messages.js +4 -24
  136. package/v2Containers/Templates/reducer.js +0 -2
  137. package/v2Containers/Templates/tests/index.test.js +0 -10
  138. package/v2Containers/TemplatesV2/index.js +2 -3
  139. package/v2Containers/TemplatesV2/messages.js +0 -4
  140. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
  141. package/v2Components/CapImageUrlUpload/constants.js +0 -19
  142. package/v2Components/CapImageUrlUpload/index.js +0 -455
  143. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  144. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  145. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
  146. package/v2Containers/WebPush/Create/components/ButtonForm.js +0 -175
  147. package/v2Containers/WebPush/Create/components/ButtonItem.js +0 -101
  148. package/v2Containers/WebPush/Create/components/ButtonList.js +0 -144
  149. package/v2Containers/WebPush/Create/components/_buttons.scss +0 -246
  150. package/v2Containers/WebPush/Create/components/tests/ButtonForm.test.js +0 -554
  151. package/v2Containers/WebPush/Create/components/tests/ButtonItem.test.js +0 -607
  152. package/v2Containers/WebPush/Create/components/tests/ButtonList.test.js +0 -633
  153. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonForm.test.js.snap +0 -666
  154. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonItem.test.js.snap +0 -74
  155. package/v2Containers/WebPush/Create/components/tests/__snapshots__/ButtonList.test.js.snap +0 -80
  156. package/v2Containers/WebPush/Create/index.js +0 -1755
  157. package/v2Containers/WebPush/Create/index.scss +0 -123
  158. package/v2Containers/WebPush/Create/messages.js +0 -199
  159. package/v2Containers/WebPush/Create/preview/DevicePreviewContent.js +0 -241
  160. package/v2Containers/WebPush/Create/preview/NotificationContainer.js +0 -290
  161. package/v2Containers/WebPush/Create/preview/PreviewContent.js +0 -81
  162. package/v2Containers/WebPush/Create/preview/PreviewControls.js +0 -240
  163. package/v2Containers/WebPush/Create/preview/PreviewDisclaimer.js +0 -23
  164. package/v2Containers/WebPush/Create/preview/WebPushPreview.js +0 -144
  165. package/v2Containers/WebPush/Create/preview/assets/Light.svg +0 -53
  166. package/v2Containers/WebPush/Create/preview/assets/Top.svg +0 -5
  167. package/v2Containers/WebPush/Create/preview/assets/chrome-icon.png +0 -0
  168. package/v2Containers/WebPush/Create/preview/assets/edge-icon.png +0 -0
  169. package/v2Containers/WebPush/Create/preview/assets/firefox-icon.svg +0 -106
  170. package/v2Containers/WebPush/Create/preview/assets/iOS.svg +0 -26
  171. package/v2Containers/WebPush/Create/preview/assets/opera-icon.svg +0 -18
  172. package/v2Containers/WebPush/Create/preview/assets/safari-icon.svg +0 -29
  173. package/v2Containers/WebPush/Create/preview/components/AndroidMobileChromeHeader.js +0 -44
  174. package/v2Containers/WebPush/Create/preview/components/AndroidMobileExpanded.js +0 -110
  175. package/v2Containers/WebPush/Create/preview/components/IOSHeader.js +0 -45
  176. package/v2Containers/WebPush/Create/preview/components/NotificationExpandedContent.js +0 -72
  177. package/v2Containers/WebPush/Create/preview/components/NotificationHeader.js +0 -55
  178. package/v2Containers/WebPush/Create/preview/components/WindowsChromeExpanded.js +0 -70
  179. package/v2Containers/WebPush/Create/preview/components/tests/AndroidMobileExpanded.test.js +0 -512
  180. package/v2Containers/WebPush/Create/preview/components/tests/__snapshots__/AndroidMobileExpanded.test.js.snap +0 -77
  181. package/v2Containers/WebPush/Create/preview/config/notificationMappings.js +0 -527
  182. package/v2Containers/WebPush/Create/preview/constants.js +0 -162
  183. package/v2Containers/WebPush/Create/preview/notification-container.scss +0 -104
  184. package/v2Containers/WebPush/Create/preview/preview.scss +0 -409
  185. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-chrome.scss +0 -300
  186. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-edge.scss +0 -12
  187. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-firefox.scss +0 -12
  188. package/v2Containers/WebPush/Create/preview/styles/_android-mobile-opera.scss +0 -12
  189. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-chrome.scss +0 -303
  190. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-edge.scss +0 -11
  191. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-firefox.scss +0 -11
  192. package/v2Containers/WebPush/Create/preview/styles/_android-tablet-opera.scss +0 -11
  193. package/v2Containers/WebPush/Create/preview/styles/_base.scss +0 -188
  194. package/v2Containers/WebPush/Create/preview/styles/_ios.scss +0 -106
  195. package/v2Containers/WebPush/Create/preview/styles/_ipados.scss +0 -107
  196. package/v2Containers/WebPush/Create/preview/styles/_macos-chrome.scss +0 -75
  197. package/v2Containers/WebPush/Create/preview/styles/_windows-chrome.scss +0 -174
  198. package/v2Containers/WebPush/Create/preview/tests/DevicePreviewContent.test.js +0 -909
  199. package/v2Containers/WebPush/Create/preview/tests/NotificationContainer.test.js +0 -1077
  200. package/v2Containers/WebPush/Create/preview/tests/PreviewControls.test.js +0 -723
  201. package/v2Containers/WebPush/Create/preview/tests/WebPushPreview.test.js +0 -943
  202. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/DevicePreviewContent.test.js.snap +0 -128
  203. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/NotificationContainer.test.js.snap +0 -121
  204. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/PreviewControls.test.js.snap +0 -144
  205. package/v2Containers/WebPush/Create/preview/tests/__snapshots__/WebPushPreview.test.js.snap +0 -127
  206. package/v2Containers/WebPush/Create/utils/urlValidation.js +0 -116
  207. package/v2Containers/WebPush/Create/utils/urlValidation.test.js +0 -449
  208. package/v2Containers/WebPush/actions.js +0 -60
  209. package/v2Containers/WebPush/constants.js +0 -108
  210. package/v2Containers/WebPush/index.js +0 -2
  211. package/v2Containers/WebPush/reducer.js +0 -104
  212. package/v2Containers/WebPush/sagas.js +0 -119
  213. package/v2Containers/WebPush/selectors.js +0 -65
  214. package/v2Containers/WebPush/tests/reducer.test.js +0 -863
  215. package/v2Containers/WebPush/tests/sagas.test.js +0 -566
  216. package/v2Containers/WebPush/tests/selectors.test.js +0 -960
@@ -0,0 +1,1046 @@
1
+ import React, {
2
+ useState, useEffect, useMemo, useCallback, useRef, useImperativeHandle, forwardRef,
3
+ } from 'react';
4
+ import PropTypes from 'prop-types';
5
+ import { injectIntl, FormattedMessage } from 'react-intl';
6
+ import isEmpty from 'lodash/isEmpty';
7
+ import get from 'lodash/get';
8
+ import _ from 'lodash';
9
+ import { CAP_SPACE_16 } from '@capillarytech/cap-ui-library/styled/variables';
10
+ import CapSpin from '@capillarytech/cap-ui-library/CapSpin';
11
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
12
+ import CapColumn from '@capillarytech/cap-ui-library/CapColumn';
13
+ import CapNotification from '@capillarytech/cap-ui-library/CapNotification';
14
+ import HTMLEditor from '../../../v2Components/HtmlEditor';
15
+ import CapTagListWithInput from '../../../v2Components/CapTagListWithInput';
16
+ import formBuilderMessages from '../../../v2Components/FormBuilder/messages';
17
+ import { validateLiquidTemplateContent } from '../../../utils/commonUtils';
18
+ import { hasLiquidSupportFeature } from '../../../utils/common';
19
+ import history from '../../../utils/history';
20
+ import messages from '../messages';
21
+ import emailMessages from '../../Email/messages';
22
+ import { validateTags } from '../../../utils/tagValidations';
23
+ import {
24
+ TAG, EMBEDDED, DEFAULT, FULL, LIBRARY,
25
+ } from '../../Whatsapp/constants';
26
+ import { EMAIL } from '../../CreativesContainer/constants';
27
+
28
+ /**
29
+ * EmailHTMLEditor Component
30
+ *
31
+ * IMPORTANT: This component is ONLY used when supportCKEditor flag is FALSE (new flow).
32
+ * When supportCKEditor is TRUE, the existing Email component with FormBuilder is used (legacy flow).
33
+ *
34
+ * A completely self-contained component for Email HTML Editor that handles:
35
+ * - Tag loading and management
36
+ * - Tag validation
37
+ * - Content editing with HTMLEditor
38
+ * - Template data extraction for edit mode
39
+ * - Save logic (full mode & library mode) with liquid validation
40
+ * - API calls via Email actions/sagas
41
+ *
42
+ * This component is independent and reusable, similar to Whatsapp and InApp channels.
43
+ */
44
+ const EmailHTMLEditor = (props) => {
45
+ const {
46
+ intl,
47
+ location,
48
+ params,
49
+ getDefaultTags,
50
+ supportedTags,
51
+ metaEntities,
52
+ injectedTags,
53
+ globalActions,
54
+ loadingTags,
55
+ eventContextTags,
56
+ forwardedTags,
57
+ selectedOfferDetails,
58
+ currentOrgDetails,
59
+ isReadOnly = false,
60
+ fetchingLiquidTags = false,
61
+ createTemplateInProgress = false,
62
+ fetchingCmsData = false,
63
+ // Email Redux state
64
+ Email,
65
+ // Email actions for API calls
66
+ emailActions,
67
+ // Full mode props
68
+ isFullMode,
69
+ templateName,
70
+ showTemplateName,
71
+ isGetFormData,
72
+ getFormdata,
73
+ // Library mode props
74
+ templateData: templateDataProp,
75
+ // Uploaded content from zip file
76
+ EmailLayout,
77
+ // Liquid validation
78
+ getLiquidTags,
79
+ showLiquidErrorInFooter,
80
+ onValidationFail,
81
+ // Preview/Test
82
+ // Parent loading control
83
+ setIsLoadingContent,
84
+ forwardedRef,
85
+ } = props;
86
+
87
+
88
+ const { formatMessage } = intl;
89
+
90
+ // State for content and subject
91
+ const [htmlContent, setHtmlContent] = useState('');
92
+ const [loadedHtmlContent, setLoadedHtmlContent] = useState(''); // Stable content for HTMLEditor initialization
93
+ const [subject, setSubject] = useState('');
94
+ const [subjectError, setSubjectError] = useState('');
95
+ // State for template name (extracted from template data in Edit mode)
96
+ const [extractedTemplateName, setExtractedTemplateName] = useState('');
97
+ const [tagValidationError, setTagValidationError] = useState(null);
98
+ const [isLoading, setIsLoading] = useState(true);
99
+
100
+ // Refs for tracking initialization and previous values
101
+ const contentInitializedRef = useRef(false);
102
+ const subjectInitializedRef = useRef(false);
103
+ const lastTemplateIdRef = useRef(null);
104
+ const fetchingTemplateIdRef = useRef(null); // Track which template we're currently fetching
105
+ const prevIsGetFormDataRef = useRef(false);
106
+
107
+ // Compute tags directly from metaEntities (same approach as Email component)
108
+ // Use deep equality check to prevent TagList from re-processing when tags haven't actually changed
109
+ // This fixes the slow expansion and React Intl errors when clicking nested tags
110
+ const tags = useMemo(() => {
111
+ let tagList = get(metaEntities, 'tags.standard', []);
112
+ const { type, module } = location?.query || {};
113
+ if (type === EMBEDDED && module === LIBRARY && !getDefaultTags) {
114
+ tagList = supportedTags || [];
115
+ }
116
+ return tagList;
117
+ }, [metaEntities, supportedTags, location, getDefaultTags]);
118
+
119
+ // Expose method to get formData for TestAndPreviewSlidebox
120
+ useImperativeHandle(forwardedRef, () => ({
121
+ getFormDataForPreview: () => {
122
+ const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
123
+ return {
124
+ "0": {
125
+ [baseLanguage]: {
126
+ 'template-content': htmlContent || '',
127
+ 'is_drag_drop': false,
128
+ },
129
+ activeTab: baseLanguage,
130
+ selectedLanguages: [baseLanguage],
131
+ base: true,
132
+ },
133
+ 'template-subject': subject || '',
134
+ };
135
+ },
136
+ getContentForPreview: () => htmlContent || '',
137
+ }), [htmlContent, subject, currentOrgDetails]);
138
+
139
+ // Check if liquid support is enabled
140
+ const isLiquidEnabled = hasLiquidSupportFeature();
141
+
142
+ // Detect edit mode
143
+ const hasParamsId = params?.id || location?.query?.id || location?.params?.id || location?.pathname?.includes('/edit/');
144
+ const currentTemplateId = templateDataProp?._id || params?.id || location?.query?.id || location?.params?.id
145
+ || location?.pathname?.match(/\/edit\/([^/]+)/)?.[1];
146
+ const isEditMode = !!currentTemplateId || !!hasParamsId;
147
+
148
+ // Load tags on component mount
149
+ useEffect(() => {
150
+ const { type, module } = location?.query || {};
151
+ const isEmbedded = type === EMBEDDED;
152
+ const query = {
153
+ layout: EMAIL,
154
+ type: TAG,
155
+ context: isEmbedded ? module : DEFAULT,
156
+ embedded: isEmbedded ? type : FULL,
157
+ };
158
+ if (getDefaultTags) {
159
+ query.context = getDefaultTags;
160
+ }
161
+ if (globalActions && globalActions.fetchSchemaForEntity) {
162
+ globalActions.fetchSchemaForEntity(query);
163
+ }
164
+ }, []);
165
+
166
+ // Initialize content from EmailLayout (uploaded zip) in create mode
167
+ useEffect(() => {
168
+ // Only check EmailLayout in create mode (not edit mode)
169
+ if (isEditMode) {
170
+ return;
171
+ }
172
+
173
+ // Check if EmailLayout has content from zip upload
174
+ if (EmailLayout) {
175
+ // EmailLayout can be a string (HTML content) or an object with html property
176
+ const uploadedContent = typeof EmailLayout === 'string'
177
+ ? EmailLayout
178
+ : (EmailLayout.html || EmailLayout.content || '');
179
+
180
+ if (uploadedContent) {
181
+ // IMPORTANT: Set both htmlContent and loadedHtmlContent for ZIP upload
182
+ // loadedHtmlContent is used by HTMLEditor's initialContent prop
183
+ setHtmlContent(uploadedContent);
184
+ setLoadedHtmlContent(uploadedContent);
185
+ setIsLoading(false);
186
+ if (setIsLoadingContent) {
187
+ setIsLoadingContent(false);
188
+ }
189
+ } else {
190
+ // No uploaded content, stop loading
191
+ setIsLoading(false);
192
+ if (setIsLoadingContent) {
193
+ setIsLoadingContent(false);
194
+ }
195
+ }
196
+ } else {
197
+ // No EmailLayout, stop loading
198
+ setIsLoading(false);
199
+ if (setIsLoadingContent) {
200
+ setIsLoadingContent(false);
201
+ }
202
+ }
203
+ }, [EmailLayout, isEditMode, setIsLoadingContent]);
204
+
205
+ // Edit mode: Extract template data and load content/subject
206
+ useEffect(() => {
207
+ if (!isEditMode) {
208
+ // Create mode: stop loading immediately
209
+ setIsLoading(false);
210
+ return;
211
+ }
212
+
213
+ const templateDataFromRedux = Email?.templateDetails || Email?.BEETemplate;
214
+ const isTemplateLoading = Email?.getTemplateDetailsInProgress || Email?.fetchingCmsData;
215
+
216
+ // Check if template ID changed (switching templates)
217
+ const templateIdChanged = currentTemplateId
218
+ && lastTemplateIdRef.current
219
+ && currentTemplateId !== lastTemplateIdRef.current;
220
+
221
+ // Reset refs when switching templates
222
+ if (templateIdChanged) {
223
+ contentInitializedRef.current = false;
224
+ subjectInitializedRef.current = false;
225
+ lastTemplateIdRef.current = currentTemplateId;
226
+ setHtmlContent('');
227
+ setSubject('');
228
+ setExtractedTemplateName('');
229
+ }
230
+
231
+ // Set last template ID on first load
232
+ if (currentTemplateId && !lastTemplateIdRef.current) {
233
+ lastTemplateIdRef.current = currentTemplateId;
234
+ }
235
+
236
+ // Check if templateDataProp has complete data
237
+ // Check if templateDataProp has complete data (must have actual content string)
238
+ const hasCompleteTemplateData = templateDataProp && (
239
+ (templateDataProp.emailBody || templateDataProp.html_content || templateDataProp['template-content'])
240
+ || (templateDataProp.base && (templateDataProp.base.html_content || templateDataProp.base.emailBody || templateDataProp.base['template-content']))
241
+ || (templateDataProp.versions?.base && (
242
+ // Check active tab content in versions
243
+ (() => {
244
+ const { base } = templateDataProp.versions;
245
+ const activeTab = base.activeTab || 'en';
246
+ const langData = base[activeTab] || {};
247
+ return langData['template-content'] || langData.html_content || base.html_content || base.emailBody;
248
+ })()
249
+ ))
250
+ );
251
+ if (hasCompleteTemplateData && !contentInitializedRef.current) {
252
+ let extractedContent = '';
253
+ let extractedSubject = '';
254
+ let extractedName = '';
255
+
256
+ if (templateDataProp.base) {
257
+ extractedContent = templateDataProp.base['template-content'] || templateDataProp.base.html_content || templateDataProp.base.emailBody || '';
258
+ extractedSubject = templateDataProp.base.subject || templateDataProp.base.emailSubject || '';
259
+ } else if (templateDataProp.versions && templateDataProp.versions.base) {
260
+ const { base } = templateDataProp.versions;
261
+ const activeTab = base.activeTab || 'en';
262
+ const languageData = base[activeTab] || {};
263
+
264
+ extractedContent = languageData['template-content']
265
+ || languageData.html_content
266
+ || base.html_content
267
+ || base.emailBody
268
+ || '';
269
+
270
+ extractedSubject = base.subject || base.emailSubject || '';
271
+ } else {
272
+ extractedContent = templateDataProp['template-content'] || templateDataProp.emailBody || templateDataProp.html_content || '';
273
+ extractedSubject = templateDataProp.emailSubject || templateDataProp.subject || '';
274
+ }
275
+
276
+ // Extract template name from templateDataProp
277
+ extractedName = templateDataProp.name || get(templateDataProp, 'versions.base.name') || '';
278
+
279
+ // IMPORTANT: Set both htmlContent and loadedHtmlContent
280
+ // loadedHtmlContent is used by HTMLEditor's initialContent prop
281
+ setHtmlContent(extractedContent);
282
+ setLoadedHtmlContent(extractedContent);
283
+ setSubject(extractedSubject);
284
+ setExtractedTemplateName(extractedName);
285
+ contentInitializedRef.current = true;
286
+ subjectInitializedRef.current = true;
287
+ setIsLoading(false);
288
+ if (setIsLoadingContent) {
289
+ setIsLoadingContent(false);
290
+ }
291
+ return;
292
+ }
293
+
294
+ if (currentTemplateId && emailActions?.getTemplateDetails && !isTemplateLoading) {
295
+ const isTemplateIdChanged = lastTemplateIdRef.current !== currentTemplateId;
296
+ const needsContent = !contentInitializedRef.current;
297
+ const alreadyFetching = fetchingTemplateIdRef.current === currentTemplateId;
298
+ const shouldFetch = (isTemplateIdChanged || needsContent) && !alreadyFetching;
299
+
300
+ // Fetch fresh data when template ID changes OR when we don't have content loaded yet
301
+ // BUT only if we're not already fetching this template
302
+ if (shouldFetch) {
303
+ fetchingTemplateIdRef.current = currentTemplateId; // Mark as fetching
304
+ emailActions.getTemplateDetails(currentTemplateId, 'email');
305
+ lastTemplateIdRef.current = currentTemplateId;
306
+ }
307
+ }
308
+
309
+ // **IMPORTANT: Clear stale Redux data in create mode**
310
+ // When not in edit mode (create mode), clear any existing template data from Redux
311
+ // This prevents previous template data from persisting when switching from Edit to Create
312
+ if (!currentTemplateId && !isEditMode && (templateDataFromRedux?._id || templateDataFromRedux?.name)) {
313
+ // Clear stale template data - component will handle via resetTemplateData or clearAllValues
314
+ if (emailActions?.clearAllValues) {
315
+ emailActions.clearAllValues();
316
+ }
317
+ // Reset component state to ensure clean slate for create mode
318
+ setExtractedTemplateName('');
319
+ contentInitializedRef.current = false;
320
+ subjectInitializedRef.current = false;
321
+ lastTemplateIdRef.current = null;
322
+ }
323
+
324
+ // Stop loading if template is loading
325
+ if (isTemplateLoading) {
326
+ return;
327
+ }
328
+
329
+ // Extract from Redux data
330
+ const hasTemplateDataFromRedux = templateDataFromRedux
331
+ && (templateDataFromRedux._id || templateDataFromRedux.name || templateDataFromRedux.versions);
332
+
333
+ if (hasTemplateDataFromRedux && currentTemplateId) {
334
+ const reduxTemplateId = templateDataFromRedux?._id;
335
+
336
+ if (reduxTemplateId === currentTemplateId) {
337
+ const baseData = get(templateDataFromRedux, 'versions.base') || get(templateDataFromRedux, 'base') || {};
338
+ const activeTab = baseData.activeTab
339
+ || get(currentOrgDetails, 'basic_details.base_language', 'en');
340
+ const languageData = baseData[activeTab] || {};
341
+
342
+ const extractedContent = languageData['template-content']
343
+ || languageData.html_content
344
+ || baseData.html_content
345
+ || baseData['template-content']
346
+ || get(templateDataFromRedux, 'html_content')
347
+ || get(templateDataFromRedux, 'template-content')
348
+ || '';
349
+
350
+ const extractedSubject = baseData.subject
351
+ || get(templateDataFromRedux, 'subject')
352
+ || get(templateDataFromRedux, 'versions.base.subject')
353
+ || '';
354
+
355
+ // Extract template name from Redux data
356
+ const extractedName = templateDataFromRedux.name || '';
357
+
358
+ // Smart update logic:
359
+ // 1. Always update loadedHtmlContent if Redux data is different (keep sync with backend)
360
+ // 2. Update htmlContent ONLY if it matches the OLD loadedHtmlContent (user hasn't edited)
361
+ // OR if it's the first initialization
362
+ if (extractedContent !== loadedHtmlContent) {
363
+ setLoadedHtmlContent(extractedContent);
364
+
365
+ if (!contentInitializedRef.current || htmlContent === loadedHtmlContent) {
366
+ setHtmlContent(extractedContent);
367
+ setSubject(extractedSubject);
368
+ setExtractedTemplateName(extractedName);
369
+ }
370
+ } else if (!contentInitializedRef.current) {
371
+ // First load, even if content matches (e.g. empty), ensure we set state
372
+ setHtmlContent(extractedContent);
373
+ setLoadedHtmlContent(extractedContent);
374
+ setSubject(extractedSubject);
375
+ setExtractedTemplateName(extractedName);
376
+ }
377
+
378
+ contentInitializedRef.current = true;
379
+ subjectInitializedRef.current = true;
380
+
381
+ // Only clear fetching ref if we are not loading anymore
382
+ // This prevents race conditions where we load stale data while real fetch is pending
383
+ if (!isTemplateLoading) {
384
+ fetchingTemplateIdRef.current = null;
385
+ }
386
+ }
387
+
388
+ setIsLoading(false);
389
+ if (setIsLoadingContent) {
390
+ setIsLoadingContent(false);
391
+ }
392
+ return;
393
+ }
394
+
395
+ // Fallback: stop loading anyway
396
+ if (!isTemplateLoading && !hasTemplateDataFromRedux && !hasCompleteTemplateData) {
397
+ setHtmlContent('');
398
+ setLoadedHtmlContent(''); // Set stable loaded content
399
+ setSubject('');
400
+ setExtractedTemplateName('');
401
+ setIsLoading(false);
402
+ if (setIsLoadingContent) {
403
+ setIsLoadingContent(false);
404
+ }
405
+ }
406
+ }, [
407
+ Email?.templateDetails,
408
+ Email?.BEETemplate,
409
+ Email?.getTemplateDetailsInProgress,
410
+ Email?.fetchingCmsData,
411
+ templateDataProp,
412
+ currentTemplateId,
413
+ isEditMode,
414
+ emailActions,
415
+ currentOrgDetails,
416
+ ]);
417
+
418
+ // Handle loading state
419
+ useEffect(() => {
420
+ const isAnyApiInProgress = loadingTags || fetchingLiquidTags || createTemplateInProgress || fetchingCmsData;
421
+
422
+ // Stop loading when no API is in progress and tags are loaded
423
+ // Use optional chaining to safely access tags
424
+ if (!isAnyApiInProgress && Array.isArray(tags) && tags?.length >= 0) {
425
+ setIsLoading(false);
426
+ } else if (isAnyApiInProgress) {
427
+ setIsLoading(true);
428
+ }
429
+ }, [loadingTags, tags, fetchingLiquidTags, createTemplateInProgress, fetchingCmsData]);
430
+
431
+ // Handle content change from HTMLEditor
432
+ const handleContentChange = useCallback((content) => {
433
+ setHtmlContent(content);
434
+
435
+ // Validate tags
436
+ if (tags.length > 0 || !isEmpty(injectedTags)) {
437
+ const validationResult = validateTags({
438
+ content,
439
+ tagsParam: tags,
440
+ injectedTagsParams: injectedTags,
441
+ location,
442
+ tagModule: getDefaultTags,
443
+ eventContextTags,
444
+ });
445
+
446
+ if (!validationResult.valid) {
447
+ setTagValidationError(validationResult);
448
+ } else {
449
+ setTagValidationError(null);
450
+ }
451
+ }
452
+ }, [tags, injectedTags, location, getDefaultTags, eventContextTags]);
453
+
454
+ // Handle tag insertion into Subject field
455
+ const handleSubjectTagSelect = useCallback((data) => {
456
+ if (data) {
457
+ const tagToInsert = `{{${data}}}`;
458
+ const input = document.getElementById('template-subject') || document.querySelector('#template-subject input');
459
+ let subjectValue = subject || '';
460
+ try {
461
+ if (input && (typeof input.selectionStart === 'number')) {
462
+ const startPos = input.selectionStart;
463
+ const endPos = input.selectionEnd;
464
+ subjectValue = `${subjectValue.substring(0, startPos)}${tagToInsert}${subjectValue.substring(endPos)}`;
465
+ setSubject(subjectValue);
466
+ try {
467
+ input.focus();
468
+ const newPos = startPos + tagToInsert.length;
469
+ input.selectionStart = newPos;
470
+ input.selectionEnd = newPos;
471
+ } catch (e) {
472
+ // Ignore focus errors
473
+ }
474
+ } else {
475
+ subjectValue = `${subjectValue}${tagToInsert}`;
476
+ setSubject(subjectValue);
477
+ if (input) {
478
+ try {
479
+ input.value = subjectValue;
480
+ } catch (e) {
481
+ // Ignore value setting errors
482
+ }
483
+ }
484
+ }
485
+ } catch (e) {
486
+ // Fallback: safe append
487
+ subjectValue = `${subjectValue}${tagToInsert}`;
488
+ setSubject(subjectValue);
489
+ }
490
+ }
491
+ }, [subject]);
492
+
493
+ // Handle subject change
494
+ const handleSubjectChange = useCallback((e) => {
495
+ const newSubject = e.target.value;
496
+ setSubject(newSubject);
497
+ if (newSubject && subjectError) {
498
+ setSubjectError('');
499
+ }
500
+ }, [subjectError]);
501
+
502
+ // Handle Save/Update with liquid validation
503
+ const handleSave = useCallback(() => {
504
+ // 1. Validate Subject - BLOCKING (matches CK/BEE behavior)
505
+ if (!subject || !subject.trim()) {
506
+ const errorMessage = formatMessage(emailMessages["Email Subject cannot be empty."]);
507
+ setSubjectError(errorMessage);
508
+ // Reset parent state so next click is detected as a change
509
+ if (onValidationFail) {
510
+ onValidationFail();
511
+ }
512
+ // IMPORTANT: Return here to block save - matches CK/BEE editor behavior
513
+ return;
514
+ }
515
+ // Clear error if subject is valid
516
+ if (subjectError) {
517
+ setSubjectError('');
518
+ }
519
+
520
+
521
+ // 2. Validate Content Tags
522
+ // For NON-liquid orgs: BLOCKING validation (matches CK/BEE behavior)
523
+ // For liquid orgs: Non-blocking (extractTags API will validate)
524
+ if (tags.length > 0 || !isEmpty(injectedTags)) {
525
+ const validationResult = validateTags({
526
+ content: htmlContent,
527
+ tagsParam: tags,
528
+ injectedTagsParams: injectedTags,
529
+ location,
530
+ tagModule: getDefaultTags,
531
+ eventContextTags,
532
+ });
533
+
534
+ const hasUnsupportedTags = validationResult?.unsupportedTags?.length > 0;
535
+ if (!validationResult?.valid || hasUnsupportedTags) {
536
+ setTagValidationError(validationResult);
537
+
538
+ // IMPORTANT: For non-liquid orgs, block save (like CK/BEE editor)
539
+ // For liquid orgs, continue (extractTags API will validate)
540
+ if (!isLiquidEnabled) {
541
+ // Show notification popup like CK/BEE editor
542
+ const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
543
+
544
+ const contentNotValidMsg = intl.formatMessage(formBuilderMessages.contentNotValidLanguage);
545
+ let errorMessage = `${contentNotValidMsg} ${baseLanguage}`;
546
+
547
+ if (hasUnsupportedTags) {
548
+ const unsupportedTagsMsg = intl.formatMessage(formBuilderMessages.unsupportedTags);
549
+ errorMessage += `\n${unsupportedTagsMsg} ${validationResult?.unsupportedTags?.join(', ')}`;
550
+ }
551
+ if (validationResult?.missingTags?.length > 0) {
552
+ const missingTagsMsg = intl.formatMessage(formBuilderMessages.missingTags);
553
+ errorMessage += `\n${missingTagsMsg} ${validationResult?.missingTags?.join(', ')}`;
554
+ }
555
+
556
+ const type = 'error';
557
+ CapNotification[type]({
558
+ message: `${type.toUpperCase()} ! ! ! `,
559
+ description: errorMessage,
560
+ duration: 5,
561
+ });
562
+
563
+ // Reset parent state so next click is detected as a change
564
+ if (onValidationFail) {
565
+ onValidationFail();
566
+ }
567
+ // Block save for non-liquid orgs
568
+ return;
569
+ }
570
+ // For liquid orgs, just show warning and continue
571
+ } else {
572
+ // Clear tag errors if valid
573
+ if (tagValidationError) {
574
+ setTagValidationError(null);
575
+ }
576
+ }
577
+ }
578
+
579
+
580
+ const baseLanguage = get(currentOrgDetails, 'basic_details.base_language', 'en');
581
+
582
+ // Actual save function - called after liquid validation (if enabled) or directly
583
+ const performSave = () => {
584
+ if (isFullMode) {
585
+ // Full mode: Call email actions directly
586
+ const templateDataFromRedux = Email?.templateDetails || Email?.BEETemplate;
587
+ // IMPORTANT: Only use currentTemplateId from URL/params - don't fallback to Redux data
588
+ // which might be stale from previous template in create mode
589
+ const templateId = currentTemplateId;
590
+ const isEditModeForSave = !!templateId;
591
+
592
+ const langId = get(currentOrgDetails, `basic_details.languages.${baseLanguage}.lang_id`, '');
593
+ const language = get(currentOrgDetails, `basic_details.languages.${baseLanguage}.language`, baseLanguage);
594
+
595
+ // Generate or reuse tabKey
596
+ let tabKey = _.uniqueId();
597
+ if (isEditMode && templateDataFromRedux) {
598
+ const existingTabKey = get(templateDataFromRedux, 'versions.base.tabKey')
599
+ || get(templateDataFromRedux, 'base.tabKey');
600
+ if (existingTabKey) {
601
+ tabKey = existingTabKey;
602
+ }
603
+ }
604
+
605
+ const languageData = {
606
+ 'template-content': htmlContent || '',
607
+ "is_drag_drop": false,
608
+ "drag_drop_id": '',
609
+ "lang_id": langId,
610
+ "iso_code": baseLanguage,
611
+ "language": language,
612
+ "tabKey": tabKey,
613
+ };
614
+
615
+ const baseStructure = {
616
+ [baseLanguage]: languageData,
617
+ activeTab: baseLanguage,
618
+ selectedLanguages: [baseLanguage],
619
+ base: true,
620
+ tabKey,
621
+ subject: subject || '',
622
+ };
623
+
624
+ const historyEntry = {
625
+ [baseLanguage]: { ...languageData },
626
+ activeTab: baseLanguage,
627
+ selectedLanguages: [baseLanguage],
628
+ base: true,
629
+ tabKey,
630
+ subject: subject || '',
631
+ };
632
+
633
+ const finalTemplateName = isEditModeForSave ? (extractedTemplateName || '') : (templateName || '');
634
+
635
+ const obj = {
636
+ type: EMAIL,
637
+ // In Edit mode, use extractedTemplateName; in Create mode, use templateName prop
638
+ name: finalTemplateName,
639
+ versions: {
640
+ base: baseStructure,
641
+ history: [historyEntry],
642
+ },
643
+ };
644
+
645
+ if (isEditModeForSave && templateId) {
646
+ obj._id = templateId;
647
+ }
648
+
649
+ if (emailActions?.transformEmailTemplate && emailActions?.createTemplate) {
650
+ emailActions.transformEmailTemplate(obj, (newEmail) => {
651
+ emailActions.createTemplate(newEmail, (createResponse) => {
652
+ // Handle error response (from 409 or other failures)
653
+ if (createResponse?.error) {
654
+ // Error already shown by Email component via stopValidation
655
+ // Just reset parent state so next click is detected
656
+ if (onValidationFail) {
657
+ onValidationFail();
658
+ }
659
+ return;
660
+ }
661
+
662
+ // Handle success response
663
+ if (createResponse && createResponse.templateId) {
664
+ const successMessage = formatMessage(emailMessages.emailCreateSuccess);
665
+ CapNotification.success({ message: successMessage, key: 'create-template-success' });
666
+
667
+ const module = location?.query?.module || 'default';
668
+ const type = location?.query?.type;
669
+ const isLanguageSupport = location?.query?.isLanguageSupport || false;
670
+ const isBEESupport = (location?.query?.isBEESupport !== "false") || false;
671
+
672
+ if (getFormdata) {
673
+ const { versions, ...rest } = createResponse.templateId;
674
+ getFormdata({ value: versions, ...rest, validity: true });
675
+ } else {
676
+ const queryParams = type === 'embedded'
677
+ ? {
678
+ type: 'embedded', module, isLanguageSupport, isBEESupport,
679
+ }
680
+ : { module, isLanguageSupport, isBEESupport };
681
+
682
+ const searchParams = new URLSearchParams();
683
+ Object.keys(queryParams).forEach((key) => {
684
+ if (queryParams[key] !== undefined && queryParams[key] !== null) {
685
+ searchParams.append(key, queryParams[key]);
686
+ }
687
+ });
688
+
689
+ history.push({
690
+ pathname: '/email',
691
+ search: searchParams.toString() ? `?${searchParams.toString()}` : '',
692
+ });
693
+ }
694
+ }
695
+ });
696
+ });
697
+ }
698
+ } else {
699
+ // Library mode: Use getFormdata flow
700
+ const tmpData = {
701
+ html_content: htmlContent || '',
702
+ is_drag_drop: false,
703
+ lang_id: get(currentOrgDetails, `basic_details.languages.${baseLanguage}.lang_id`, ''),
704
+ iso_code: baseLanguage,
705
+ language: get(currentOrgDetails, `basic_details.languages.${baseLanguage}.language`, baseLanguage),
706
+ };
707
+
708
+ const libraryPayload = {
709
+ base: tmpData,
710
+ secondary_templates: [{ template_data: tmpData }],
711
+ };
712
+ libraryPayload.base.subject = subject || '';
713
+
714
+ if (location?.query?.module === 'library' && isGetFormData && getFormdata) {
715
+ const response = {
716
+ action: "getFormData",
717
+ postAction: 'next',
718
+ id: get(Email, 'templateDetails._id', ''),
719
+ value: libraryPayload,
720
+ validity: true,
721
+ type: EMAIL,
722
+ };
723
+ getFormdata(response);
724
+ } else if (getFormdata) {
725
+ getFormdata({
726
+ value: libraryPayload,
727
+ validity: true,
728
+ type: EMAIL,
729
+ });
730
+ }
731
+ }
732
+ };
733
+
734
+ // If liquid enabled, validate first using extractTags API
735
+ if (isLiquidEnabled && getLiquidTags) {
736
+ const onError = ({ standardErrors, liquidErrors }) => {
737
+ if (showLiquidErrorInFooter) {
738
+ showLiquidErrorInFooter({
739
+ STANDARD_ERROR_MSG: standardErrors || [],
740
+ LIQUID_ERROR_MSG: liquidErrors || [],
741
+ });
742
+ }
743
+ // Don't reset ref here - liquid validation is async and resetting causes infinite loop
744
+ // The parent's isGetFormData will be reset by onValidationFail, and the next click will be detected
745
+ if (onValidationFail) {
746
+ onValidationFail();
747
+ }
748
+ };
749
+
750
+ const onSuccess = () => {
751
+ performSave();
752
+ };
753
+
754
+ validateLiquidTemplateContent(htmlContent || '', {
755
+ getLiquidTags: getLiquidTags
756
+ ? (inputContent, callback) => getLiquidTags(inputContent, callback)
757
+ : (inputContent, callback) => globalActions?.getLiquidTags?.(inputContent, callback),
758
+ formatMessage: intl.formatMessage,
759
+ messages: formBuilderMessages,
760
+ onError,
761
+ onSuccess,
762
+ tagLookupMap: metaEntities?.tagLookupMap,
763
+ eventContextTags,
764
+ isLiquidFlow: true,
765
+ forwardedTags: forwardedTags || {},
766
+ });
767
+ } else {
768
+ performSave();
769
+ }
770
+ }, [
771
+ subject,
772
+ htmlContent,
773
+ tags,
774
+ injectedTags,
775
+ location,
776
+ getDefaultTags,
777
+ eventContextTags,
778
+ formatMessage,
779
+ subjectError,
780
+ isFullMode,
781
+ currentOrgDetails,
782
+ Email,
783
+ currentTemplateId,
784
+ templateDataProp,
785
+ templateName,
786
+ emailActions,
787
+ getFormdata,
788
+ isGetFormData,
789
+ isLiquidEnabled,
790
+ getLiquidTags,
791
+ showLiquidErrorInFooter,
792
+ metaEntities,
793
+ forwardedTags,
794
+ globalActions,
795
+ intl,
796
+ extractedTemplateName,
797
+ ]);
798
+
799
+ // Trigger save when isGetFormData becomes true (Create/Done button clicked)
800
+ useEffect(() => {
801
+ const isGetFormDataChanged = isGetFormData && !prevIsGetFormDataRef.current;
802
+ const wasReset = !isGetFormData && prevIsGetFormDataRef.current;
803
+
804
+ if (isGetFormDataChanged) {
805
+ handleSave();
806
+ }
807
+
808
+ // Reset ref when parent resets isGetFormData (e.g., after validation failure)
809
+ if (wasReset) {
810
+ prevIsGetFormDataRef.current = false;
811
+ return;
812
+ }
813
+
814
+ prevIsGetFormDataRef.current = isGetFormData;
815
+ }, [isGetFormData, handleSave]);
816
+
817
+ // Handle tag context change
818
+ const handleOnTagsContextChange = useCallback((data) => {
819
+ const { type, module } = location?.query || {};
820
+ const isEmbedded = type === EMBEDDED;
821
+ const contextValue = data || (isEmbedded ? module : DEFAULT);
822
+ const query = {
823
+ layout: EMAIL,
824
+ type: TAG,
825
+ context: contextValue ? contextValue.toLowerCase() : contextValue,
826
+ embedded: isEmbedded ? type : FULL,
827
+ };
828
+ if (globalActions && globalActions.fetchSchemaForEntity) {
829
+ globalActions.fetchSchemaForEntity(query);
830
+ }
831
+ }, [location, globalActions]);
832
+
833
+
834
+ const spinTip = fetchingLiquidTags ? <FormattedMessage {...formBuilderMessages.liquidSpinText} /> : '';
835
+
836
+ // Handle template name change
837
+ // Call showTemplateName when templateName is available (for CreativesContainer header)
838
+ // This matches the behavior of Email component which calls showTemplateName in onFormDataChange
839
+ // In Edit mode, use extractedTemplateName from template data; in Create mode, use templateName prop
840
+ const { onFormDataChange: onFormDataChangeProp } = props;
841
+
842
+ // Create onFormDataChange callback that updates extractedTemplateName in Edit mode
843
+ // This matches the Email component's onFormDataChange which updates its state
844
+ const handleFormDataChange = useCallback((updatedFormData) => {
845
+ const newTemplateName = updatedFormData?.['template-name'] || '';
846
+
847
+ // In Edit mode, update extractedTemplateName state (similar to Email component updating its formData state)
848
+ if (isEditMode && newTemplateName !== extractedTemplateName) {
849
+ setExtractedTemplateName(newTemplateName);
850
+ }
851
+
852
+ // Call the parent's onFormDataChange if provided
853
+ if (onFormDataChangeProp) {
854
+ onFormDataChangeProp(updatedFormData);
855
+ }
856
+
857
+ // Call showTemplateName again with updated formData (same pattern as Email component)
858
+ if (showTemplateName && isFullMode) {
859
+ showTemplateName({ formData: updatedFormData, onFormDataChange: handleFormDataChange });
860
+ }
861
+ }, [isEditMode, extractedTemplateName, onFormDataChangeProp, showTemplateName, isFullMode]);
862
+
863
+ useEffect(() => {
864
+ if (showTemplateName && isFullMode) {
865
+ // In Edit mode, use extractedTemplateName; in Create mode, use templateName prop
866
+ const nameToUse = isEditMode ? extractedTemplateName : templateName;
867
+ const formData = {
868
+ 'template-name': nameToUse || '',
869
+ };
870
+ // Pass handleFormDataChange callback so CreativesContainer can update template name
871
+ // This is the same pattern used in Email component
872
+ showTemplateName({ formData, onFormDataChange: handleFormDataChange });
873
+ }
874
+ }, [showTemplateName, isFullMode, templateName, extractedTemplateName, isEditMode, handleFormDataChange]);
875
+
876
+ return (
877
+ <CapSpin spinning={isLoading} tip={spinTip}>
878
+ <CapRow>
879
+ <CapColumn span={24}>
880
+ {/* Subject Field */}
881
+ <CapColumn span={24} style={{ marginBottom: CAP_SPACE_16 }}>
882
+ <CapTagListWithInput
883
+ inputId="template-subject"
884
+ inputValue={subject}
885
+ inputOnChange={handleSubjectChange}
886
+ inputPlaceholder={formatMessage(messages.enterEmailSubject)}
887
+ inputRequired
888
+ inputErrorMessage={subjectError}
889
+ headingText={formatMessage(messages.subject)}
890
+ headingType="h4"
891
+ headingStyle={{ marginRight: '85%' }}
892
+ onTagSelect={handleSubjectTagSelect}
893
+ onContextChange={handleOnTagsContextChange}
894
+ location={location}
895
+ tags={tags}
896
+ injectedTags={injectedTags || {}}
897
+ selectedOfferDetails={selectedOfferDetails}
898
+ eventContextTags={eventContextTags}
899
+ showHeading
900
+ showTagList
901
+ showInput
902
+ containerStyle={{
903
+ display: 'flex',
904
+ flexDirection: 'column',
905
+ }}
906
+ popoverPlacement="bottomRight"
907
+ />
908
+ </CapColumn>
909
+
910
+ {/* Tag Validation Error Display */}
911
+ {/* {tagValidationError && !tagValidationError.valid && (
912
+ <CapColumn span={24} style={{ marginBottom: CAP_SPACE_16 }}>
913
+ <CapNotification
914
+ type="error"
915
+ message={formatMessage({
916
+ id: 'creatives.containersV2.Email.tagValidationError',
917
+ defaultMessage: 'Tag validation error',
918
+ })}
919
+ description={(
920
+ <CapRow>
921
+ {tagValidationError?.missingTags?.length > 0 && (
922
+ <CapColumn span={24}>
923
+ Missing tags:
924
+ {' '}
925
+ {tagValidationError.missingTags.join(', ')}
926
+ </CapColumn>
927
+ )}
928
+ {tagValidationError?.unsupportedTags?.length > 0 && (
929
+ <CapColumn span={24}>
930
+ Unsupported tags:
931
+ {' '}
932
+ {tagValidationError.unsupportedTags.join(', ')}
933
+ </CapColumn>
934
+ )}
935
+ {tagValidationError?.isBraceError && (
936
+ <CapColumn span={24}>
937
+ Unbalanced brackets detected
938
+ </CapColumn>
939
+ )}
940
+ </CapRow>
941
+ )}
942
+ />
943
+ </CapColumn>
944
+ )} */}
945
+
946
+ <HTMLEditor
947
+ variant="email"
948
+ initialContent={loadedHtmlContent || ''}
949
+ onContentChange={handleContentChange}
950
+ onSave={handleSave}
951
+ readOnly={isReadOnly}
952
+ showFullscreenButton
953
+ autoSave={false}
954
+ tags={tags}
955
+ injectedTags={injectedTags}
956
+ location={location}
957
+ eventContextTags={eventContextTags}
958
+ selectedOfferDetails={selectedOfferDetails}
959
+ channel={EMAIL}
960
+ userLocale={intl.locale || 'en'}
961
+ moduleFilterEnabled={location?.query?.type !== EMBEDDED}
962
+ onTagContextChange={handleOnTagsContextChange}
963
+ isLiquidEnabled={isLiquidEnabled}
964
+ isFullMode={isFullMode}
965
+ />
966
+ </CapColumn>
967
+ </CapRow>
968
+ </CapSpin>
969
+ );
970
+ };
971
+
972
+ EmailHTMLEditor.propTypes = {
973
+ intl: PropTypes.object.isRequired,
974
+ location: PropTypes.object,
975
+ params: PropTypes.object,
976
+ getDefaultTags: PropTypes.string,
977
+ supportedTags: PropTypes.array,
978
+ metaEntities: PropTypes.object,
979
+ injectedTags: PropTypes.object,
980
+ globalActions: PropTypes.object,
981
+ loadingTags: PropTypes.bool,
982
+ eventContextTags: PropTypes.array,
983
+ forwardedTags: PropTypes.object,
984
+ selectedOfferDetails: PropTypes.array,
985
+ currentOrgDetails: PropTypes.object,
986
+ isReadOnly: PropTypes.bool,
987
+ fetchingLiquidTags: PropTypes.bool,
988
+ createTemplateInProgress: PropTypes.bool,
989
+ fetchingCmsData: PropTypes.bool,
990
+ // Email Redux state
991
+ Email: PropTypes.object,
992
+ // Email actions
993
+ emailActions: PropTypes.object,
994
+ // Full mode props
995
+ isFullMode: PropTypes.bool,
996
+ templateName: PropTypes.string,
997
+ showTemplateName: PropTypes.func,
998
+ onFormDataChange: PropTypes.func,
999
+ isGetFormData: PropTypes.bool,
1000
+ getFormdata: PropTypes.func,
1001
+ // Library mode props
1002
+ templateData: PropTypes.object,
1003
+ // Uploaded content from zip file
1004
+ EmailLayout: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), // eslint-disable-line react/require-default-props
1005
+ // Liquid validation
1006
+ getLiquidTags: PropTypes.func,
1007
+ showLiquidErrorInFooter: PropTypes.func,
1008
+ // Preview/Test
1009
+ // Parent loading control
1010
+ setIsLoadingContent: PropTypes.func,
1011
+ };
1012
+
1013
+ EmailHTMLEditor.defaultProps = {
1014
+ location: {},
1015
+ params: {},
1016
+ getDefaultTags: null,
1017
+ supportedTags: [],
1018
+ showTemplateName: null,
1019
+ metaEntities: {},
1020
+ injectedTags: {},
1021
+ globalActions: {},
1022
+ loadingTags: false,
1023
+ eventContextTags: [],
1024
+ forwardedTags: {},
1025
+ selectedOfferDetails: [],
1026
+ currentOrgDetails: {},
1027
+ isReadOnly: false,
1028
+ fetchingLiquidTags: false,
1029
+ createTemplateInProgress: false,
1030
+ fetchingCmsData: false,
1031
+ Email: {},
1032
+ emailActions: {},
1033
+ isFullMode: false,
1034
+ templateName: '',
1035
+ onFormDataChange: null,
1036
+ isGetFormData: false,
1037
+ getFormdata: null,
1038
+ templateData: null,
1039
+ getLiquidTags: null,
1040
+ showLiquidErrorInFooter: null,
1041
+ setIsLoadingContent: null,
1042
+ };
1043
+
1044
+ const EmailHTMLEditorWithIntl = injectIntl(EmailHTMLEditor);
1045
+
1046
+ export default forwardRef((props, ref) => <EmailHTMLEditorWithIntl {...props} forwardedRef={ref} />);