@capillarytech/creatives-library 8.0.271 → 8.0.272

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 (149) 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/services/api.js +10 -0
  7. package/services/tests/api.test.js +34 -0
  8. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
  9. package/tests/integration/TemplateCreation/api-response.js +31 -1
  10. package/tests/integration/TemplateCreation/msw-handler.js +2 -0
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +28 -5
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/v2Components/CapDeviceContent/index.js +61 -56
  16. package/v2Components/CapTagList/index.js +6 -1
  17. package/v2Components/CapTagListWithInput/index.js +5 -1
  18. package/v2Components/CapTagListWithInput/messages.js +1 -1
  19. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  20. package/v2Components/ErrorInfoNote/constants.js +1 -0
  21. package/v2Components/ErrorInfoNote/index.js +402 -72
  22. package/v2Components/ErrorInfoNote/messages.js +32 -6
  23. package/v2Components/ErrorInfoNote/style.scss +278 -6
  24. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  25. package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
  26. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
  27. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
  28. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  29. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  30. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  32. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  33. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  34. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  35. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
  36. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
  37. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  43. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
  44. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  45. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  46. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
  47. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
  48. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
  49. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  50. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
  51. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
  53. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  54. package/v2Components/HtmlEditor/constants.js +45 -20
  55. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  56. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
  57. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  58. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  59. package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
  60. package/v2Components/HtmlEditor/index.js +1 -1
  61. package/v2Components/HtmlEditor/messages.js +102 -94
  62. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
  63. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  64. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  65. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  66. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
  67. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  68. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  69. package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
  70. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  71. package/v2Components/MobilePushPreviewV2/index.js +33 -7
  72. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  73. package/v2Components/TemplatePreview/index.js +47 -32
  74. package/v2Components/TemplatePreview/messages.js +4 -0
  75. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  76. package/v2Containers/BeeEditor/index.js +172 -90
  77. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  78. package/v2Containers/BeePopupEditor/constants.js +10 -0
  79. package/v2Containers/BeePopupEditor/index.js +194 -0
  80. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  84. package/v2Containers/CreativesContainer/constants.js +1 -0
  85. package/v2Containers/CreativesContainer/index.js +251 -47
  86. package/v2Containers/CreativesContainer/messages.js +8 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  88. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
  90. package/v2Containers/Email/actions.js +7 -0
  91. package/v2Containers/Email/constants.js +5 -1
  92. package/v2Containers/Email/index.js +234 -29
  93. package/v2Containers/Email/messages.js +32 -0
  94. package/v2Containers/Email/reducer.js +12 -1
  95. package/v2Containers/Email/sagas.js +61 -7
  96. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  97. package/v2Containers/Email/tests/reducer.test.js +46 -0
  98. package/v2Containers/Email/tests/sagas.test.js +320 -29
  99. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
  100. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
  101. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  102. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
  103. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  104. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  105. package/v2Containers/EmailWrapper/constants.js +2 -0
  106. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
  107. package/v2Containers/EmailWrapper/index.js +103 -23
  108. package/v2Containers/EmailWrapper/messages.js +65 -1
  109. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
  110. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
  111. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  112. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  113. package/v2Containers/InApp/actions.js +7 -0
  114. package/v2Containers/InApp/constants.js +20 -4
  115. package/v2Containers/InApp/index.js +802 -360
  116. package/v2Containers/InApp/index.scss +4 -3
  117. package/v2Containers/InApp/messages.js +7 -3
  118. package/v2Containers/InApp/reducer.js +21 -3
  119. package/v2Containers/InApp/sagas.js +29 -9
  120. package/v2Containers/InApp/selectors.js +25 -5
  121. package/v2Containers/InApp/tests/index.test.js +154 -50
  122. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  123. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  124. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  125. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  126. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  127. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  128. package/v2Containers/InAppWrapper/constants.js +16 -0
  129. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  130. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  131. package/v2Containers/InAppWrapper/index.js +148 -0
  132. package/v2Containers/InAppWrapper/messages.js +49 -0
  133. package/v2Containers/InappAdvance/index.js +1099 -0
  134. package/v2Containers/InappAdvance/index.scss +10 -0
  135. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  136. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  137. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  138. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  139. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  140. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  141. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  142. package/v2Containers/TagList/index.js +62 -19
  143. package/v2Containers/Templates/_templates.scss +60 -1
  144. package/v2Containers/Templates/index.js +89 -4
  145. package/v2Containers/Templates/messages.js +4 -0
  146. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  148. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  149. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -1,21 +1,389 @@
1
- import React from "react";
2
- import PropTypes from "prop-types";
3
- import CapRow from "@capillarytech/cap-ui-library/CapRow";
4
- import CapButton from "@capillarytech/cap-ui-library/CapButton";
5
- import CapIcon from "@capillarytech/cap-ui-library/CapIcon";
6
- import CapLabel from "@capillarytech/cap-ui-library/CapLabel";
7
- import CapList from "@capillarytech/cap-ui-library/CapList";
1
+ import React, { useState, useMemo, useEffect } from 'react';
2
+ import PropTypes from 'prop-types';
3
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
4
+ import CapButton from '@capillarytech/cap-ui-library/CapButton';
5
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
6
+ import CapLabel from '@capillarytech/cap-ui-library/CapLabel';
7
+ import CapTab from '@capillarytech/cap-ui-library/CapTab';
8
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
8
9
  import {
9
10
  FormattedMessage,
10
- FormattedNumber,
11
11
  injectIntl,
12
- } from "react-intl";
13
- import "./style.scss";
14
- import messages from "./messages";
15
- import { processErrors } from "./utils";
16
- import ErrorTypeRenderer from "./ErrorTypeRenderer";
17
- import { ANDROID, GENERIC, IOS } from "../../v2Containers/CreativesContainer/constants";
12
+ } from 'react-intl';
13
+ import './style.scss';
14
+ import messages from './messages';
15
+ import { processErrors } from './utils';
16
+ import ErrorTypeRenderer from './ErrorTypeRenderer';
17
+ import { ANDROID, GENERIC, IOS } from '../../v2Containers/CreativesContainer/constants';
18
+ import { ERROR_TAB_KEYS } from '../HtmlEditor/utils/validationConstants';
19
+ import { SEVERITY } from '../HtmlEditor/components/ValidationPanel/constants';
20
+ import { LIQUID_DOC_URL } from './constants';
18
21
 
22
+ const { CapLabelInline } = CapLabel;
23
+
24
+ /**
25
+ * Group messages into Errors (blocking) and Warnings (non-blocking).
26
+ * STANDARD_ERROR_MSG + LIQUID_ERROR_MSG = errors; optional STANDARD_WARNING_MSG = warnings.
27
+ */
28
+ const groupByErrorsAndWarnings = (standardErrors, liquidErrors, standardWarnings = []) => {
29
+ const errors = [];
30
+
31
+ (standardErrors || []).forEach((message, index) => {
32
+ errors.push({
33
+ message,
34
+ severity: SEVERITY.ERROR,
35
+ index,
36
+ source: 'standard',
37
+ });
38
+ });
39
+
40
+ (liquidErrors || []).forEach((message, index) => {
41
+ errors.push({
42
+ message,
43
+ severity: SEVERITY.ERROR,
44
+ index,
45
+ source: 'liquid',
46
+ });
47
+ });
48
+
49
+ const warnings = (standardWarnings || []).map((message, index) => ({
50
+ message,
51
+ severity: SEVERITY.WARNING,
52
+ index,
53
+ source: 'standard',
54
+ }));
55
+
56
+ return { errors, warnings };
57
+ };
58
+
59
+ /**
60
+ * Clean error message by removing line/char and rule parts
61
+ * @param {string} message - Original error message
62
+ * @param {RegExpMatchArray|null} lineMatch - Line number match result
63
+ * @param {RegExpMatchArray|null} charMatch - Character number match result
64
+ * @param {RegExpMatchArray|null} ruleMatch - Rule name match result
65
+ * @returns {string} Cleaned message without line/char/rule information
66
+ */
67
+ const cleanErrorMessage = (message, lineMatch, charMatch, ruleMatch) => {
68
+ let displayMessage = message;
69
+ if (lineMatch) {
70
+ displayMessage = displayMessage.replace(/Line\s+\d+,?\s*/gi, '');
71
+ }
72
+ if (charMatch) {
73
+ displayMessage = displayMessage.replace(/Char\s+\d+\.?\s*/gi, '');
74
+ }
75
+ if (ruleMatch) {
76
+ displayMessage = displayMessage.replace(/•\s*[a-z-]+$/i, '');
77
+ }
78
+ return displayMessage.trim();
79
+ };
80
+
81
+ /**
82
+ * Tab content component
83
+ */
84
+ const TabContent = ({ issues, onErrorClick, intl }) => {
85
+ if (!issues || issues.length === 0) {
86
+ return null;
87
+ }
88
+
89
+ const handleNavigateClick = (issue, e) => {
90
+ e.stopPropagation();
91
+ if (onErrorClick) {
92
+ onErrorClick(issue);
93
+ }
94
+ };
95
+
96
+ return (
97
+ <CapRow className="error-info-note__content">
98
+ {issues.map((issue, index) => {
99
+ const { severity, message } = issue;
100
+ const key = `${message}-${index}`;
101
+
102
+ // Parse line and char from message if present (format: "... Line X, Char Y.")
103
+ const lineMatch = message.match(/Line\s+(\d+)/i);
104
+ const charMatch = message.match(/Char\s+(\d+)/i);
105
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : null;
106
+ const char = charMatch ? parseInt(charMatch[1], 10) : null;
107
+
108
+ // Extract rule from message (format: "... • rule-name")
109
+ const ruleMatch = message.match(/•\s*([a-z-]+)$/i);
110
+ const rule = ruleMatch ? ruleMatch[1] : null;
111
+
112
+ // Clean message (remove line/char and rule parts for display)
113
+ const displayMessage = cleanErrorMessage(message, lineMatch, charMatch, ruleMatch);
114
+
115
+ return (
116
+ <CapRow
117
+ key={key}
118
+ className={`error-info-note__item error-info-note__item--${severity || 'error'}`}
119
+ >
120
+ <CapRow className="error-info-note__item-content">
121
+ <CapLabelInline type="label2" className="error-info-note__item-message">
122
+ {displayMessage}
123
+ </CapLabelInline>
124
+ {line && (
125
+ <CapLabelInline type="label2" className="error-info-note__item-location">
126
+ <FormattedMessage {...messages.line} />
127
+ {' '}
128
+ {line}
129
+ {char ? (
130
+ <>
131
+ ,
132
+ {' '}
133
+ <FormattedMessage {...messages.char} />
134
+ {' '}
135
+ {char}
136
+ </>
137
+ ) : ''}
138
+ .
139
+ </CapLabelInline>
140
+ )}
141
+ {rule && (
142
+ <CapLabelInline type="label2" className="error-info-note__item-rule">
143
+
144
+ {' '}
145
+ {rule}
146
+ </CapLabelInline>
147
+ )}
148
+ </CapRow>
149
+ {onErrorClick && (
150
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}>
151
+ <CapButton
152
+ type="flat"
153
+ className="error-info-note__item-navigate"
154
+ onClick={(e) => handleNavigateClick({ ...issue, line, column: char }, e)}
155
+ aria-label={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}
156
+ >
157
+ <CapIcon type="redirection" />
158
+ </CapButton>
159
+ </CapTooltip>
160
+ )}
161
+ </CapRow>
162
+ );
163
+ })}
164
+ </CapRow>
165
+ );
166
+ };
167
+
168
+ TabContent.propTypes = {
169
+ issues: PropTypes.array,
170
+ onErrorClick: PropTypes.func,
171
+ intl: PropTypes.object,
172
+ };
173
+
174
+ TabContent.defaultProps = {
175
+ issues: [],
176
+ onErrorClick: null,
177
+ intl: null,
178
+ };
179
+
180
+ /**
181
+ * ErrorInfoNote Component with Tabbed Interface
182
+ * @param {boolean} useLegacyDisplay - If true, uses simple list display instead of tabbed interface (for BEE Editor)
183
+ */
184
+ export const ErrorInfoNote = (props) => {
185
+ const {
186
+ errorMessages,
187
+ onErrorClick,
188
+ onClose,
189
+ isLiquidEnabled = true,
190
+ intl,
191
+ useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
192
+ } = props;
193
+
194
+ const [isDismissed, setIsDismissed] = useState(false);
195
+ const [activeKey, setActiveKey] = useState(null);
196
+
197
+ const {
198
+ LIQUID_ERROR_MSG: rawLiquidErrors = [],
199
+ STANDARD_ERROR_MSG: rawStandardErrors = [],
200
+ STANDARD_WARNING_MSG: rawStandardWarnings = [],
201
+ } = errorMessages || {};
202
+
203
+ // Detect if platform-specific (ANDROID/IOS) or GENERIC
204
+ const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
205
+ const isNotArray = !Array.isArray(rawLiquidErrors);
206
+ const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
207
+
208
+ // For platform-specific errors or when useLegacyDisplay is true, use the legacy renderer
209
+ if (hasPlatformKeys) {
210
+ // Process errors for both platforms - they use the same structure but different platform parameters
211
+ const createPlatformErrors = (platform) => ({
212
+ STANDARD: processErrors(rawStandardErrors, 'standard', platform, messages),
213
+ LIQUID: processErrors(rawLiquidErrors, 'liquid', platform, messages),
214
+ });
215
+
216
+ const androidErrors = createPlatformErrors(ANDROID);
217
+ const iosErrors = createPlatformErrors(IOS);
218
+ return (
219
+ <ErrorTypeRenderer
220
+ genericErrors={null}
221
+ androidErrors={androidErrors}
222
+ iosErrors={iosErrors}
223
+ ErrorSectionComponent={ErrorSection}
224
+ />
225
+ );
226
+ }
227
+
228
+ // For BEE Editor (useLegacyDisplay=true), use simple list display without tabs
229
+ if (useLegacyDisplay) {
230
+ const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
231
+ const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
232
+ const hasStandardErrors = standardErrors.length > 0;
233
+ const hasLiquidErrors = liquidErrors.length > 0 && isLiquidEnabled;
234
+
235
+ if (!hasStandardErrors && !hasLiquidErrors) {
236
+ return null;
237
+ }
238
+
239
+ return (
240
+ <CapRow className="error-container error-container--legacy">
241
+ {hasStandardErrors && (
242
+ <ErrorSection
243
+ title={<FormattedMessage {...messages.standardErrorHeader} />}
244
+ errors={standardErrors}
245
+ liquidError={false}
246
+ />
247
+ )}
248
+ {hasLiquidErrors && (
249
+ <ErrorSection
250
+ title={<FormattedMessage {...messages.dynamicErrorHeader} />}
251
+ errors={liquidErrors}
252
+ liquidError
253
+ />
254
+ )}
255
+ </CapRow>
256
+ );
257
+ }
258
+
259
+ // Group into Errors (blocking) and Warnings (non-blocking)
260
+ const { errors: errorIssues, warnings: warningIssues } = useMemo(() => groupByErrorsAndWarnings(
261
+ Array.isArray(rawStandardErrors) ? rawStandardErrors : [],
262
+ Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [],
263
+ Array.isArray(rawStandardWarnings) ? rawStandardWarnings : [],
264
+ ), [rawStandardErrors, rawLiquidErrors, rawStandardWarnings]);
265
+
266
+ const errorsCount = errorIssues.length;
267
+ const warningsCount = warningIssues.length;
268
+ const totalCount = errorsCount + warningsCount;
269
+ const hasLiquidErrors = Array.isArray(rawLiquidErrors) && rawLiquidErrors.length > 0;
270
+
271
+ // Default active tab: errors if any, else warnings
272
+ useEffect(() => {
273
+ if (errorsCount > 0) {
274
+ setActiveKey(ERROR_TAB_KEYS.ERRORS);
275
+ } else if (warningsCount > 0) {
276
+ setActiveKey(ERROR_TAB_KEYS.WARNINGS);
277
+ }
278
+ }, [errorsCount, warningsCount]);
279
+
280
+ // Handle close
281
+ const handleClose = () => {
282
+ setIsDismissed(true);
283
+ if (onClose) {
284
+ onClose();
285
+ }
286
+ };
287
+
288
+ // Handle liquid documentation click
289
+ const handleLiquidDocClick = () => {
290
+ window.open(
291
+ LIQUID_DOC_URL,
292
+ '_blank',
293
+ );
294
+ };
295
+
296
+ // Don't render if no issues or dismissed
297
+ if (totalCount === 0 || isDismissed) {
298
+ return null;
299
+ }
300
+
301
+ // Build tab panes: only Errors and Warnings
302
+ const tabPanes = [];
303
+
304
+ if (errorsCount > 0) {
305
+ tabPanes.push({
306
+ key: ERROR_TAB_KEYS.ERRORS,
307
+ tab: (
308
+ <CapLabelInline type="label2" className="error-info-note__tab-label error-info-note__tab-label--errors">
309
+ <FormattedMessage {...messages.errors} />
310
+ <CapLabelInline type="label2" className="error-info-note__tab-count">
311
+ (
312
+ {errorsCount}
313
+ )
314
+ </CapLabelInline>
315
+ </CapLabelInline>
316
+ ),
317
+ content: (
318
+ <TabContent
319
+ issues={errorIssues}
320
+ onErrorClick={onErrorClick}
321
+ intl={intl}
322
+ />
323
+ ),
324
+ });
325
+ }
326
+
327
+ if (warningsCount > 0) {
328
+ tabPanes.push({
329
+ key: ERROR_TAB_KEYS.WARNINGS,
330
+ tab: (
331
+ <CapLabelInline type="label2" className="error-info-note__tab-label error-info-note__tab-label--warnings">
332
+ <FormattedMessage {...messages.warnings} />
333
+ <CapLabelInline type="label2" className="error-info-note__tab-count">
334
+ (
335
+ {warningsCount}
336
+ )
337
+ </CapLabelInline>
338
+ </CapLabelInline>
339
+ ),
340
+ content: (
341
+ <TabContent
342
+ issues={warningIssues}
343
+ onErrorClick={onErrorClick}
344
+ intl={intl}
345
+ />
346
+ ),
347
+ });
348
+ }
349
+
350
+ return (
351
+ <CapRow className="error-container error-container--tabs">
352
+ <CapRow className="error-info-note__header">
353
+ <CapTab
354
+ activeKey={activeKey || (tabPanes[0]?.key)}
355
+ onChange={setActiveKey}
356
+ panes={tabPanes}
357
+ className="error-info-note__tabs"
358
+ />
359
+ <CapRow className="error-info-note__actions">
360
+ {hasLiquidErrors && isLiquidEnabled && (
361
+ <CapButton
362
+ type="flat"
363
+ className="error-info-note__liquid-doc"
364
+ onClick={handleLiquidDocClick}
365
+ >
366
+ <FormattedMessage {...messages.liquidDoc} />
367
+ <CapIcon size="s" type="launch" />
368
+ </CapButton>
369
+ )}
370
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}>
371
+ <CapButton
372
+ type="flat"
373
+ className="error-info-note__close"
374
+ onClick={handleClose}
375
+ aria-label={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}
376
+ >
377
+ <CapIcon type="close" />
378
+ </CapButton>
379
+ </CapTooltip>
380
+ </CapRow>
381
+ </CapRow>
382
+ </CapRow>
383
+ );
384
+ };
385
+
386
+ // Legacy ErrorSection component for platform-specific errors (backwards compatibility)
19
387
  const ErrorSection = ({
20
388
  title,
21
389
  errors,
@@ -26,7 +394,7 @@ const ErrorSection = ({
26
394
  {title && (
27
395
  <CapRow
28
396
  className={`error-header ${
29
- !liquidError ? "standard-error-header" : ""
397
+ !liquidError ? 'standard-error-header' : ''
30
398
  }`}
31
399
  >
32
400
  <>
@@ -38,8 +406,7 @@ const ErrorSection = ({
38
406
  <CapButton
39
407
  type="flat"
40
408
  className="add-btn"
41
- onClick={() => window.open("https://docs.capillarytech.com/docs/liquid-language-in-messages", "_blank")
42
- }
409
+ onClick={() => window.open(LIQUID_DOC_URL, '_blank')}
43
410
  >
44
411
  <FormattedMessage {...messages.liquidDoc} />
45
412
  <CapIcon size="s" type="launch" />
@@ -53,21 +420,15 @@ const ErrorSection = ({
53
420
  <CapLabel type="label2">{platformLabel}</CapLabel>
54
421
  </CapRow>
55
422
  )}
56
- <CapList
57
- className="error-list"
58
- size="small"
59
- dataSource={errors}
60
- renderItem={(error, index) => (
61
- <CapList.Item>
423
+ <CapRow className="error-list-legacy">
424
+ {(errors || []).map((error) => (
425
+ <CapRow key={`${error}`} className="error-list-legacy__item">
62
426
  <CapLabel type="label2" className="cap-list-v2-error-item">
63
- <CapLabel type="label2">
64
- <FormattedNumber value={index + 1} />.
65
- </CapLabel>
66
427
  <CapLabel type="label2">{error}</CapLabel>
67
428
  </CapLabel>
68
- </CapList.Item>
69
- )}
70
- />
429
+ </CapRow>
430
+ ))}
431
+ </CapRow>
71
432
  </>
72
433
  );
73
434
 
@@ -84,54 +445,16 @@ ErrorSection.defaultProps = {
84
445
  platformLabel: null,
85
446
  };
86
447
 
87
- export const ErrorInfoNote = (props) => {
88
- const { errorMessages } = props;
89
-
90
- const {
91
- LIQUID_ERROR_MSG: rawLiquidErrors = [],
92
- STANDARD_ERROR_MSG: rawStandardErrors = [],
93
- } = errorMessages || {};
94
-
95
- // Detect if platform-specific (ANDROID/IOS) or GENERIC
96
- const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
97
- const isNotArray = !Array.isArray(rawLiquidErrors);
98
- const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
99
-
100
- if (hasPlatformKeys) {
101
- // Platform-specific
102
- const androidErrors = {
103
- STANDARD: processErrors(rawStandardErrors, 'standard', ANDROID, messages),
104
- LIQUID: processErrors(rawLiquidErrors, 'liquid', ANDROID, messages),
105
- };
106
- const iosErrors = {
107
- STANDARD: processErrors(rawStandardErrors, 'standard', IOS, messages),
108
- LIQUID: processErrors(rawLiquidErrors, 'liquid', IOS, messages),
109
- };
110
- return (
111
- <ErrorTypeRenderer
112
- genericErrors={null}
113
- androidErrors={androidErrors}
114
- iosErrors={iosErrors}
115
- ErrorSectionComponent={ErrorSection}
116
- />
117
- );
118
- }
119
- // GENERIC (not platform-specific)
120
- const genericStandard = processErrors(rawStandardErrors, 'standard', null, messages);
121
- const genericLiquid = processErrors(rawLiquidErrors, 'liquid', null, messages);
122
- return (
123
- <ErrorTypeRenderer
124
- genericErrors={{ standard: genericStandard, liquid: genericLiquid }}
125
- ErrorSectionComponent={ErrorSection}
126
- />
127
- );
128
- };
129
-
130
448
  ErrorInfoNote.defaultProps = {
131
449
  errorMessages: {
132
450
  LIQUID_ERROR_MSG: [],
133
451
  STANDARD_ERROR_MSG: [],
134
452
  },
453
+ onErrorClick: null,
454
+ onClose: null,
455
+ isLiquidEnabled: true,
456
+ intl: null,
457
+ useLegacyDisplay: false, // Use simple list display for BEE Editor
135
458
  };
136
459
 
137
460
  ErrorInfoNote.propTypes = {
@@ -152,6 +475,13 @@ ErrorInfoNote.propTypes = {
152
475
  GENERIC: PropTypes.array,
153
476
  }),
154
477
  ]),
478
+ STANDARD_WARNING_MSG: PropTypes.array,
155
479
  }),
480
+ onErrorClick: PropTypes.func,
481
+ onClose: PropTypes.func,
482
+ isLiquidEnabled: PropTypes.bool,
483
+ intl: PropTypes.object,
484
+ useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
156
485
  };
486
+
157
487
  export default injectIntl(ErrorInfoNote);
@@ -6,6 +6,24 @@
6
6
  import { defineMessages } from "react-intl";
7
7
  const scope = "creatives.componentsV2.ErrorInfoNote";
8
8
  export default defineMessages({
9
+ // Tab labels: Errors = blocking, Warnings = non-blocking
10
+ errors: {
11
+ id: `${scope}.errors`,
12
+ defaultMessage: "Errors",
13
+ },
14
+ warnings: {
15
+ id: `${scope}.warnings`,
16
+ defaultMessage: "Warnings",
17
+ },
18
+ navigateToError: {
19
+ id: `${scope}.navigateToError`,
20
+ defaultMessage: "Go to error location",
21
+ },
22
+ closePanel: {
23
+ id: `${scope}.closePanel`,
24
+ defaultMessage: "Close validation panel",
25
+ },
26
+ // Legacy messages (kept for backwards compatibility)
9
27
  dynamicErrorHeader: {
10
28
  id: `${scope}.dynamicErrorHeader`,
11
29
  defaultMessage:
@@ -24,31 +42,39 @@ export default defineMessages({
24
42
  ariaFooter: {
25
43
  id: `${scope}.ariaFooter`,
26
44
  defaultMessage:
27
- "Aira can make mistakes. Please verify the suggestions before applying them"
45
+ "Aira can make mistakes. Please verify the suggestions before applying them",
28
46
  },
29
47
  liquidDocLink: {
30
48
  id: `${scope}.liquidDocLink`,
31
49
  defaultMessage:
32
- "https://docs.capillarytech.com/docs/liquid-language-in-messages"
50
+ "https://docs.capillarytech.com/docs/add-dynamic-content-using-liquid",
33
51
  },
34
52
  androidDynamicErrorHeader: {
35
53
  id: `${scope}.androidDynamicErrorHeader`,
36
54
  defaultMessage:
37
- "Errors found in Android Dynamic Tags"
55
+ "Errors found in Android Dynamic Tags",
38
56
  },
39
57
  iosDynamicErrorHeader: {
40
58
  id: `${scope}.iosDynamicErrorHeader`,
41
59
  defaultMessage:
42
- "Errors found in iOS Dynamic Tags"
60
+ "Errors found in iOS Dynamic Tags",
43
61
  },
44
62
  androidStandardErrorHeader: {
45
63
  id: `${scope}.androidStandardErrorHeader`,
46
64
  defaultMessage:
47
- "Errors found in Android Standard Tags"
65
+ "Errors found in Android Standard Tags",
48
66
  },
49
67
  iosStandardErrorHeader: {
50
68
  id: `${scope}.iosStandardErrorHeader`,
51
69
  defaultMessage:
52
- "Errors found in iOS Standard Tags"
70
+ "Errors found in iOS Standard Tags",
71
+ },
72
+ line: {
73
+ id: `${scope}.line`,
74
+ defaultMessage: "Line",
75
+ },
76
+ char: {
77
+ id: `${scope}.char`,
78
+ defaultMessage: "Char",
53
79
  },
54
80
  });