@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
@@ -0,0 +1,254 @@
1
+ /**
2
+ * ValidationTabs Styles
3
+ */
4
+
5
+ @import '~@capillarytech/cap-ui-library/styles/_variables.scss';
6
+
7
+ .validation-tabs {
8
+ overflow-y: hidden;
9
+ width: 100%;
10
+ background-color: $CAP_COLOR_05; // Light pink background
11
+ border-radius: 0.25rem;
12
+ padding: 1rem 1rem;
13
+ box-sizing: border-box;
14
+
15
+ &__header {
16
+ display: flex;
17
+ align-items: flex-start;
18
+ justify-content: space-between;
19
+ width: 100%;
20
+ }
21
+
22
+ &__tabs {
23
+ flex: 1;
24
+
25
+ // Override CapTab styles for proper spacing
26
+ .cap-tab-v2 {
27
+ .ant-tabs-nav {
28
+ margin-bottom: 0;
29
+
30
+ &::before {
31
+ border-bottom: none;
32
+ }
33
+ }
34
+
35
+ .ant-tabs-tab {
36
+ padding: 0.5rem 0.75rem; // Add horizontal padding for spacing
37
+ margin-right: 0; // Remove margin, use padding instead
38
+ color: $CAP_G03;
39
+ font-size: 0.875rem;
40
+ font-weight: 500;
41
+
42
+ & + .ant-tabs-tab {
43
+ margin-left: 1.5rem; // Add space between tabs
44
+ }
45
+
46
+ &:hover {
47
+ color: $CAP_COLOR_05;
48
+ background: transparent;
49
+ }
50
+
51
+ &.ant-tabs-tab-active {
52
+ .ant-tabs-tab-btn {
53
+ color: $CAP_COLOR_05;
54
+ font-weight: 600;
55
+ }
56
+ }
57
+ }
58
+
59
+ .ant-tabs-ink-bar {
60
+ background-color: $CAP_COLOR_05;
61
+ height: 0.125rem;
62
+ }
63
+
64
+ .ant-tabs-content-holder {
65
+ padding-top: 0.25rem; // Reduced from 0.5rem
66
+ padding-bottom: 0; // No bottom padding
67
+ }
68
+ }
69
+ }
70
+
71
+ &__tab-label {
72
+ display: flex;
73
+ align-items: center;
74
+ gap: 0.25rem;
75
+ }
76
+
77
+ &__tab-count {
78
+ color: inherit;
79
+ }
80
+
81
+ &__actions {
82
+ display: flex;
83
+ align-items: center;
84
+ gap: 0.5rem;
85
+ flex-shrink: 0;
86
+ padding-top: 0.5rem;
87
+ }
88
+
89
+ &__close {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ width: 1.5rem;
94
+ height: 1.5rem;
95
+ padding: 0;
96
+ background: transparent;
97
+ border: none;
98
+ border-radius: 0.25rem;
99
+ cursor: pointer;
100
+ color: $CAP_G03;
101
+ transition: all 0.2s ease;
102
+
103
+ &:hover {
104
+ background-color: rgba($CAP_COLOR_05, 0.1);
105
+ color: $CAP_COLOR_05;
106
+ }
107
+
108
+ .cap-icon-v2 {
109
+ font-size: 0.875rem;
110
+ }
111
+ }
112
+
113
+ &__content {
114
+ max-height: 15rem; // Limit height for many errors
115
+ overflow-y: auto;
116
+ padding-right: 0.25rem;
117
+ padding-bottom: 0; // Remove bottom padding completely
118
+
119
+ // Custom scrollbar
120
+ &::-webkit-scrollbar {
121
+ width: 0.375rem;
122
+ }
123
+
124
+ &::-webkit-scrollbar-track {
125
+ background: transparent;
126
+ }
127
+
128
+ &::-webkit-scrollbar-thumb {
129
+ background-color: $CAP_G06;
130
+ border-radius: 0.1875rem;
131
+
132
+ &:hover {
133
+ background-color: $CAP_G04;
134
+ }
135
+ }
136
+ }
137
+
138
+ &__item {
139
+ display: flex;
140
+ align-items: flex-start;
141
+ gap: 0.5rem;
142
+ padding: 0.25rem 0; // Reduced from 0.375rem
143
+ border-bottom: 1px solid rgba($CAP_G06, 0.3);
144
+
145
+ &:last-child {
146
+ border-bottom: none;
147
+ padding-bottom: 0.25rem; // Minimal padding on last item
148
+ }
149
+
150
+ &--error {
151
+ .validation-tabs__icon--error {
152
+ color: $CAP_RED;
153
+ }
154
+ }
155
+
156
+ &--warning {
157
+ .validation-tabs__icon--warning {
158
+ color: $CAP_YELLOW;
159
+ }
160
+ }
161
+ }
162
+
163
+ &__item-icon {
164
+ flex-shrink: 0;
165
+ display: flex;
166
+ align-items: center;
167
+ padding-top: 0.125rem;
168
+ }
169
+
170
+ &__icon {
171
+ font-size: 0.875rem;
172
+
173
+ &--error {
174
+ color: $CAP_RED;
175
+ }
176
+
177
+ &--warning {
178
+ color: $CAP_YELLOW;
179
+ }
180
+ }
181
+
182
+ &__item-content {
183
+ flex: 1;
184
+ display: flex;
185
+ flex-wrap: wrap;
186
+ align-items: baseline;
187
+ gap: 0.25rem;
188
+ font-size: 0.75rem;
189
+ line-height: 2;
190
+ color: $CAP_G01;
191
+ }
192
+
193
+ &__item-message {
194
+ color: $CAP_G01;
195
+ }
196
+
197
+ &__item-location {
198
+ color: $CAP_G03;
199
+ white-space: nowrap;
200
+ }
201
+
202
+ &__item-rule {
203
+ color: $CAP_G04;
204
+ font-family: monospace;
205
+ font-size: 0.6875rem;
206
+ }
207
+
208
+ &__item-navigate {
209
+ flex-shrink: 0;
210
+ display: flex;
211
+ align-items: center;
212
+ justify-content: center;
213
+ width: 1.25rem;
214
+ height: 1.25rem;
215
+ padding: 0;
216
+ background: transparent;
217
+ border: none;
218
+ border-radius: 0.25rem;
219
+ cursor: pointer;
220
+ color: $CAP_G04;
221
+ transition: all 0.2s ease;
222
+
223
+ &:hover {
224
+ background-color: rgba($CAP_G01, 0.1);
225
+ color: $CAP_G01;
226
+ }
227
+
228
+ .anticon {
229
+ font-size: 0.75rem;
230
+ }
231
+ }
232
+ }
233
+
234
+ // Responsive adjustments
235
+ @media (max-width: 768px) {
236
+ .validation-tabs {
237
+ padding: 0.375rem 0.5rem;
238
+
239
+ &__tabs {
240
+ .ant-tabs-tab {
241
+ margin-right: 1rem;
242
+ font-size: 0.8125rem;
243
+ }
244
+ }
245
+
246
+ &__content {
247
+ max-height: 10rem;
248
+ }
249
+
250
+ &__item-content {
251
+ font-size: 0.6875rem;
252
+ }
253
+ }
254
+ }
@@ -0,0 +1,364 @@
1
+ /**
2
+ * ValidationTabs Component
3
+ *
4
+ * Displays validation errors in a tabbed interface with 3 categories:
5
+ * - HTML issues: General HTML/CSS validation errors and warnings
6
+ * - Label issues: Tag syntax errors (open/close tags, attributes, brackets)
7
+ * - Liquid issues: Liquid expression errors (shown when liquid content is detected, even if liquid feature is disabled)
8
+ */
9
+
10
+ import React, { useState, useMemo } from 'react';
11
+ import PropTypes from 'prop-types';
12
+ import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
13
+
14
+ // Cap UI Library
15
+ import CapRow from '@capillarytech/cap-ui-library/CapRow';
16
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
17
+ import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
18
+
19
+ // Messages
20
+ import messages from './messages';
21
+ import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
22
+ import { ISSUE_SOURCES, LABEL_ISSUE_PATTERNS } from '../../utils/validationConstants';
23
+
24
+ // Styles
25
+ import './_validationTabs.scss';
26
+ import {StyledCapTab} from '../../../../v2Containers/MobilePushNew/style';
27
+
28
+ /**
29
+ * Categorize issues into HTML, Label, and Liquid categories
30
+ */
31
+ const categorizeIssues = (allIssues) => {
32
+ const htmlIssues = [];
33
+ const labelIssues = [];
34
+ const liquidIssues = [];
35
+
36
+ allIssues.forEach((issue) => {
37
+ const { source, rule, message } = issue;
38
+ const messageLower = (message || '').toLowerCase();
39
+ const ruleLower = (rule || '').toLowerCase();
40
+
41
+ // Check if it's a Liquid issue - ONLY by source, not by message content
42
+ // This prevents false positives where HTML errors mention liquid syntax
43
+ if (source === ISSUE_SOURCES.LIQUID) {
44
+ liquidIssues.push(issue);
45
+ return;
46
+ }
47
+
48
+ // Check if it's a Label (tag syntax) issue
49
+ const isLabelIssue = LABEL_ISSUE_PATTERNS.some(
50
+ (pattern) => messageLower.includes(pattern.toLowerCase())
51
+ || ruleLower.includes(pattern.toLowerCase()),
52
+ );
53
+
54
+ if (isLabelIssue) {
55
+ labelIssues.push(issue);
56
+ return;
57
+ }
58
+
59
+ // Default to HTML issues
60
+ htmlIssues.push(issue);
61
+ });
62
+
63
+ return { htmlIssues, labelIssues, liquidIssues };
64
+ };
65
+
66
+ /**
67
+ * Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
68
+ */
69
+ const isBlockingError = (issue) => {
70
+ const { rule, source, severity } = issue || {};
71
+ // API errors are blocking
72
+ if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') {
73
+ return true;
74
+ }
75
+ // Client-side Liquid validation errors are blocking (genuine syntax errors)
76
+ if (source === 'liquid-validator' && severity === 'error') {
77
+ return true;
78
+ }
79
+ // Rule Group #1 errors are blocking
80
+ if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
81
+ return true;
82
+ }
83
+ return false;
84
+ };
85
+
86
+ /**
87
+ * Get icon based on whether issue is blocking error or warning
88
+ * Blocking errors use error-icon, warnings use alert-warning
89
+ */
90
+ const getSeverityIcon = (issue) => {
91
+ if (isBlockingError(issue)) {
92
+ return <CapIcon type="error-icon" className="validation-tabs__icon validation-tabs__icon--error" />;
93
+ }
94
+ // All other issues (warnings, non-blocking) use warning icon
95
+ return <CapIcon type="alert-warning" className="validation-tabs__icon validation-tabs__icon--warning" />;
96
+ };
97
+
98
+ /**
99
+ * ValidationTabContent - Renders the content for each tab
100
+ */
101
+ const ValidationTabContent = ({
102
+ issues,
103
+ onErrorClick,
104
+ }) => {
105
+ if (!issues || issues.length === 0) {
106
+ return null;
107
+ }
108
+
109
+ const handleNavigateClick = (issue, e) => {
110
+ e.stopPropagation();
111
+ if (onErrorClick) {
112
+ // Always call onErrorClick to acknowledge the error (enables buttons)
113
+ // If line number exists, navigate to it; otherwise just acknowledge and focus editor
114
+ onErrorClick({
115
+ line: issue.line || 1, // Default to line 1 if no line number (for API errors)
116
+ column: issue.column || 1,
117
+ message: issue.message,
118
+ severity: issue.severity,
119
+ });
120
+ }
121
+ };
122
+
123
+ return (
124
+ <div className="validation-tabs__content">
125
+ {issues.map((issue, index) => {
126
+ const {
127
+ message, line, column,
128
+ } = issue;
129
+ const key = `${message}-${line}-${column}-${index}`;
130
+ const isBlocking = isBlockingError(issue);
131
+ const displaySeverity = isBlocking ? 'error' : 'warning';
132
+
133
+ return (
134
+ <div
135
+ key={key}
136
+ className={`validation-tabs__item validation-tabs__item--${displaySeverity}`}
137
+ >
138
+ <div className="validation-tabs__item-icon">
139
+ {getSeverityIcon(issue)}
140
+ </div>
141
+ <div className="validation-tabs__item-content">
142
+ <span className="validation-tabs__item-message">
143
+ {message}
144
+ </span>
145
+ {line && (
146
+ <span className="validation-tabs__item-location">
147
+ <FormattedMessage
148
+ {...messages.lineChar}
149
+ values={{ line, char: column || 1 }}
150
+ />
151
+ </span>
152
+ )}
153
+ </div>
154
+ {/* Always show redirection icon for errors (even API errors without line numbers) */}
155
+ {/* Clicking it acknowledges the error and enables buttons */}
156
+ <CapTooltip title={line ? `Line ${line}, Char ${column || 1}` : 'Click to acknowledge error and focus editor'}>
157
+ <button
158
+ type="button"
159
+ className="validation-tabs__item-navigate"
160
+ onClick={(e) => handleNavigateClick(issue, e)}
161
+ aria-label={line ? `Line ${line}, Char ${column || 1}` : 'Acknowledge error'}
162
+ >
163
+ <CapIcon type="redirection" />
164
+ </button>
165
+ </CapTooltip>
166
+ </div>
167
+ );
168
+ })}
169
+ </div>
170
+ );
171
+ };
172
+
173
+ ValidationTabContent.propTypes = {
174
+ issues: PropTypes.array,
175
+ onErrorClick: PropTypes.func,
176
+ };
177
+
178
+ ValidationTabContent.defaultProps = {
179
+ issues: [],
180
+ onErrorClick: null,
181
+ };
182
+
183
+ /**
184
+ * ValidationTabs Component
185
+ */
186
+ const ValidationTabs = ({
187
+ intl,
188
+ validation,
189
+ onErrorClick,
190
+ onClose,
191
+ className,
192
+ }) => {
193
+ const [activeKey, setActiveKey] = useState(null);
194
+
195
+ // Categorize issues
196
+ const { htmlIssues, labelIssues, liquidIssues } = useMemo(() => {
197
+ if (!validation) {
198
+ return { htmlIssues: [], labelIssues: [], liquidIssues: [] };
199
+ }
200
+
201
+ // Get all issues from validation
202
+ const allIssues = validation.getAllIssues ? validation.getAllIssues() : [];
203
+ const categorized = categorizeIssues(allIssues);
204
+ return categorized;
205
+ }, [validation]);
206
+
207
+ // Calculate counts
208
+ const htmlCount = htmlIssues.length;
209
+ const labelCount = labelIssues.length;
210
+ const liquidCount = liquidIssues.length;
211
+ // Include liquid issues in total count even when liquid is disabled
212
+ // This ensures liquid errors are shown when liquid content is detected but feature is disabled
213
+ const totalCount = htmlCount + labelCount + liquidCount;
214
+
215
+ // Set default active key when issues change
216
+ useMemo(() => {
217
+ if (htmlCount > 0 && !activeKey) {
218
+ setActiveKey('html');
219
+ } else if (labelCount > 0 && !activeKey) {
220
+ setActiveKey('label');
221
+ } else if (liquidCount > 0 && !activeKey) {
222
+ // Show liquid tab even when liquid is disabled if liquid content is detected
223
+ setActiveKey('liquid');
224
+ }
225
+ }, [htmlCount, labelCount, liquidCount, activeKey]);
226
+
227
+ // Don't render if no issues
228
+ if (totalCount === 0) {
229
+ return null;
230
+ }
231
+
232
+ // Build tab panes (CapTab uses 'panes' with 'tab' and 'content' properties)
233
+ const tabPanes = [];
234
+
235
+ if (htmlCount > 0) {
236
+ tabPanes.push({
237
+ key: 'html',
238
+ tab: (
239
+ <CapTooltip title={`${intl.formatMessage(messages.htmlIssues)} (${htmlCount})`}>
240
+ <span className="validation-tabs__tab-label">
241
+ <FormattedMessage {...messages.htmlIssues} />
242
+ <span className="validation-tabs__tab-count">
243
+ (
244
+ {htmlCount}
245
+ )
246
+ </span>
247
+ </span>
248
+ </CapTooltip>
249
+ ),
250
+ content: (
251
+ <ValidationTabContent
252
+ issues={htmlIssues}
253
+ onErrorClick={onErrorClick}
254
+ intl={intl}
255
+ />
256
+ ),
257
+ });
258
+ }
259
+
260
+ if (labelCount > 0) {
261
+ tabPanes.push({
262
+ key: 'label',
263
+ tab: (
264
+ <CapTooltip title={`${intl.formatMessage(messages.labelIssues)} (${labelCount})`}>
265
+ <span className="validation-tabs__tab-label">
266
+ <FormattedMessage {...messages.labelIssues} />
267
+ <span className="validation-tabs__tab-count">
268
+ (
269
+ {labelCount}
270
+ )
271
+ </span>
272
+ </span>
273
+ </CapTooltip>
274
+ ),
275
+ content: (
276
+ <ValidationTabContent
277
+ issues={labelIssues}
278
+ onErrorClick={onErrorClick}
279
+ intl={intl}
280
+ />
281
+ ),
282
+ });
283
+ }
284
+
285
+ // Show liquid issues tab even when liquid is disabled if liquid content is detected
286
+ // This allows users to see errors when they add liquid content but liquid feature is not enabled
287
+ if (liquidCount > 0) {
288
+ tabPanes.push({
289
+ key: 'liquid',
290
+ tab: (
291
+ <CapTooltip title={`${intl.formatMessage(messages.liquidIssues)} (${liquidCount})`}>
292
+ <span className="validation-tabs__tab-label">
293
+ <FormattedMessage {...messages.liquidIssues} />
294
+ <span className="validation-tabs__tab-count">
295
+ (
296
+ {liquidCount}
297
+ )
298
+ </span>
299
+ </span>
300
+ </CapTooltip>
301
+ ),
302
+ content: (
303
+ <ValidationTabContent
304
+ issues={liquidIssues}
305
+ onErrorClick={onErrorClick}
306
+ intl={intl}
307
+ />
308
+ ),
309
+ });
310
+ }
311
+
312
+ // Handle close
313
+ const handleClose = () => {
314
+ if (onClose) {
315
+ onClose();
316
+ }
317
+ };
318
+
319
+ return (
320
+ <div className={`validation-tabs ${className || ''}`}>
321
+ <CapRow className="validation-tabs__header">
322
+ <StyledCapTab
323
+ className="validation-tabs__tabs"
324
+ activeKey={activeKey || (tabPanes[0]?.key)}
325
+ onChange={setActiveKey}
326
+ panes={tabPanes}
327
+ />
328
+ <CapRow className="validation-tabs__actions">
329
+ <CapTooltip title={intl.formatMessage(messages.closePanel)}>
330
+ <button
331
+ type="button"
332
+ className="validation-tabs__close"
333
+ onClick={handleClose}
334
+ aria-label={intl.formatMessage(messages.closePanel)}
335
+ >
336
+ <CapIcon type="close" />
337
+ </button>
338
+ </CapTooltip>
339
+ </CapRow>
340
+ </CapRow>
341
+ </div>
342
+ );
343
+ };
344
+
345
+ ValidationTabs.propTypes = {
346
+ intl: intlShape.isRequired,
347
+ validation: PropTypes.shape({
348
+ getAllIssues: PropTypes.func,
349
+ }),
350
+ onErrorClick: PropTypes.func,
351
+ onClose: PropTypes.func,
352
+ isLiquidEnabled: PropTypes.bool,
353
+ className: PropTypes.string,
354
+ };
355
+
356
+ ValidationTabs.defaultProps = {
357
+ validation: null,
358
+ onErrorClick: null,
359
+ onClose: null,
360
+ isLiquidEnabled: false,
361
+ className: '',
362
+ };
363
+
364
+ export default injectIntl(ValidationTabs);
@@ -0,0 +1,51 @@
1
+ /**
2
+ * ValidationTabs Messages
3
+ *
4
+ * Internationalization messages for the ValidationTabs component
5
+ */
6
+
7
+ import { defineMessages } from 'react-intl';
8
+
9
+ const scope = 'app.components.HtmlEditor.ValidationTabs';
10
+
11
+ export default defineMessages({
12
+ // Tab labels
13
+ htmlIssues: {
14
+ id: `${scope}.htmlIssues`,
15
+ defaultMessage: 'HTML issues',
16
+ },
17
+ labelIssues: {
18
+ id: `${scope}.labelIssues`,
19
+ defaultMessage: 'Label issues',
20
+ },
21
+ liquidIssues: {
22
+ id: `${scope}.liquidIssues`,
23
+ defaultMessage: 'Liquid issues',
24
+ },
25
+
26
+ // Error item labels
27
+ syntaxError: {
28
+ id: `${scope}.syntaxError`,
29
+ defaultMessage: 'Syntax Error',
30
+ },
31
+ lineChar: {
32
+ id: `${scope}.lineChar`,
33
+ defaultMessage: 'Line {line}, Char {char}.',
34
+ },
35
+
36
+ // Tooltips
37
+ navigateToError: {
38
+ id: `${scope}.navigateToError`,
39
+ defaultMessage: 'Go to error location',
40
+ },
41
+ closePanel: {
42
+ id: `${scope}.closePanel`,
43
+ defaultMessage: 'Close validation panel',
44
+ },
45
+
46
+ // Liquid documentation
47
+ liquidDocumentation: {
48
+ id: `${scope}.liquidDocumentation`,
49
+ defaultMessage: 'Liquid documentation',
50
+ },
51
+ });