@capillarytech/creatives-library 8.0.271 → 8.0.272

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/assets/Android.png +0 -0
  2. package/assets/iOS.png +0 -0
  3. package/constants/unified.js +2 -1
  4. package/initialReducer.js +2 -0
  5. package/package.json +1 -1
  6. package/services/api.js +10 -0
  7. package/services/tests/api.test.js +34 -0
  8. package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
  9. package/tests/integration/TemplateCreation/api-response.js +31 -1
  10. package/tests/integration/TemplateCreation/msw-handler.js +2 -0
  11. package/utils/common.js +5 -0
  12. package/utils/commonUtils.js +28 -5
  13. package/utils/tests/commonUtil.test.js +224 -0
  14. package/utils/transformTemplateConfig.js +0 -10
  15. package/v2Components/CapDeviceContent/index.js +61 -56
  16. package/v2Components/CapTagList/index.js +6 -1
  17. package/v2Components/CapTagListWithInput/index.js +5 -1
  18. package/v2Components/CapTagListWithInput/messages.js +1 -1
  19. package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
  20. package/v2Components/ErrorInfoNote/constants.js +1 -0
  21. package/v2Components/ErrorInfoNote/index.js +402 -72
  22. package/v2Components/ErrorInfoNote/messages.js +32 -6
  23. package/v2Components/ErrorInfoNote/style.scss +278 -6
  24. package/v2Components/FormBuilder/tests/index.test.js +13 -4
  25. package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
  26. package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
  27. package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
  28. package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
  29. package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
  30. package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
  31. package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
  32. package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
  33. package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
  34. package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
  35. package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
  36. package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
  37. package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
  38. package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
  39. package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
  40. package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
  41. package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
  42. package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
  43. package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
  44. package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
  45. package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
  46. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
  47. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
  48. package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
  49. package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
  50. package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
  51. package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
  52. package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
  53. package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
  54. package/v2Components/HtmlEditor/constants.js +45 -20
  55. package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
  56. package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
  57. package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
  58. package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
  59. package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
  60. package/v2Components/HtmlEditor/index.js +1 -1
  61. package/v2Components/HtmlEditor/messages.js +102 -94
  62. package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
  63. package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
  64. package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
  65. package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
  66. package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
  67. package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
  68. package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
  69. package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
  70. package/v2Components/MobilePushPreviewV2/constants.js +6 -0
  71. package/v2Components/MobilePushPreviewV2/index.js +33 -7
  72. package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
  73. package/v2Components/TemplatePreview/index.js +47 -32
  74. package/v2Components/TemplatePreview/messages.js +4 -0
  75. package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
  76. package/v2Containers/BeeEditor/index.js +172 -90
  77. package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
  78. package/v2Containers/BeePopupEditor/constants.js +10 -0
  79. package/v2Containers/BeePopupEditor/index.js +194 -0
  80. package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
  81. package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
  82. package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
  83. package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
  84. package/v2Containers/CreativesContainer/constants.js +1 -0
  85. package/v2Containers/CreativesContainer/index.js +251 -47
  86. package/v2Containers/CreativesContainer/messages.js +8 -0
  87. package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
  88. package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
  89. package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
  90. package/v2Containers/Email/actions.js +7 -0
  91. package/v2Containers/Email/constants.js +5 -1
  92. package/v2Containers/Email/index.js +234 -29
  93. package/v2Containers/Email/messages.js +32 -0
  94. package/v2Containers/Email/reducer.js +12 -1
  95. package/v2Containers/Email/sagas.js +61 -7
  96. package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
  97. package/v2Containers/Email/tests/reducer.test.js +46 -0
  98. package/v2Containers/Email/tests/sagas.test.js +320 -29
  99. package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
  100. package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
  101. package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
  102. package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2472 -0
  103. package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
  104. package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
  105. package/v2Containers/EmailWrapper/constants.js +2 -0
  106. package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
  107. package/v2Containers/EmailWrapper/index.js +103 -23
  108. package/v2Containers/EmailWrapper/messages.js +65 -1
  109. package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
  110. package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
  111. package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
  112. package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
  113. package/v2Containers/InApp/actions.js +7 -0
  114. package/v2Containers/InApp/constants.js +20 -4
  115. package/v2Containers/InApp/index.js +802 -360
  116. package/v2Containers/InApp/index.scss +4 -3
  117. package/v2Containers/InApp/messages.js +7 -3
  118. package/v2Containers/InApp/reducer.js +21 -3
  119. package/v2Containers/InApp/sagas.js +29 -9
  120. package/v2Containers/InApp/selectors.js +25 -5
  121. package/v2Containers/InApp/tests/index.test.js +154 -50
  122. package/v2Containers/InApp/tests/reducer.test.js +34 -0
  123. package/v2Containers/InApp/tests/sagas.test.js +61 -9
  124. package/v2Containers/InApp/tests/selectors.test.js +612 -0
  125. package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
  126. package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
  127. package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
  128. package/v2Containers/InAppWrapper/constants.js +16 -0
  129. package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
  130. package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
  131. package/v2Containers/InAppWrapper/index.js +148 -0
  132. package/v2Containers/InAppWrapper/messages.js +49 -0
  133. package/v2Containers/InappAdvance/index.js +1099 -0
  134. package/v2Containers/InappAdvance/index.scss +10 -0
  135. package/v2Containers/InappAdvance/tests/index.test.js +448 -0
  136. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
  137. package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
  138. package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
  139. package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
  140. package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
  141. package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
  142. package/v2Containers/TagList/index.js +62 -19
  143. package/v2Containers/Templates/_templates.scss +60 -1
  144. package/v2Containers/Templates/index.js +89 -4
  145. package/v2Containers/Templates/messages.js +4 -0
  146. package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
  147. package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
  148. package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
  149. package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
@@ -15,13 +15,13 @@
15
15
  * - Filters dangerous protocols (javascript:, data:, vbscript:)
16
16
  * - Variant-aware sanitization (EMAIL vs INAPP with different security levels)
17
17
  * - Iframe sandbox uses "allow-scripts" only (no allow-same-origin for security)
18
- * - Content delivered via blob URLs to isolate iframe from parent origin
18
+ * - Content delivered via srcdoc attribute for CSP compliance (replaces blob URLs)
19
19
  *
20
20
  * Previous Security Issues (FIXED):
21
21
  * - Removed insecure manual sanitizeJavaScript function that only escaped </script> tags
22
22
  * - Replaced with robust DOMPurify-based sanitization for full XSS protection
23
23
  * - Removed "allow-same-origin" from iframe sandbox to prevent same-origin access
24
- * - Implemented blob URL delivery to maintain isolation while allowing script execution
24
+ * - Replaced blob URLs with srcdoc to comply with CSP frame-src directives
25
25
  */
26
26
 
27
27
  import React, { useMemo, useEffect, useRef } from 'react';
@@ -55,7 +55,7 @@ const PreviewPane = ({
55
55
  className = '',
56
56
  isFullscreenMode = false,
57
57
  isModalContext = false,
58
- layoutType = LAYOUT_TYPES.MODAL
58
+ layoutType = LAYOUT_TYPES.MODAL,
59
59
  }) => {
60
60
  const { content, layout, variant } = useEditorContext();
61
61
 
@@ -103,36 +103,25 @@ const PreviewPane = ({
103
103
  return sanitizationResult.sanitized;
104
104
  }, [contentValue, variant]);
105
105
 
106
- // Create blob URL for secure iframe content delivery
107
- // This isolates the iframe content from the parent origin while allowing script execution
108
- const blobUrlRef = useRef(null);
109
- const blobUrl = useMemo(() => {
110
- // Clean up previous blob URL to prevent memory leaks
111
- if (blobUrlRef.current) {
112
- URL.revokeObjectURL(blobUrlRef.current);
113
- blobUrlRef.current = null;
106
+ // Use srcdoc instead of blob URLs for CSP compliance
107
+ // srcdoc allows embedding HTML directly in iframe without violating frame-src CSP directives
108
+ // This is more secure and CSP-compliant than blob URLs
109
+ const iframeContent = useMemo(() => {
110
+ if (!combinedContent) {
111
+ return null;
114
112
  }
115
113
 
116
- // Create new blob URL if we have content
117
- if (combinedContent) {
118
- const blob = new Blob([combinedContent], { type: 'text/html' });
119
- blobUrlRef.current = URL.createObjectURL(blob);
120
- return blobUrlRef.current;
114
+ // srcdoc has a size limit (~2MB in some browsers), but for HTML preview content this should be sufficient
115
+ // If content is too large, we'll fall back to about:blank and log a warning
116
+ const MAX_SRCDOC_SIZE = 2000000; // 2MB limit
117
+ if (combinedContent.length > MAX_SRCDOC_SIZE) {
118
+ console.warn('PreviewPane: Content too large for srcdoc, using about:blank');
119
+ return null;
121
120
  }
122
121
 
123
- return null;
122
+ return combinedContent;
124
123
  }, [combinedContent]);
125
124
 
126
- // Cleanup blob URL on unmount
127
- useEffect(() => {
128
- return () => {
129
- if (blobUrlRef.current) {
130
- URL.revokeObjectURL(blobUrlRef.current);
131
- blobUrlRef.current = null;
132
- }
133
- };
134
- }, []);
135
-
136
125
  // Generate CSS classes based on view mode
137
126
  const getIframeClasses = () => {
138
127
  const baseClass = 'preview-pane__iframe';
@@ -154,15 +143,15 @@ const PreviewPane = ({
154
143
  // Use layout.mobileWidth or fallback to 375px
155
144
  const width = mobileWidth || 375;
156
145
  baseStyles.width = `${width}px`;
157
- baseStyles.height = '100%'; // Fill container height
158
146
  }
159
-
147
+ baseStyles.height = '100%'; // Fill container height
160
148
  return baseStyles;
161
149
  };
162
150
 
163
151
  const renderDeviceFrame = () => (
164
152
  <iframe
165
- src={blobUrl || 'about:blank'}
153
+ src={iframeContent ? undefined : 'about:blank'}
154
+ srcDoc={iframeContent || undefined}
166
155
  title={intl.formatMessage(messages.htmlPreview)}
167
156
  className={getIframeClasses()}
168
157
  style={getPreviewStyles()}
@@ -173,17 +162,7 @@ const PreviewPane = ({
173
162
  const renderEmptyState = () => (
174
163
  <div className="preview-empty">
175
164
  <div className="empty-content">
176
- <div className="empty-icon">📝</div>
177
- <p>{intl.formatMessage(messages.startTypingHtml)}</p>
178
- <p className="preview-mode-info">
179
- {intl.formatMessage(messages.previewMode)}: {
180
- viewMode === PREVIEW_MODES.DESKTOP
181
- ? intl.formatMessage(messages.desktop)
182
- : viewMode === PREVIEW_MODES.MOBILE
183
- ? intl.formatMessage(messages.mobile)
184
- : intl.formatMessage(messages.mobileDevice)
185
- }
186
- </p>
165
+ <p className="preview-mode-info"></p>
187
166
  </div>
188
167
  </div>
189
168
  );
@@ -212,7 +191,7 @@ const PreviewPane = ({
212
191
  </CapRow>
213
192
 
214
193
  <CapRow className="preview-pane__content">
215
- {blobUrl ? renderDeviceFrame() : renderEmptyState()}
194
+ {iframeContent ? renderDeviceFrame() : renderEmptyState()}
216
195
  </CapRow>
217
196
  </div>
218
197
  );
@@ -223,7 +202,7 @@ PreviewPane.propTypes = {
223
202
  className: PropTypes.string,
224
203
  isFullscreenMode: PropTypes.bool,
225
204
  isModalContext: PropTypes.bool,
226
- layoutType: PropTypes.oneOf(Object.values(LAYOUT_TYPES))
205
+ layoutType: PropTypes.oneOf(Object.values(LAYOUT_TYPES)),
227
206
  };
228
207
 
229
208
  export default injectIntl(PreviewPane);
@@ -161,7 +161,7 @@
161
161
 
162
162
  // Dragging state styling
163
163
  &--dragging &__splitter {
164
- background-color: map-get($CAP_PRIMARY, base);
164
+ background-color: map-get($CAP_SECONDARY, base);
165
165
  }
166
166
 
167
167
  &--dragging &__splitter-line {
@@ -1,5 +1,7 @@
1
1
  /**
2
2
  * ValidationErrorDisplay Styles
3
+ *
4
+ * When collapsed, panel sticks to footer (header bar only visible).
3
5
  */
4
6
 
5
7
  @import '~@capillarytech/cap-ui-library/styles/_variables.scss';
@@ -9,10 +11,18 @@
9
11
  flex-direction: column;
10
12
  width: 100%;
11
13
  margin-bottom: 1rem;
14
+ flex-shrink: 0;
12
15
 
13
16
  // Ensure proper spacing when used in different contexts
14
17
  &:last-child {
15
18
  margin-bottom: 0;
19
+ padding-bottom: 0.25rem;
20
+ }
21
+
22
+ // Collapsed: panel stays in footer as a slim bar (tabs + expand arrow only)
23
+ &--collapsed {
24
+ margin-bottom: 0;
25
+ padding-bottom: 0.25rem;
16
26
  }
17
27
 
18
28
  // Integration with ErrorInfoNote component
@@ -23,9 +33,17 @@
23
33
  // Responsive adjustments
24
34
  @media (max-width: 768px) {
25
35
  margin-bottom: 0.75rem;
36
+
37
+ &.validation-error-display--collapsed {
38
+ margin-bottom: 0;
39
+ }
26
40
  }
27
41
 
28
42
  @media (max-width: 576px) {
29
43
  margin-bottom: 0.5rem;
44
+
45
+ &.validation-error-display--collapsed {
46
+ margin-bottom: 0;
47
+ }
30
48
  }
31
49
  }
@@ -1,18 +1,15 @@
1
1
  /**
2
- * ValidationErrorDisplay - HTML Editor validation using ErrorInfoNote
2
+ * ValidationErrorDisplay - HTML Editor validation display
3
3
  *
4
- * This component integrates the existing ErrorInfoNote component with the HTML Editor's
5
- * validation system, providing a consistent error display that matches the Figma design.
4
+ * Displays validation errors and warnings in two tabs (Errors, Warnings).
5
+ * Panel can be collapsed to a sticky footer bar; it does not disappear.
6
6
  */
7
7
 
8
- import React from 'react';
8
+ import React, { useState } from 'react';
9
9
  import PropTypes from 'prop-types';
10
10
 
11
- import CapRow from '@capillarytech/cap-ui-library/CapRow';
12
- import ErrorInfoNote from '../../../ErrorInfoNote';
13
-
14
- import { transformValidationToErrorInfo, hasValidationErrors } from '../../utils/validationAdapter';
15
- import { HTML_EDITOR_VARIANTS } from '../../constants';
11
+ import { hasValidationErrors } from '../../utils/validationAdapter';
12
+ import ValidationTabs from '../ValidationTabs';
16
13
 
17
14
  // Styles
18
15
  import './_validationErrorDisplay.scss';
@@ -20,51 +17,59 @@ import './_validationErrorDisplay.scss';
20
17
  /**
21
18
  * ValidationErrorDisplay Component
22
19
  *
23
- * Displays validation errors using the existing ErrorInfoNote component
20
+ * Displays validation issues in Errors and Warnings tabs; collapse toggles visibility but panel stays in footer.
24
21
  */
25
22
  const ValidationErrorDisplay = ({
26
23
  validation,
27
- variant = HTML_EDITOR_VARIANTS.EMAIL,
28
24
  onErrorClick,
29
- className = ''
25
+ className = '',
30
26
  }) => {
31
- // Don't render if no validation or no errors
32
- if (!hasValidationErrors(validation)) {
33
- return null;
34
- }
27
+ const [isCollapsed, setIsCollapsed] = useState(false);
35
28
 
36
- // Transform validation data to ErrorInfoNote format
37
- const errorData = transformValidationToErrorInfo(validation, variant);
38
- const { errorMessages } = errorData || {};
29
+ const handleToggleCollapse = () => {
30
+ setIsCollapsed((prev) => !prev);
31
+ };
39
32
 
40
- // Handle error click if provided
41
- const handleErrorClick = (error) => {
42
- onErrorClick?.(error);
33
+ // Expand panel when user clicks redirection (so panel does not stay stuck to footer)
34
+ const handleExpand = () => {
35
+ setIsCollapsed(false);
43
36
  };
44
37
 
38
+ if (!hasValidationErrors(validation)) {
39
+ return null;
40
+ }
41
+
45
42
  return (
46
- <CapRow
47
- className={`validation-error-display ${className}`}
43
+ <div
44
+ className={`validation-error-display ${isCollapsed ? 'validation-error-display--collapsed' : ''} ${className}`}
48
45
  role="alert"
49
46
  aria-live="polite"
50
47
  aria-label="Validation errors"
51
48
  >
52
- <ErrorInfoNote
53
- errorMessages={errorMessages}
54
- onErrorClick={handleErrorClick}
49
+ <ValidationTabs
50
+ validation={validation}
51
+ onErrorClick={onErrorClick}
52
+ isCollapsed={isCollapsed}
53
+ onToggleCollapse={handleToggleCollapse}
54
+ onExpand={handleExpand}
55
55
  />
56
- </CapRow>
56
+ </div>
57
57
  );
58
58
  };
59
59
 
60
60
  ValidationErrorDisplay.propTypes = {
61
61
  validation: PropTypes.shape({
62
62
  isValidating: PropTypes.bool,
63
- getAllIssues: PropTypes.func
63
+ getAllIssues: PropTypes.func,
64
64
  }),
65
- variant: PropTypes.oneOf(Object.values(HTML_EDITOR_VARIANTS)),
66
65
  onErrorClick: PropTypes.func,
67
- className: PropTypes.string
66
+ className: PropTypes.string,
67
+ };
68
+
69
+ ValidationErrorDisplay.defaultProps = {
70
+ validation: null,
71
+ onErrorClick: null,
72
+ className: '',
68
73
  };
69
74
 
70
75
  export default ValidationErrorDisplay;
@@ -5,17 +5,17 @@
5
5
  @import '~@capillarytech/cap-ui-library/styles/_variables.scss';
6
6
 
7
7
  .validation-panel {
8
- border: 1px solid #d9d9d9;
9
- border-radius: 6px;
10
- background: #fff;
8
+ border: 1px solid $CAP_COLOR_16;
9
+ border-radius: $CAP_SPACE_06;
10
+ background: $CAP_WHITE;
11
11
 
12
12
  &--loading {
13
- padding: 16px;
13
+ padding: $CAP_SPACE_16;
14
14
  text-align: center;
15
15
  }
16
16
 
17
17
  &--clean {
18
- padding: 16px;
18
+ padding: $CAP_SPACE_16;
19
19
  text-align: center;
20
20
  background: #f6ffed;
21
21
  border-color: #b7eb8f;
@@ -25,7 +25,7 @@
25
25
  display: flex;
26
26
  align-items: center;
27
27
  justify-content: center;
28
- gap: 8px;
28
+ gap: $CAP_SPACE_08;
29
29
  color: #666;
30
30
  font-size: 14px;
31
31
  }
@@ -34,36 +34,36 @@
34
34
  display: flex;
35
35
  align-items: center;
36
36
  justify-content: center;
37
- gap: 8px;
37
+ gap: $CAP_SPACE_08;
38
38
  color: #52c41a;
39
39
  font-size: 14px;
40
- font-weight: 500;
40
+ font-weight: $FONT_WEIGHT_MEDIUM;
41
41
  }
42
42
 
43
43
  &__summary {
44
44
  display: flex;
45
- gap: 16px;
46
- padding: 12px 16px;
47
- background: #fafafa;
48
- border-bottom: 1px solid #d9d9d9;
49
- border-radius: 6px 6px 0 0;
45
+ gap: $CAP_SPACE_16;
46
+ padding: $CAP_SPACE_12 $CAP_SPACE_16;
47
+ background: $CAP_G21;
48
+ border-bottom: 1px solid $CAP_COLOR_16;
49
+ border-radius: $CAP_SPACE_06 $CAP_SPACE_06 0 0;
50
50
  }
51
51
 
52
52
  &__summary-item {
53
53
  display: flex;
54
54
  align-items: center;
55
- gap: 4px;
56
- font-size: 12px;
55
+ gap: $CAP_SPACE_04;
56
+ font-size: $CAP_SPACE_12;
57
57
  color: #666;
58
58
 
59
59
  &--security {
60
60
  color: #ff4d4f;
61
- font-weight: 500;
61
+ font-weight: $FONT_WEIGHT_MEDIUM;
62
62
  }
63
63
 
64
64
  span:first-of-type {
65
65
  font-weight: 600;
66
- margin-left: 2px;
66
+ margin-left: $CAP_SPACE_02;
67
67
  }
68
68
  }
69
69
 
@@ -72,7 +72,7 @@
72
72
  border: none;
73
73
 
74
74
  &:last-child {
75
- border-radius: 0 0 6px 6px;
75
+ border-radius: 0 0 $CAP_SPACE_06 $CAP_SPACE_06;
76
76
  }
77
77
  }
78
78
 
@@ -123,8 +123,8 @@
123
123
  &__title {
124
124
  display: flex;
125
125
  align-items: center;
126
- gap: 8px;
127
- font-weight: 500;
126
+ gap: $CAP_SPACE_08;
127
+ font-weight: $FONT_WEIGHT_MEDIUM;
128
128
  }
129
129
 
130
130
  &__issues {
@@ -133,41 +133,41 @@
133
133
  }
134
134
 
135
135
  &__sanitization {
136
- border-top: 1px solid #d9d9d9;
136
+ border-top: 1px solid $CAP_COLOR_16;
137
137
  background: #f9f9f9;
138
138
  }
139
139
 
140
140
  &__sanitization-header {
141
141
  display: flex;
142
142
  align-items: center;
143
- gap: 8px;
144
- padding: 12px 16px;
145
- font-weight: 500;
143
+ gap: $CAP_SPACE_08;
144
+ padding: $CAP_SPACE_12 $CAP_SPACE_16;
145
+ font-weight: $FONT_WEIGHT_MEDIUM;
146
146
  color: #666;
147
147
  font-size: 13px;
148
148
  }
149
149
 
150
150
  &__sanitization-list {
151
- padding: 0 16px 12px;
151
+ padding: 0 $CAP_SPACE_16 $CAP_SPACE_12;
152
152
  }
153
153
 
154
154
  &__sanitization-item {
155
- padding: 4px 0;
156
- font-size: 12px;
155
+ padding: $CAP_SPACE_04 0;
156
+ font-size: $CAP_SPACE_12;
157
157
  color: #8c8c8c;
158
158
  }
159
159
  }
160
160
 
161
161
  .validation-issue {
162
162
  display: flex;
163
- gap: 12px;
164
- padding: 12px 16px;
163
+ gap: $CAP_SPACE_12;
164
+ padding: $CAP_SPACE_12 $CAP_SPACE_16;
165
165
  border-bottom: 1px solid #f0f0f0;
166
166
  cursor: pointer;
167
167
  transition: background-color 0.2s;
168
168
 
169
169
  &:hover {
170
- background: #fafafa;
170
+ background: $CAP_G21;
171
171
  }
172
172
 
173
173
  &:last-child {
@@ -189,6 +189,18 @@
189
189
  &__icon {
190
190
  flex-shrink: 0;
191
191
  margin-top: 2px;
192
+
193
+ &--error {
194
+ color: #ff4d4f;
195
+ }
196
+
197
+ &--info {
198
+ color: #1890ff;
199
+ }
200
+
201
+ &--security {
202
+ color: #ff4d4f;
203
+ }
192
204
  }
193
205
 
194
206
  &__content {
@@ -200,14 +212,14 @@
200
212
  font-size: 13px;
201
213
  line-height: 1.4;
202
214
  color: #262626;
203
- margin-bottom: 4px;
215
+ margin-bottom: $CAP_SPACE_04;
204
216
  word-break: break-word;
205
217
  }
206
218
 
207
219
  &__meta {
208
220
  display: flex;
209
221
  flex-wrap: wrap;
210
- gap: 8px;
222
+ gap: $CAP_SPACE_08;
211
223
  font-size: 11px;
212
224
  color: #8c8c8c;
213
225
  }
@@ -230,9 +242,9 @@
230
242
  &__source {
231
243
  display: flex;
232
244
  align-items: center;
233
- gap: 4px;
245
+ gap: $CAP_SPACE_04;
234
246
  background: #f0f0f0;
235
- padding: 2px 6px;
247
+ padding: $CAP_SPACE_02 $CAP_SPACE_06;
236
248
  border-radius: 3px;
237
249
  }
238
250
  }
@@ -0,0 +1,6 @@
1
+ // Severity constants
2
+ export const SEVERITY = {
3
+ ERROR: 'error',
4
+ WARNING: 'warning',
5
+ INFO: 'info',
6
+ };
@@ -21,13 +21,15 @@ import ShieldOutlined from '@ant-design/icons/ShieldOutlined';
21
21
  import BugOutlined from '@ant-design/icons/BugOutlined';
22
22
  import CodeOutlined from '@ant-design/icons/CodeOutlined';
23
23
  import EyeInvisibleOutlined from '@ant-design/icons/EyeInvisibleOutlined';
24
- import CheckCircleOutlined from '@ant-design/icons/CheckCircleOutlined';
24
+ import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
25
25
 
26
26
  import messages from './messages';
27
+ import { BLOCKING_ERROR_RULE_IDS } from '../../constants';
27
28
  import './_validationPanel.scss';
29
+ import { SEVERITY } from './constants';
30
+ import { ISSUE_SOURCES } from '../../utils/validationConstants';
28
31
 
29
32
  const { Panel } = Collapse;
30
-
31
33
  /**
32
34
  * ValidationPanel Component
33
35
  */
@@ -37,7 +39,6 @@ const ValidationPanel = ({
37
39
  onErrorClick,
38
40
  showLineNumbers = true,
39
41
  groupBySource = false,
40
- variant = 'email'
41
42
  }) => {
42
43
  const [activeKeys, setActiveKeys] = useState(['errors', 'warnings']);
43
44
 
@@ -56,33 +57,35 @@ const ValidationPanel = ({
56
57
  groups[source].push(issue);
57
58
  return groups;
58
59
  }, {});
59
- } else {
60
- return {
61
- errors: allIssues.filter(issue => issue.severity === 'error'),
62
- warnings: allIssues.filter(issue => issue.severity === 'warning'),
63
- info: allIssues.filter(issue => issue.severity === 'info')
64
- };
65
60
  }
61
+ return {
62
+ errors: allIssues.filter((issue) => issue.severity === SEVERITY.ERROR),
63
+ warnings: allIssues.filter((issue) => issue.severity === SEVERITY.WARNING),
64
+ info: allIssues.filter((issue) => issue.severity === SEVERITY.INFO),
65
+ };
66
66
  }, [validation, groupBySource]);
67
67
 
68
- // Get icon for issue type
69
- const getIssueIcon = (severity, source) => {
70
- if (source === 'security') {
71
- return <ShieldOutlined style={{ color: '#ff4d4f' }} />;
68
+ // Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
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;
72
74
  }
73
-
74
- switch (severity) {
75
- case 'error':
76
- return <ExclamationCircleOutlined style={{ color: '#ff4d4f' }} />;
77
- case 'warning':
78
- return <WarningOutlined style={{ color: '#faad14' }} />;
79
- case 'info':
80
- return <InfoCircleOutlined style={{ color: '#1890ff' }} />;
81
- default:
82
- return <BugOutlined style={{ color: '#666' }} />;
75
+ // Client-side Liquid validation errors are blocking (genuine syntax errors)
76
+ if (source === ISSUE_SOURCES.LIQUID && severity === SEVERITY.ERROR) {
77
+ return true;
78
+ }
79
+ // Rule Group #1 errors are blocking
80
+ if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
81
+ return true;
83
82
  }
83
+ return false;
84
84
  };
85
85
 
86
+ // Get icon for issue type
87
+ // Blocking errors use error-icon, warnings use warning
88
+
86
89
  // Get source icon
87
90
  const getSourceIcon = (source) => {
88
91
  switch (source) {
@@ -106,7 +109,7 @@ const ValidationPanel = ({
106
109
  line: issue.line,
107
110
  column: issue.column || 1,
108
111
  message: issue.message,
109
- severity: issue.severity
112
+ severity: issue.severity,
110
113
  });
111
114
  }
112
115
  };
@@ -125,9 +128,6 @@ const ValidationPanel = ({
125
128
  }
126
129
  }}
127
130
  >
128
- <div className="validation-issue__icon">
129
- {getIssueIcon(issue.severity, issue.source)}
130
- </div>
131
131
 
132
132
  <div className="validation-issue__content">
133
133
  <div className="validation-issue__message">
@@ -137,10 +137,12 @@ const ValidationPanel = ({
137
137
  <div className="validation-issue__meta">
138
138
  {showLineNumbers && issue.line && (
139
139
  <span className="validation-issue__location">
140
- <FormattedMessage {...messages.lineColumn} values={{
141
- line: issue.line,
142
- column: issue.column || 1
143
- }} />
140
+ <FormattedMessage
141
+ {...messages.lineColumn}
142
+ values={{
143
+ line: issue.line,
144
+ column: issue.column || 1,
145
+ }} />
144
146
  </span>
145
147
  )}
146
148
 
@@ -162,17 +164,21 @@ const ValidationPanel = ({
162
164
  );
163
165
 
164
166
  // Render panel header with count
165
- const renderPanelHeader = (title, count, severity) => (
166
- <div className="validation-panel__header">
167
- <span className="validation-panel__title">
168
- {getIssueIcon(severity)}
169
- <FormattedMessage {...title} />
170
- </span>
171
- {count > 0 && (
172
- <Badge count={count} style={{ backgroundColor: getSeverityColor(severity) }} />
173
- )}
174
- </div>
175
- );
167
+ const renderPanelHeader = (title, count, severity, issues = []) =>
168
+ // Check if any issue in this group is a blocking error
169
+ // Use blocking error icon only if there are blocking errors, otherwise use warning icon
170
+
171
+ (
172
+ <div className="validation-panel__header">
173
+ <span className="validation-panel__title">
174
+ <FormattedMessage {...title} />
175
+ </span>
176
+ {count > 0 && (
177
+ <Badge count={count} style={{ backgroundColor: getSeverityColor(severity) }} />
178
+ )}
179
+ </div>
180
+ )
181
+ ;
176
182
 
177
183
  // Get severity color
178
184
  const getSeverityColor = (severity) => {
@@ -241,14 +247,14 @@ const ValidationPanel = ({
241
247
  : messages[key] || { id: `htmlEditor.validation.${key}`, defaultMessage: key };
242
248
 
243
249
  const severity = isSourceGroup
244
- ? (issues.find(i => i.severity === 'error') ? 'error' :
245
- issues.find(i => i.severity === 'warning') ? 'warning' : 'info')
250
+ ? (issues.find((i) => i.severity === SEVERITY.ERROR) ? SEVERITY.ERROR
251
+ : issues.find((i) => i.severity === SEVERITY.WARNING) ? SEVERITY.WARNING : SEVERITY.INFO)
246
252
  : key;
247
253
 
248
254
  return (
249
255
  <Panel
250
256
  key={key}
251
- header={renderPanelHeader(title, issues.length, severity)}
257
+ header={renderPanelHeader(title, issues.length, severity, issues)}
252
258
  className={`validation-panel__panel validation-panel__panel--${severity}`}
253
259
  >
254
260
  <div className="validation-panel__issues">
@@ -291,7 +297,7 @@ ValidationPanel.propTypes = {
291
297
  onErrorClick: PropTypes.func,
292
298
  showLineNumbers: PropTypes.bool,
293
299
  groupBySource: PropTypes.bool,
294
- variant: PropTypes.oneOf(['email', 'inapp'])
300
+ variant: PropTypes.oneOf(['email', 'inapp']),
295
301
  };
296
302
 
297
303
  export default ValidationPanel;