@capillarytech/creatives-library 8.0.254 → 8.0.255-alpha.0

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 (143) 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/utils/common.js +5 -0
  9. package/utils/commonUtils.js +28 -5
  10. package/utils/tests/commonUtil.test.js +224 -0
  11. package/utils/transformTemplateConfig.js +0 -10
  12. package/v2Components/CapDeviceContent/index.js +61 -56
  13. package/v2Components/CapTagList/index.js +6 -1
  14. package/v2Components/CapTagListWithInput/index.js +5 -1
  15. package/v2Components/CapTagListWithInput/messages.js +1 -1
  16. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  17. package/v2Components/ErrorInfoNote/constants.js +1 -0
  18. package/v2Components/ErrorInfoNote/index.js +457 -72
  19. package/v2Components/ErrorInfoNote/messages.js +36 -6
  20. package/v2Components/ErrorInfoNote/style.scss +282 -6
  21. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  22. package/v2Components/HtmlEditor/HTMLEditor.js +547 -94
  23. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +874 -0
  24. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1358 -133
  25. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  26. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  27. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  28. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +22 -101
  29. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +149 -140
  30. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  31. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  32. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -0
  33. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  34. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  35. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  36. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  37. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  40. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +3 -6
  41. package/v2Components/HtmlEditor/components/PreviewPane/index.js +24 -34
  42. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  43. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +49 -31
  44. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +50 -34
  45. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  46. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +70 -41
  47. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +254 -0
  48. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +364 -0
  49. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  50. package/v2Components/HtmlEditor/constants.js +42 -20
  51. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  52. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.apiErrors.test.js +794 -0
  53. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  54. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  55. package/v2Components/HtmlEditor/hooks/useValidation.js +189 -53
  56. package/v2Components/HtmlEditor/index.js +1 -1
  57. package/v2Components/HtmlEditor/messages.js +95 -85
  58. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +94 -45
  59. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  60. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  61. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  62. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +134 -102
  63. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  64. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  65. package/v2Components/HtmlEditor/utils/validationConstants.js +40 -0
  66. package/v2Components/MobilePushPreviewV2/index.js +32 -7
  67. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  68. package/v2Components/TemplatePreview/index.js +47 -32
  69. package/v2Components/TemplatePreview/messages.js +4 -0
  70. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  71. package/v2Containers/BeeEditor/index.js +172 -90
  72. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  73. package/v2Containers/BeePopupEditor/constants.js +10 -0
  74. package/v2Containers/BeePopupEditor/index.js +194 -0
  75. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  76. package/v2Containers/CreativesContainer/SlideBoxContent.js +128 -51
  77. package/v2Containers/CreativesContainer/SlideBoxFooter.js +163 -13
  78. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  79. package/v2Containers/CreativesContainer/constants.js +1 -0
  80. package/v2Containers/CreativesContainer/index.js +239 -46
  81. package/v2Containers/CreativesContainer/messages.js +8 -0
  82. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  83. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  84. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +106 -0
  85. package/v2Containers/Email/actions.js +7 -0
  86. package/v2Containers/Email/constants.js +5 -1
  87. package/v2Containers/Email/index.js +234 -29
  88. package/v2Containers/Email/messages.js +32 -0
  89. package/v2Containers/Email/reducer.js +12 -1
  90. package/v2Containers/Email/sagas.js +61 -7
  91. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  92. package/v2Containers/Email/tests/reducer.test.js +46 -0
  93. package/v2Containers/Email/tests/sagas.test.js +320 -29
  94. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1285 -0
  95. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +207 -19
  96. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  97. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +1870 -0
  98. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  99. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  100. package/v2Containers/EmailWrapper/constants.js +2 -0
  101. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +629 -77
  102. package/v2Containers/EmailWrapper/index.js +103 -23
  103. package/v2Containers/EmailWrapper/messages.js +61 -1
  104. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +643 -0
  105. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +594 -77
  106. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  107. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  108. package/v2Containers/InApp/actions.js +7 -0
  109. package/v2Containers/InApp/constants.js +20 -4
  110. package/v2Containers/InApp/index.js +802 -359
  111. package/v2Containers/InApp/index.scss +4 -3
  112. package/v2Containers/InApp/messages.js +7 -3
  113. package/v2Containers/InApp/reducer.js +21 -3
  114. package/v2Containers/InApp/sagas.js +29 -9
  115. package/v2Containers/InApp/selectors.js +25 -5
  116. package/v2Containers/InApp/tests/index.test.js +154 -50
  117. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  118. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  119. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  120. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  121. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  122. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  123. package/v2Containers/InAppWrapper/constants.js +16 -0
  124. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  125. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  126. package/v2Containers/InAppWrapper/index.js +148 -0
  127. package/v2Containers/InAppWrapper/messages.js +49 -0
  128. package/v2Containers/InappAdvance/index.js +1099 -0
  129. package/v2Containers/InappAdvance/index.scss +10 -0
  130. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  131. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  132. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  133. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  134. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  135. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  136. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  137. package/v2Containers/TagList/index.js +62 -19
  138. package/v2Containers/Templates/_templates.scss +60 -1
  139. package/v2Containers/Templates/index.js +89 -4
  140. package/v2Containers/Templates/messages.js +4 -0
  141. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  142. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  143. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -48,13 +48,14 @@ export const CapTagListWithInput = (props) => {
48
48
  showTagList = true,
49
49
  showInput = true,
50
50
  inputProps = {},
51
+ popoverPlacement,
51
52
  } = props;
52
53
 
53
54
  const { formatMessage } = intl;
54
55
 
55
56
  return (
56
57
  <CapColumn style={containerStyle}>
57
- <CapRow style={{display: 'flex', flexDirection: 'row'}}>
58
+ <CapRow align="middle" type="flex">
58
59
  {showHeading && headingText && (
59
60
  <CapHeading type={headingType} style={headingStyle}>
60
61
  {headingText}
@@ -76,6 +77,7 @@ export const CapTagListWithInput = (props) => {
76
77
  selectedOfferDetails={selectedOfferDetails}
77
78
  eventContextTags={eventContextTags}
78
79
  style={tagListStyle}
80
+ popoverPlacement={popoverPlacement}
79
81
  />
80
82
  )}
81
83
  </CapRow>
@@ -136,6 +138,7 @@ CapTagListWithInput.propTypes = {
136
138
  showHeading: PropTypes.bool,
137
139
  showTagList: PropTypes.bool,
138
140
  showInput: PropTypes.bool,
141
+ popoverPlacement: PropTypes.string,
139
142
  };
140
143
 
141
144
  CapTagListWithInput.defaultProps = {
@@ -164,6 +167,7 @@ CapTagListWithInput.defaultProps = {
164
167
  showTagList: true,
165
168
  showInput: true,
166
169
  inputProps: {},
170
+ popoverPlacement: undefined,
167
171
  };
168
172
 
169
173
  export default injectIntl(CapTagListWithInput);
@@ -5,6 +5,6 @@ const prefix = 'creatives.componentsV2.CapTagListWithInput';
5
5
  export default defineMessages({
6
6
  addLabels: {
7
7
  id: `${prefix}.addLabels`,
8
- defaultMessage: 'Add labels',
8
+ defaultMessage: 'Add label',
9
9
  },
10
10
  });
@@ -4,6 +4,11 @@ import '@testing-library/jest-dom';
4
4
  import { render, screen, fireEvent } from '../../../utils/test-utils';
5
5
  import { CapWhatsappCTA } from '../index';
6
6
 
7
+ // Mock the missing reducer
8
+ jest.mock('@capillarytech/cap-ui-library/CapCollapsibleLeftNavigation/reducer', () => {
9
+ return (state = {}) => state;
10
+ }, { virtual: true });
11
+
7
12
  const updateHandler = jest.fn();
8
13
  const deleteHandler = jest.fn();
9
14
  const initializeComponent = (
@@ -0,0 +1 @@
1
+ export const LIQUID_DOC_URL = 'https://docs.capillarytech.com/docs/liquid-language-in-messages';
@@ -1,21 +1,445 @@
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 } 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 { LABEL_ISSUE_PATTERNS, 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
+ * Categorize error messages into HTML, Label, and Liquid categories
26
+ */
27
+ const categorizeErrorMessages = (standardErrors, liquidErrors) => {
28
+ const htmlIssues = [];
29
+ const labelIssues = [];
30
+ const liquidIssues = [];
31
+
32
+ // Process standard errors
33
+ (standardErrors || []).forEach((error, index) => {
34
+ const errorLower = (error || '').toLowerCase();
35
+
36
+ // Check if it's a Label (tag syntax) issue
37
+ const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
38
+ (pattern) => errorLower.includes(pattern.toLowerCase()),
39
+ );
40
+
41
+ if (isLabelIssue) {
42
+ labelIssues.push({
43
+ message: error,
44
+ severity: SEVERITY.ERROR,
45
+ index,
46
+ source: 'label',
47
+ });
48
+ } else {
49
+ htmlIssues.push({
50
+ message: error,
51
+ severity: SEVERITY.ERROR,
52
+ index,
53
+ source: 'html',
54
+ });
55
+ }
56
+ });
57
+
58
+ // Process liquid errors
59
+ (liquidErrors || []).forEach((error, index) => {
60
+ liquidIssues.push({
61
+ message: error,
62
+ severity: SEVERITY.ERROR,
63
+ index,
64
+ source: 'liquid',
65
+ });
66
+ });
67
+
68
+ return { htmlIssues, labelIssues, liquidIssues };
69
+ };
70
+
71
+ /**
72
+ * Clean error message by removing line/char and rule parts
73
+ * @param {string} message - Original error message
74
+ * @param {RegExpMatchArray|null} lineMatch - Line number match result
75
+ * @param {RegExpMatchArray|null} charMatch - Character number match result
76
+ * @param {RegExpMatchArray|null} ruleMatch - Rule name match result
77
+ * @returns {string} Cleaned message without line/char/rule information
78
+ */
79
+ const cleanErrorMessage = (message, lineMatch, charMatch, ruleMatch) => {
80
+ let displayMessage = message;
81
+ if (lineMatch) {
82
+ displayMessage = displayMessage.replace(/Line\s+\d+,?\s*/gi, '');
83
+ }
84
+ if (charMatch) {
85
+ displayMessage = displayMessage.replace(/Char\s+\d+\.?\s*/gi, '');
86
+ }
87
+ if (ruleMatch) {
88
+ displayMessage = displayMessage.replace(/•\s*[a-z-]+$/i, '');
89
+ }
90
+ return displayMessage.trim();
91
+ };
92
+
93
+ /**
94
+ * Get icon based on severity
95
+ */
96
+ const getSeverityIcon = (severity) => {
97
+ if (severity === SEVERITY.WARNING) {
98
+ return <CapIcon type="alert-warning" className="error-info-note__icon error-info-note__icon--warning" />;
99
+ }
100
+ return <CapIcon type="warning-circle" className="error-info-note__icon error-info-note__icon--error" />;
101
+ };
102
+
103
+ /**
104
+ * Tab content component
105
+ */
106
+ const TabContent = ({ issues, onErrorClick, intl }) => {
107
+ if (!issues || issues.length === 0) {
108
+ return null;
109
+ }
110
+
111
+ const handleNavigateClick = (issue, e) => {
112
+ e.stopPropagation();
113
+ if (onErrorClick) {
114
+ onErrorClick(issue);
115
+ }
116
+ };
117
+
118
+ return (
119
+ <CapRow className="error-info-note__content">
120
+ {issues.map((issue, index) => {
121
+ const { severity, message } = issue;
122
+ const key = `${message}-${index}`;
123
+
124
+ // Parse line and char from message if present (format: "... Line X, Char Y.")
125
+ const lineMatch = message.match(/Line\s+(\d+)/i);
126
+ const charMatch = message.match(/Char\s+(\d+)/i);
127
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : null;
128
+ const char = charMatch ? parseInt(charMatch[1], 10) : null;
129
+
130
+ // Extract rule from message (format: "... • rule-name")
131
+ const ruleMatch = message.match(/•\s*([a-z-]+)$/i);
132
+ const rule = ruleMatch ? ruleMatch[1] : null;
133
+
134
+ // Clean message (remove line/char and rule parts for display)
135
+ const displayMessage = cleanErrorMessage(message, lineMatch, charMatch, ruleMatch);
136
+
137
+ return (
138
+ <CapRow
139
+ key={key}
140
+ className={`error-info-note__item error-info-note__item--${severity || 'error'}`}
141
+ >
142
+ <CapRow className="error-info-note__item-icon">
143
+ {getSeverityIcon(severity)}
144
+ </CapRow>
145
+ <CapRow className="error-info-note__item-content">
146
+ <CapLabelInline type="label2" className="error-info-note__item-message">
147
+ {displayMessage}
148
+ </CapLabelInline>
149
+ {line && (
150
+ <CapLabelInline type="label2" className="error-info-note__item-location">
151
+ <FormattedMessage {...messages.line} />
152
+ {' '}
153
+ {line}
154
+ {char ? (
155
+ <>
156
+ ,
157
+ {' '}
158
+ <FormattedMessage {...messages.char} />
159
+ {' '}
160
+ {char}
161
+ </>
162
+ ) : ''}
163
+ .
164
+ </CapLabelInline>
165
+ )}
166
+ {rule && (
167
+ <CapLabelInline type="label2" className="error-info-note__item-rule">
168
+
169
+ {' '}
170
+ {rule}
171
+ </CapLabelInline>
172
+ )}
173
+ </CapRow>
174
+ {onErrorClick && (
175
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}>
176
+ <CapButton
177
+ type="flat"
178
+ className="error-info-note__item-navigate"
179
+ onClick={(e) => handleNavigateClick({ ...issue, line, column: char }, e)}
180
+ aria-label={intl?.formatMessage ? intl.formatMessage(messages.navigateToError) : 'Go to error location'}
181
+ >
182
+ <CapIcon type="redirection" />
183
+ </CapButton>
184
+ </CapTooltip>
185
+ )}
186
+ </CapRow>
187
+ );
188
+ })}
189
+ </CapRow>
190
+ );
191
+ };
192
+
193
+ TabContent.propTypes = {
194
+ issues: PropTypes.array,
195
+ onErrorClick: PropTypes.func,
196
+ intl: PropTypes.object,
197
+ };
198
+
199
+ TabContent.defaultProps = {
200
+ issues: [],
201
+ onErrorClick: null,
202
+ intl: null,
203
+ };
204
+
205
+ /**
206
+ * ErrorInfoNote Component with Tabbed Interface
207
+ * @param {boolean} useLegacyDisplay - If true, uses simple list display instead of tabbed interface (for BEE Editor)
208
+ */
209
+ export const ErrorInfoNote = (props) => {
210
+ const {
211
+ errorMessages,
212
+ onErrorClick,
213
+ onClose,
214
+ isLiquidEnabled = true,
215
+ intl,
216
+ useLegacyDisplay = false, // Use simple list display instead of tabs (for BEE Editor)
217
+ } = props;
218
+
219
+ const [isDismissed, setIsDismissed] = useState(false);
220
+ const [activeKey, setActiveKey] = useState(null);
221
+
222
+ const {
223
+ LIQUID_ERROR_MSG: rawLiquidErrors = [],
224
+ STANDARD_ERROR_MSG: rawStandardErrors = [],
225
+ } = errorMessages || {};
226
+
227
+ // Detect if platform-specific (ANDROID/IOS) or GENERIC
228
+ const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
229
+ const isNotArray = !Array.isArray(rawLiquidErrors);
230
+ const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
231
+
232
+ // For platform-specific errors or when useLegacyDisplay is true, use the legacy renderer
233
+ if (hasPlatformKeys) {
234
+ // Process errors for both platforms - they use the same structure but different platform parameters
235
+ const createPlatformErrors = (platform) => ({
236
+ STANDARD: processErrors(rawStandardErrors, 'standard', platform, messages),
237
+ LIQUID: processErrors(rawLiquidErrors, 'liquid', platform, messages),
238
+ });
239
+
240
+ const androidErrors = createPlatformErrors(ANDROID);
241
+ const iosErrors = createPlatformErrors(IOS);
242
+ return (
243
+ <ErrorTypeRenderer
244
+ genericErrors={null}
245
+ androidErrors={androidErrors}
246
+ iosErrors={iosErrors}
247
+ ErrorSectionComponent={ErrorSection}
248
+ />
249
+ );
250
+ }
251
+
252
+ // For BEE Editor (useLegacyDisplay=true), use simple list display without tabs
253
+ if (useLegacyDisplay) {
254
+ const standardErrors = Array.isArray(rawStandardErrors) ? rawStandardErrors : [];
255
+ const liquidErrors = Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [];
256
+ const hasStandardErrors = standardErrors.length > 0;
257
+ const hasLiquidErrors = liquidErrors.length > 0 && isLiquidEnabled;
258
+
259
+ if (!hasStandardErrors && !hasLiquidErrors) {
260
+ return null;
261
+ }
262
+
263
+ return (
264
+ <CapRow className="error-container error-container--legacy">
265
+ {hasStandardErrors && (
266
+ <ErrorSection
267
+ title={<FormattedMessage {...messages.standardErrorHeader} />}
268
+ errors={standardErrors}
269
+ liquidError={false}
270
+ />
271
+ )}
272
+ {hasLiquidErrors && (
273
+ <ErrorSection
274
+ title={<FormattedMessage {...messages.dynamicErrorHeader} />}
275
+ errors={liquidErrors}
276
+ liquidError
277
+ />
278
+ )}
279
+ </CapRow>
280
+ );
281
+ }
282
+
283
+ // Categorize errors for tabbed interface
284
+ const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => categorizeErrorMessages(
285
+ Array.isArray(rawStandardErrors) ? rawStandardErrors : [],
286
+ Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [],
287
+ ), [rawStandardErrors, rawLiquidErrors]);
288
+
289
+ // Calculate counts
290
+ const htmlCount = htmlIssues.length;
291
+ const labelCount = labelIssues.length;
292
+ const liquidCount = liquidIssues.length;
293
+ // Include liquid issues in total count even when liquid is disabled
294
+ // This ensures liquid errors are shown when liquid content is detected but feature is disabled
295
+ const totalCount = htmlCount + labelCount + liquidCount;
296
+
297
+ // Set default active key
298
+ useMemo(() => {
299
+ if (!activeKey) {
300
+ if (htmlCount > 0) {
301
+ setActiveKey(ERROR_TAB_KEYS.HTML);
302
+ } else if (labelCount > 0) {
303
+ setActiveKey(ERROR_TAB_KEYS.LABEL);
304
+ } else if (liquidCount > 0) {
305
+ // Show liquid tab even when liquid is disabled if liquid content is detected
306
+ setActiveKey(ERROR_TAB_KEYS.LIQUID);
307
+ }
308
+ }
309
+ }, [htmlCount, labelCount, liquidCount, activeKey]);
310
+
311
+ // Handle close
312
+ const handleClose = () => {
313
+ setIsDismissed(true);
314
+ if (onClose) {
315
+ onClose();
316
+ }
317
+ };
318
+
319
+ // Handle liquid documentation click
320
+ const handleLiquidDocClick = () => {
321
+ window.open(
322
+ LIQUID_DOC_URL,
323
+ '_blank',
324
+ );
325
+ };
326
+
327
+ // Don't render if no issues or dismissed
328
+ if (totalCount === 0 || isDismissed) {
329
+ return null;
330
+ }
331
+
332
+ // Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
333
+ const tabPanes = [];
334
+
335
+ if (htmlCount > 0) {
336
+ tabPanes.push({
337
+ key: ERROR_TAB_KEYS.HTML,
338
+ tab: (
339
+ <CapLabelInline type="label2" className="error-info-note__tab-label">
340
+ <FormattedMessage {...messages.htmlIssues} />
341
+ <CapLabelInline type="label2" className="error-info-note__tab-count">
342
+ (
343
+ {htmlCount}
344
+ )
345
+ </CapLabelInline>
346
+ </CapLabelInline>
347
+ ),
348
+ content: (
349
+ <TabContent
350
+ issues={htmlIssues}
351
+ onErrorClick={onErrorClick}
352
+ intl={intl}
353
+ />
354
+ ),
355
+ });
356
+ }
357
+
358
+ if (labelCount > 0) {
359
+ tabPanes.push({
360
+ key: ERROR_TAB_KEYS.LABEL,
361
+ tab: (
362
+ <CapLabelInline type="label2" className="error-info-note__tab-label">
363
+ <FormattedMessage {...messages.labelIssues} />
364
+ <CapLabelInline type="label2" className="error-info-note__tab-count">
365
+ (
366
+ {labelCount}
367
+ )
368
+ </CapLabelInline>
369
+ </CapLabelInline>
370
+ ),
371
+ content: (
372
+ <TabContent
373
+ issues={labelIssues}
374
+ onErrorClick={onErrorClick}
375
+ intl={intl}
376
+ />
377
+ ),
378
+ });
379
+ }
380
+
381
+ // Show liquid issues tab even when liquid is disabled if liquid content is detected
382
+ // This allows users to see errors when they add liquid content but liquid feature is not enabled
383
+ if (liquidCount > 0) {
384
+ tabPanes.push({
385
+ key: ERROR_TAB_KEYS.LIQUID,
386
+ tab: (
387
+ <CapLabelInline type="label2" className="error-info-note__tab-label">
388
+ <FormattedMessage {...messages.liquidIssues} />
389
+ <CapLabelInline type="label2" className="error-info-note__tab-count">
390
+ (
391
+ {liquidCount}
392
+ )
393
+ </CapLabelInline>
394
+ </CapLabelInline>
395
+ ),
396
+ content: (
397
+ <TabContent
398
+ issues={liquidIssues}
399
+ onErrorClick={onErrorClick}
400
+ intl={intl}
401
+ />
402
+ ),
403
+ });
404
+ }
405
+
406
+ return (
407
+ <CapRow className="error-container error-container--tabs">
408
+ <CapRow className="error-info-note__header">
409
+ <CapTab
410
+ activeKey={activeKey || (tabPanes[0]?.key)}
411
+ onChange={setActiveKey}
412
+ panes={tabPanes}
413
+ className="error-info-note__tabs"
414
+ />
415
+ <CapRow className="error-info-note__actions">
416
+ {activeKey === ERROR_TAB_KEYS.LIQUID && isLiquidEnabled && (
417
+ <CapButton
418
+ type="flat"
419
+ className="error-info-note__liquid-doc"
420
+ onClick={handleLiquidDocClick}
421
+ >
422
+ <FormattedMessage {...messages.liquidDoc} />
423
+ <CapIcon size="s" type="launch" />
424
+ </CapButton>
425
+ )}
426
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}>
427
+ <CapButton
428
+ type="flat"
429
+ className="error-info-note__close"
430
+ onClick={handleClose}
431
+ aria-label={intl?.formatMessage ? intl.formatMessage(messages.closePanel) : 'Close validation panel'}
432
+ >
433
+ <CapIcon type="close" />
434
+ </CapButton>
435
+ </CapTooltip>
436
+ </CapRow>
437
+ </CapRow>
438
+ </CapRow>
439
+ );
440
+ };
441
+
442
+ // Legacy ErrorSection component for platform-specific errors (backwards compatibility)
19
443
  const ErrorSection = ({
20
444
  title,
21
445
  errors,
@@ -26,7 +450,7 @@ const ErrorSection = ({
26
450
  {title && (
27
451
  <CapRow
28
452
  className={`error-header ${
29
- !liquidError ? "standard-error-header" : ""
453
+ !liquidError ? 'standard-error-header' : ''
30
454
  }`}
31
455
  >
32
456
  <>
@@ -38,8 +462,7 @@ const ErrorSection = ({
38
462
  <CapButton
39
463
  type="flat"
40
464
  className="add-btn"
41
- onClick={() => window.open("https://docs.capillarytech.com/docs/liquid-language-in-messages", "_blank")
42
- }
465
+ onClick={() => window.open('https://docs.capillarytech.com/docs/liquid-language-in-messages', '_blank')}
43
466
  >
44
467
  <FormattedMessage {...messages.liquidDoc} />
45
468
  <CapIcon size="s" type="launch" />
@@ -53,21 +476,15 @@ const ErrorSection = ({
53
476
  <CapLabel type="label2">{platformLabel}</CapLabel>
54
477
  </CapRow>
55
478
  )}
56
- <CapList
57
- className="error-list"
58
- size="small"
59
- dataSource={errors}
60
- renderItem={(error, index) => (
61
- <CapList.Item>
479
+ <CapRow className="error-list-legacy">
480
+ {(errors || []).map((error) => (
481
+ <CapRow key={`${error}`} className="error-list-legacy__item">
62
482
  <CapLabel type="label2" className="cap-list-v2-error-item">
63
- <CapLabel type="label2">
64
- <FormattedNumber value={index + 1} />.
65
- </CapLabel>
66
483
  <CapLabel type="label2">{error}</CapLabel>
67
484
  </CapLabel>
68
- </CapList.Item>
69
- )}
70
- />
485
+ </CapRow>
486
+ ))}
487
+ </CapRow>
71
488
  </>
72
489
  );
73
490
 
@@ -84,54 +501,16 @@ ErrorSection.defaultProps = {
84
501
  platformLabel: null,
85
502
  };
86
503
 
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
504
  ErrorInfoNote.defaultProps = {
131
505
  errorMessages: {
132
506
  LIQUID_ERROR_MSG: [],
133
507
  STANDARD_ERROR_MSG: [],
134
508
  },
509
+ onErrorClick: null,
510
+ onClose: null,
511
+ isLiquidEnabled: true,
512
+ intl: null,
513
+ useLegacyDisplay: false, // Use simple list display for BEE Editor
135
514
  };
136
515
 
137
516
  ErrorInfoNote.propTypes = {
@@ -153,5 +532,11 @@ ErrorInfoNote.propTypes = {
153
532
  }),
154
533
  ]),
155
534
  }),
535
+ onErrorClick: PropTypes.func,
536
+ onClose: PropTypes.func,
537
+ isLiquidEnabled: PropTypes.bool,
538
+ intl: PropTypes.object,
539
+ useLegacyDisplay: PropTypes.bool, // Use simple list display for BEE Editor
156
540
  };
541
+
157
542
  export default injectIntl(ErrorInfoNote);