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

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 +217 -90
  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 +15 -23
  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 +1 -0
  38. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +1 -1
  39. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +1 -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 +87 -62
  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/EmailWrapperView.test.js +26 -1
  97. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +111 -77
  98. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  99. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  100. package/v2Containers/InApp/actions.js +7 -0
  101. package/v2Containers/InApp/constants.js +20 -4
  102. package/v2Containers/InApp/index.js +800 -357
  103. package/v2Containers/InApp/index.scss +4 -3
  104. package/v2Containers/InApp/messages.js +7 -3
  105. package/v2Containers/InApp/reducer.js +21 -3
  106. package/v2Containers/InApp/sagas.js +29 -9
  107. package/v2Containers/InApp/selectors.js +25 -5
  108. package/v2Containers/InApp/tests/index.test.js +154 -50
  109. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  110. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  111. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  112. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +162 -0
  113. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  114. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +9 -0
  115. package/v2Containers/InAppWrapper/constants.js +16 -0
  116. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  117. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  118. package/v2Containers/InAppWrapper/index.js +148 -0
  119. package/v2Containers/InAppWrapper/messages.js +49 -0
  120. package/v2Containers/InappAdvance/index.js +1099 -0
  121. package/v2Containers/InappAdvance/index.scss +10 -0
  122. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  123. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -3
  124. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -2
  125. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -25
  126. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -18
  127. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -46
  128. package/v2Containers/SmsTrai/Create/tests/__snapshots__/index.test.js.snap +0 -4
  129. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -8
  130. package/v2Containers/TagList/index.js +67 -1
  131. package/v2Containers/Templates/ChannelTypeIllustration.js +1 -13
  132. package/v2Containers/Templates/_templates.scss +56 -200
  133. package/v2Containers/Templates/actions.js +1 -2
  134. package/v2Containers/Templates/constants.js +0 -1
  135. package/v2Containers/Templates/index.js +124 -277
  136. package/v2Containers/Templates/messages.js +4 -24
  137. package/v2Containers/Templates/reducer.js +0 -2
  138. package/v2Containers/Templates/tests/index.test.js +0 -10
  139. package/v2Containers/TemplatesV2/index.js +2 -3
  140. package/v2Containers/TemplatesV2/messages.js +0 -4
  141. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +35 -132
  142. package/v2Components/CapImageUrlUpload/constants.js +0 -19
  143. package/v2Components/CapImageUrlUpload/index.js +0 -455
  144. package/v2Components/CapImageUrlUpload/index.scss +0 -35
  145. package/v2Components/CapImageUrlUpload/messages.js +0 -47
  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
@@ -1,21 +1,402 @@
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
18
 
19
+ // Label issue patterns - syntax errors related to tags
20
+ const LABEL_ISSUE_PATTERNS = [
21
+ 'tag must be paired',
22
+ 'open tag match failed',
23
+ 'closed tag match failed',
24
+ 'unclosed',
25
+ 'missing required',
26
+ 'tag-pair',
27
+ 'attr-value-not-empty',
28
+ 'attr-no-duplication',
29
+ 'tag-self-close',
30
+ 'spec-char-escape',
31
+ '</html>',
32
+ '<html>',
33
+ '</div>',
34
+ '<div>',
35
+ '{{',
36
+ '}}',
37
+ ];
38
+
39
+ /**
40
+ * Categorize error messages into HTML, Label, and Liquid categories
41
+ */
42
+ const categorizeErrorMessages = (standardErrors, liquidErrors) => {
43
+ const htmlIssues = [];
44
+ const labelIssues = [];
45
+ const liquidIssues = [];
46
+
47
+ // Process standard errors
48
+ (standardErrors || []).forEach((error, index) => {
49
+ const errorLower = (error || '').toLowerCase();
50
+
51
+ // Check if it's a Label (tag syntax) issue
52
+ const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
53
+ (pattern) => errorLower.includes(pattern.toLowerCase()),
54
+ );
55
+
56
+ if (isLabelIssue) {
57
+ labelIssues.push({
58
+ message: error,
59
+ severity: 'error',
60
+ index,
61
+ source: 'label',
62
+ });
63
+ } else {
64
+ htmlIssues.push({
65
+ message: error,
66
+ severity: 'error',
67
+ index,
68
+ source: 'html',
69
+ });
70
+ }
71
+ });
72
+
73
+ // Process liquid errors
74
+ (liquidErrors || []).forEach((error, index) => {
75
+ liquidIssues.push({
76
+ message: error,
77
+ severity: 'error',
78
+ index,
79
+ source: 'liquid',
80
+ });
81
+ });
82
+
83
+ return { htmlIssues, labelIssues, liquidIssues };
84
+ };
85
+
86
+ /**
87
+ * Get icon based on severity
88
+ */
89
+ const getSeverityIcon = (severity) => {
90
+ if (severity === 'warning') {
91
+ return <CapIcon type="alert-warning" className="error-info-note__icon error-info-note__icon--warning" />;
92
+ }
93
+ return <CapIcon type="warning-circle" className="error-info-note__icon error-info-note__icon--error" />;
94
+ };
95
+
96
+ /**
97
+ * Tab content component
98
+ */
99
+ const TabContent = ({ issues, onErrorClick, intl }) => {
100
+ if (!issues || issues.length === 0) {
101
+ return null;
102
+ }
103
+
104
+ const handleNavigateClick = (issue, e) => {
105
+ e.stopPropagation();
106
+ if (onErrorClick) {
107
+ onErrorClick(issue);
108
+ }
109
+ };
110
+
111
+ return (
112
+ <div className="error-info-note__content">
113
+ {issues.map((issue, index) => {
114
+ const { severity, message } = issue;
115
+ const key = `${message}-${index}`;
116
+
117
+ // Parse line and char from message if present (format: "... Line X, Char Y.")
118
+ const lineMatch = message.match(/Line\s+(\d+)/i);
119
+ const charMatch = message.match(/Char\s+(\d+)/i);
120
+ const line = lineMatch ? parseInt(lineMatch[1], 10) : null;
121
+ const char = charMatch ? parseInt(charMatch[1], 10) : null;
122
+
123
+ // Extract rule from message (format: "... • rule-name")
124
+ const ruleMatch = message.match(/•\s*([a-z-]+)$/i);
125
+ const rule = ruleMatch ? ruleMatch[1] : null;
126
+
127
+ // Clean message (remove line/char and rule parts for display)
128
+ let displayMessage = message;
129
+ if (lineMatch) {
130
+ displayMessage = displayMessage.replace(/Line\s+\d+,?\s*/gi, '');
131
+ }
132
+ if (charMatch) {
133
+ displayMessage = displayMessage.replace(/Char\s+\d+\.?\s*/gi, '');
134
+ }
135
+ if (ruleMatch) {
136
+ displayMessage = displayMessage.replace(/•\s*[a-z-]+$/i, '');
137
+ }
138
+ displayMessage = displayMessage.trim();
139
+
140
+ return (
141
+ <div
142
+ key={key}
143
+ className={`error-info-note__item error-info-note__item--${severity || 'error'}`}
144
+ >
145
+ <div className="error-info-note__item-icon">
146
+ {getSeverityIcon(severity)}
147
+ </div>
148
+ <div className="error-info-note__item-content">
149
+ <span className="error-info-note__item-message">
150
+ {displayMessage}
151
+ </span>
152
+ {line && (
153
+ <span className="error-info-note__item-location">
154
+ Line
155
+ {' '}
156
+ {line}
157
+ {char ? `, Char ${char}` : ''}
158
+ .
159
+ </span>
160
+ )}
161
+ {rule && (
162
+ <span className="error-info-note__item-rule">
163
+
164
+ {' '}
165
+ {rule}
166
+ </span>
167
+ )}
168
+ </div>
169
+ {onErrorClick && (
170
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage({ id: 'errorInfoNote.navigateToError', defaultMessage: 'Go to error location' }) : 'Go to error location'}>
171
+ <button
172
+ type="button"
173
+ className="error-info-note__item-navigate"
174
+ onClick={(e) => handleNavigateClick({ ...issue, line, column: char }, e)}
175
+ aria-label="Go to error location"
176
+ >
177
+ <CapIcon type="redirection" />
178
+ </button>
179
+ </CapTooltip>
180
+ )}
181
+ </div>
182
+ );
183
+ })}
184
+ </div>
185
+ );
186
+ };
187
+
188
+ TabContent.propTypes = {
189
+ issues: PropTypes.array,
190
+ onErrorClick: PropTypes.func,
191
+ intl: PropTypes.object,
192
+ };
193
+
194
+ TabContent.defaultProps = {
195
+ issues: [],
196
+ onErrorClick: null,
197
+ intl: null,
198
+ };
199
+
200
+ /**
201
+ * ErrorInfoNote Component with Tabbed Interface
202
+ */
203
+ export const ErrorInfoNote = (props) => {
204
+ const {
205
+ errorMessages,
206
+ onErrorClick,
207
+ onClose,
208
+ isLiquidEnabled = true,
209
+ intl,
210
+ } = props;
211
+
212
+ const [isDismissed, setIsDismissed] = useState(false);
213
+ const [activeKey, setActiveKey] = useState(null);
214
+
215
+ const {
216
+ LIQUID_ERROR_MSG: rawLiquidErrors = [],
217
+ STANDARD_ERROR_MSG: rawStandardErrors = [],
218
+ } = errorMessages || {};
219
+
220
+ // Detect if platform-specific (ANDROID/IOS) or GENERIC
221
+ const isObject = typeof rawStandardErrors === 'object' && rawStandardErrors !== null;
222
+ const isNotArray = !Array.isArray(rawLiquidErrors);
223
+ const hasPlatformKeys = isObject && isNotArray && [ANDROID, IOS, GENERIC].some((key) => key in rawLiquidErrors);
224
+
225
+ // For platform-specific errors, use the legacy renderer
226
+ if (hasPlatformKeys) {
227
+ const androidErrors = {
228
+ STANDARD: processErrors(rawStandardErrors, 'standard', ANDROID, messages),
229
+ LIQUID: processErrors(rawLiquidErrors, 'liquid', ANDROID, messages),
230
+ };
231
+ const iosErrors = {
232
+ STANDARD: processErrors(rawStandardErrors, 'standard', IOS, messages),
233
+ LIQUID: processErrors(rawLiquidErrors, 'liquid', IOS, messages),
234
+ };
235
+ return (
236
+ <ErrorTypeRenderer
237
+ genericErrors={null}
238
+ androidErrors={androidErrors}
239
+ iosErrors={iosErrors}
240
+ ErrorSectionComponent={ErrorSection}
241
+ />
242
+ );
243
+ }
244
+
245
+ // Categorize errors for tabbed interface
246
+ const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => categorizeErrorMessages(
247
+ Array.isArray(rawStandardErrors) ? rawStandardErrors : [],
248
+ Array.isArray(rawLiquidErrors) ? rawLiquidErrors : [],
249
+ ), [rawStandardErrors, rawLiquidErrors]);
250
+
251
+ // Calculate counts
252
+ const htmlCount = htmlIssues.length;
253
+ const labelCount = labelIssues.length;
254
+ const liquidCount = liquidIssues.length;
255
+ const totalCount = htmlCount + labelCount + (isLiquidEnabled ? liquidCount : 0);
256
+
257
+ // Set default active key
258
+ useMemo(() => {
259
+ if (!activeKey) {
260
+ if (htmlCount > 0) {
261
+ setActiveKey('html');
262
+ } else if (labelCount > 0) {
263
+ setActiveKey('label');
264
+ } else if (liquidCount > 0 && isLiquidEnabled) {
265
+ setActiveKey('liquid');
266
+ }
267
+ }
268
+ }, [htmlCount, labelCount, liquidCount, isLiquidEnabled, activeKey]);
269
+
270
+ // Handle close
271
+ const handleClose = () => {
272
+ setIsDismissed(true);
273
+ if (onClose) {
274
+ onClose();
275
+ }
276
+ };
277
+
278
+ // Handle liquid documentation click
279
+ const handleLiquidDocClick = () => {
280
+ window.open(
281
+ 'https://docs.capillarytech.com/docs/liquid-language-in-messages',
282
+ '_blank',
283
+ );
284
+ };
285
+
286
+ // Don't render if no issues or dismissed
287
+ if (totalCount === 0 || isDismissed) {
288
+ return null;
289
+ }
290
+
291
+ // Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
292
+ const tabPanes = [];
293
+
294
+ if (htmlCount > 0) {
295
+ tabPanes.push({
296
+ key: 'html',
297
+ tab: (
298
+ <span className="error-info-note__tab-label">
299
+ <FormattedMessage {...messages.htmlIssues} />
300
+ <span className="error-info-note__tab-count">
301
+ (
302
+ {htmlCount}
303
+ )
304
+ </span>
305
+ </span>
306
+ ),
307
+ content: (
308
+ <TabContent
309
+ issues={htmlIssues}
310
+ onErrorClick={onErrorClick}
311
+ intl={intl}
312
+ />
313
+ ),
314
+ });
315
+ }
316
+
317
+ if (labelCount > 0) {
318
+ tabPanes.push({
319
+ key: 'label',
320
+ tab: (
321
+ <span className="error-info-note__tab-label">
322
+ <FormattedMessage {...messages.labelIssues} />
323
+ <span className="error-info-note__tab-count">
324
+ (
325
+ {labelCount}
326
+ )
327
+ </span>
328
+ </span>
329
+ ),
330
+ content: (
331
+ <TabContent
332
+ issues={labelIssues}
333
+ onErrorClick={onErrorClick}
334
+ intl={intl}
335
+ />
336
+ ),
337
+ });
338
+ }
339
+
340
+ if (isLiquidEnabled && liquidCount > 0) {
341
+ tabPanes.push({
342
+ key: 'liquid',
343
+ tab: (
344
+ <span className="error-info-note__tab-label">
345
+ <FormattedMessage {...messages.liquidIssues} />
346
+ <span className="error-info-note__tab-count">
347
+ (
348
+ {liquidCount}
349
+ )
350
+ </span>
351
+ </span>
352
+ ),
353
+ content: (
354
+ <TabContent
355
+ issues={liquidIssues}
356
+ onErrorClick={onErrorClick}
357
+ intl={intl}
358
+ />
359
+ ),
360
+ });
361
+ }
362
+
363
+ return (
364
+ <div className="error-container error-container--tabs">
365
+ <CapRow className="error-info-note__header">
366
+ <CapTab
367
+ activeKey={activeKey || (tabPanes[0]?.key)}
368
+ onChange={setActiveKey}
369
+ panes={tabPanes}
370
+ className="error-info-note__tabs"
371
+ />
372
+ <CapRow className="error-info-note__actions">
373
+ {activeKey === 'liquid' && isLiquidEnabled && (
374
+ <CapButton
375
+ type="flat"
376
+ className="error-info-note__liquid-doc"
377
+ onClick={handleLiquidDocClick}
378
+ >
379
+ <FormattedMessage {...messages.liquidDoc} />
380
+ <CapIcon size="s" type="launch" />
381
+ </CapButton>
382
+ )}
383
+ <CapTooltip title={intl?.formatMessage ? intl.formatMessage({ id: 'errorInfoNote.closePanel', defaultMessage: 'Close validation panel' }) : 'Close validation panel'}>
384
+ <button
385
+ type="button"
386
+ className="error-info-note__close"
387
+ onClick={handleClose}
388
+ aria-label="Close validation panel"
389
+ >
390
+ <CapIcon type="close" />
391
+ </button>
392
+ </CapTooltip>
393
+ </CapRow>
394
+ </CapRow>
395
+ </div>
396
+ );
397
+ };
398
+
399
+ // Legacy ErrorSection component for platform-specific errors (backwards compatibility)
19
400
  const ErrorSection = ({
20
401
  title,
21
402
  errors,
@@ -26,7 +407,7 @@ const ErrorSection = ({
26
407
  {title && (
27
408
  <CapRow
28
409
  className={`error-header ${
29
- !liquidError ? "standard-error-header" : ""
410
+ !liquidError ? 'standard-error-header' : ''
30
411
  }`}
31
412
  >
32
413
  <>
@@ -38,8 +419,7 @@ const ErrorSection = ({
38
419
  <CapButton
39
420
  type="flat"
40
421
  className="add-btn"
41
- onClick={() => window.open("https://docs.capillarytech.com/docs/liquid-language-in-messages", "_blank")
42
- }
422
+ onClick={() => window.open('https://docs.capillarytech.com/docs/liquid-language-in-messages', '_blank')}
43
423
  >
44
424
  <FormattedMessage {...messages.liquidDoc} />
45
425
  <CapIcon size="s" type="launch" />
@@ -53,21 +433,15 @@ const ErrorSection = ({
53
433
  <CapLabel type="label2">{platformLabel}</CapLabel>
54
434
  </CapRow>
55
435
  )}
56
- <CapList
57
- className="error-list"
58
- size="small"
59
- dataSource={errors}
60
- renderItem={(error, index) => (
61
- <CapList.Item>
436
+ <div className="error-list-legacy">
437
+ {(errors || []).map((error) => (
438
+ <div key={`${error}`} className="error-list-legacy__item">
62
439
  <CapLabel type="label2" className="cap-list-v2-error-item">
63
- <CapLabel type="label2">
64
- <FormattedNumber value={index + 1} />.
65
- </CapLabel>
66
440
  <CapLabel type="label2">{error}</CapLabel>
67
441
  </CapLabel>
68
- </CapList.Item>
69
- )}
70
- />
442
+ </div>
443
+ ))}
444
+ </div>
71
445
  </>
72
446
  );
73
447
 
@@ -84,54 +458,15 @@ ErrorSection.defaultProps = {
84
458
  platformLabel: null,
85
459
  };
86
460
 
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
461
  ErrorInfoNote.defaultProps = {
131
462
  errorMessages: {
132
463
  LIQUID_ERROR_MSG: [],
133
464
  STANDARD_ERROR_MSG: [],
134
465
  },
466
+ onErrorClick: null,
467
+ onClose: null,
468
+ isLiquidEnabled: true,
469
+ intl: null,
135
470
  };
136
471
 
137
472
  ErrorInfoNote.propTypes = {
@@ -153,5 +488,10 @@ ErrorInfoNote.propTypes = {
153
488
  }),
154
489
  ]),
155
490
  }),
491
+ onErrorClick: PropTypes.func,
492
+ onClose: PropTypes.func,
493
+ isLiquidEnabled: PropTypes.bool,
494
+ intl: PropTypes.object,
156
495
  };
496
+
157
497
  export default injectIntl(ErrorInfoNote);
@@ -6,6 +6,28 @@
6
6
  import { defineMessages } from "react-intl";
7
7
  const scope = "creatives.componentsV2.ErrorInfoNote";
8
8
  export default defineMessages({
9
+ // Tab labels for new tabbed interface
10
+ htmlIssues: {
11
+ id: `${scope}.htmlIssues`,
12
+ defaultMessage: "HTML issues",
13
+ },
14
+ labelIssues: {
15
+ id: `${scope}.labelIssues`,
16
+ defaultMessage: "Label issues",
17
+ },
18
+ liquidIssues: {
19
+ id: `${scope}.liquidIssues`,
20
+ defaultMessage: "Liquid issues",
21
+ },
22
+ navigateToError: {
23
+ id: `${scope}.navigateToError`,
24
+ defaultMessage: "Go to error location",
25
+ },
26
+ closePanel: {
27
+ id: `${scope}.closePanel`,
28
+ defaultMessage: "Close validation panel",
29
+ },
30
+ // Legacy messages (kept for backwards compatibility)
9
31
  dynamicErrorHeader: {
10
32
  id: `${scope}.dynamicErrorHeader`,
11
33
  defaultMessage: