@capillarytech/creatives-library 8.0.271 → 8.0.273
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.
- package/assets/Android.png +0 -0
- package/assets/iOS.png +0 -0
- package/constants/unified.js +2 -1
- package/initialReducer.js +2 -0
- package/package.json +1 -1
- package/services/api.js +10 -0
- package/services/tests/api.test.js +34 -0
- package/tests/integration/TemplateCreation/TemplateCreation.integration.test.js +17 -35
- package/tests/integration/TemplateCreation/api-response.js +31 -1
- package/tests/integration/TemplateCreation/msw-handler.js +2 -0
- package/utils/common.js +5 -0
- package/utils/commonUtils.js +28 -5
- package/utils/imageUrlUpload.js +13 -14
- package/utils/tests/commonUtil.test.js +224 -0
- package/utils/tests/imageUrlUpload.test.js +298 -0
- package/utils/transformTemplateConfig.js +0 -10
- package/v2Components/CapDeviceContent/index.js +61 -56
- package/v2Components/CapTagList/index.js +6 -1
- package/v2Components/CapTagListWithInput/index.js +5 -1
- package/v2Components/CapTagListWithInput/messages.js +1 -1
- package/v2Components/CapWhatsappCTA/tests/index.test.js +5 -0
- package/v2Components/ErrorInfoNote/constants.js +1 -0
- package/v2Components/ErrorInfoNote/index.js +402 -72
- package/v2Components/ErrorInfoNote/messages.js +32 -6
- package/v2Components/ErrorInfoNote/style.scss +278 -6
- package/v2Components/FormBuilder/tests/index.test.js +13 -4
- package/v2Components/HtmlEditor/HTMLEditor.js +418 -99
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.apiErrors.test.js +870 -0
- package/v2Components/HtmlEditor/__tests__/HTMLEditor.test.js +1882 -133
- package/v2Components/HtmlEditor/__tests__/index.lazy.test.js +27 -16
- package/v2Components/HtmlEditor/_htmlEditor.scss +108 -45
- package/v2Components/HtmlEditor/_index.lazy.scss +0 -1
- package/v2Components/HtmlEditor/components/CodeEditorPane/_codeEditorPane.scss +23 -102
- package/v2Components/HtmlEditor/components/CodeEditorPane/index.js +148 -140
- package/v2Components/HtmlEditor/components/DeviceToggle/_deviceToggle.scss +2 -1
- package/v2Components/HtmlEditor/components/DeviceToggle/index.js +3 -3
- package/v2Components/HtmlEditor/components/EditorToolbar/_editorToolbar.scss +9 -1
- package/v2Components/HtmlEditor/components/EditorToolbar/index.js +31 -6
- package/v2Components/HtmlEditor/components/FullscreenModal/_fullscreenModal.scss +22 -0
- package/v2Components/HtmlEditor/components/InAppPreviewPane/DeviceFrame.js +4 -7
- package/v2Components/HtmlEditor/components/InAppPreviewPane/__tests__/DeviceFrame.test.js +35 -45
- package/v2Components/HtmlEditor/components/InAppPreviewPane/_inAppPreviewPane.scss +1 -3
- package/v2Components/HtmlEditor/components/InAppPreviewPane/constants.js +33 -33
- package/v2Components/HtmlEditor/components/InAppPreviewPane/index.js +7 -6
- package/v2Components/HtmlEditor/components/PreviewPane/_previewPane.scss +7 -10
- package/v2Components/HtmlEditor/components/PreviewPane/index.js +22 -43
- package/v2Components/HtmlEditor/components/SplitContainer/_splitContainer.scss +1 -1
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/_validationErrorDisplay.scss +18 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/index.js +36 -31
- package/v2Components/HtmlEditor/components/ValidationPanel/_validationPanel.scss +46 -34
- package/v2Components/HtmlEditor/components/ValidationPanel/constants.js +6 -0
- package/v2Components/HtmlEditor/components/ValidationPanel/index.js +52 -46
- package/v2Components/HtmlEditor/components/ValidationTabs/_validationTabs.scss +277 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/index.js +295 -0
- package/v2Components/HtmlEditor/components/ValidationTabs/messages.js +51 -0
- package/v2Components/HtmlEditor/constants.js +45 -20
- package/v2Components/HtmlEditor/hooks/__tests__/useInAppContent.test.js +373 -16
- package/v2Components/HtmlEditor/hooks/__tests__/useValidation.test.js +351 -16
- package/v2Components/HtmlEditor/hooks/useEditorContent.js +5 -2
- package/v2Components/HtmlEditor/hooks/useInAppContent.js +88 -146
- package/v2Components/HtmlEditor/hooks/useValidation.js +213 -56
- package/v2Components/HtmlEditor/index.js +1 -1
- package/v2Components/HtmlEditor/messages.js +102 -94
- package/v2Components/HtmlEditor/utils/__tests__/htmlValidator.enhanced.test.js +214 -45
- package/v2Components/HtmlEditor/utils/__tests__/validationAdapter.test.js +134 -0
- package/v2Components/HtmlEditor/utils/contentSanitizer.js +40 -41
- package/v2Components/HtmlEditor/utils/htmlValidator.js +71 -72
- package/v2Components/HtmlEditor/utils/liquidTemplateSupport.js +158 -124
- package/v2Components/HtmlEditor/utils/properSyntaxHighlighting.js +23 -25
- package/v2Components/HtmlEditor/utils/validationAdapter.js +66 -41
- package/v2Components/HtmlEditor/utils/validationConstants.js +38 -0
- package/v2Components/MobilePushPreviewV2/constants.js +6 -0
- package/v2Components/MobilePushPreviewV2/index.js +33 -7
- package/v2Components/TemplatePreview/_templatePreview.scss +55 -24
- package/v2Components/TemplatePreview/index.js +47 -32
- package/v2Components/TemplatePreview/messages.js +4 -0
- package/v2Components/TestAndPreviewSlidebox/_testAndPreviewSlidebox.scss +1 -0
- package/v2Containers/BeeEditor/index.js +172 -90
- package/v2Containers/BeePopupEditor/_beePopupEditor.scss +14 -0
- package/v2Containers/BeePopupEditor/constants.js +10 -0
- package/v2Containers/BeePopupEditor/index.js +194 -0
- package/v2Containers/BeePopupEditor/tests/index.test.js +627 -0
- package/v2Containers/CreativesContainer/SlideBoxContent.js +127 -51
- package/v2Containers/CreativesContainer/SlideBoxFooter.js +156 -13
- package/v2Containers/CreativesContainer/SlideBoxHeader.js +2 -1
- package/v2Containers/CreativesContainer/constants.js +1 -0
- package/v2Containers/CreativesContainer/index.js +251 -47
- package/v2Containers/CreativesContainer/messages.js +8 -0
- package/v2Containers/CreativesContainer/tests/SlideBoxFooter.test.js +11 -2
- package/v2Containers/CreativesContainer/tests/__snapshots__/SlideBoxContent.test.js.snap +38 -50
- package/v2Containers/CreativesContainer/tests/__snapshots__/index.test.js.snap +103 -0
- package/v2Containers/Email/actions.js +7 -0
- package/v2Containers/Email/constants.js +5 -1
- package/v2Containers/Email/index.js +234 -29
- package/v2Containers/Email/messages.js +32 -0
- package/v2Containers/Email/reducer.js +12 -1
- package/v2Containers/Email/sagas.js +61 -7
- package/v2Containers/Email/tests/__snapshots__/reducer.test.js.snap +2 -0
- package/v2Containers/Email/tests/reducer.test.js +46 -0
- package/v2Containers/Email/tests/sagas.test.js +320 -29
- package/v2Containers/EmailWrapper/components/EmailHTMLEditor.js +1246 -0
- package/v2Containers/EmailWrapper/components/EmailWrapperView.js +212 -21
- package/v2Containers/EmailWrapper/components/HTMLEditorTesting.js +40 -74
- package/v2Containers/EmailWrapper/components/__tests__/EmailHTMLEditor.test.js +2614 -0
- package/v2Containers/EmailWrapper/components/__tests__/EmailWrapperView.test.js +520 -0
- package/v2Containers/EmailWrapper/components/__tests__/HTMLEditorTesting.test.js +2 -67
- package/v2Containers/EmailWrapper/constants.js +2 -0
- package/v2Containers/EmailWrapper/hooks/useEmailWrapper.js +627 -79
- package/v2Containers/EmailWrapper/index.js +103 -23
- package/v2Containers/EmailWrapper/messages.js +65 -1
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.edgeCases.test.js +955 -0
- package/v2Containers/EmailWrapper/tests/useEmailWrapper.test.js +596 -82
- package/v2Containers/InApp/__tests__/InAppHTMLEditor.test.js +376 -0
- package/v2Containers/InApp/__tests__/sagas.test.js +363 -0
- package/v2Containers/InApp/actions.js +7 -0
- package/v2Containers/InApp/constants.js +20 -4
- package/v2Containers/InApp/index.js +802 -360
- package/v2Containers/InApp/index.scss +4 -3
- package/v2Containers/InApp/messages.js +7 -3
- package/v2Containers/InApp/reducer.js +21 -3
- package/v2Containers/InApp/sagas.js +29 -9
- package/v2Containers/InApp/selectors.js +25 -5
- package/v2Containers/InApp/tests/index.test.js +154 -50
- package/v2Containers/InApp/tests/reducer.test.js +34 -0
- package/v2Containers/InApp/tests/sagas.test.js +61 -9
- package/v2Containers/InApp/tests/selectors.test.js +612 -0
- package/v2Containers/InAppWrapper/components/InAppWrapperView.js +151 -0
- package/v2Containers/InAppWrapper/components/__tests__/InAppWrapperView.test.js +267 -0
- package/v2Containers/InAppWrapper/components/inAppWrapperView.scss +23 -0
- package/v2Containers/InAppWrapper/constants.js +16 -0
- package/v2Containers/InAppWrapper/hooks/__tests__/useInAppWrapper.test.js +473 -0
- package/v2Containers/InAppWrapper/hooks/useInAppWrapper.js +198 -0
- package/v2Containers/InAppWrapper/index.js +148 -0
- package/v2Containers/InAppWrapper/messages.js +49 -0
- package/v2Containers/InappAdvance/index.js +1099 -0
- package/v2Containers/InappAdvance/index.scss +10 -0
- package/v2Containers/InappAdvance/tests/index.test.js +448 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/content.test.js.snap +3 -0
- package/v2Containers/Line/Container/ImageCarousel/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/Wrapper/tests/__snapshots__/index.test.js.snap +2 -0
- package/v2Containers/Line/Container/tests/__snapshots__/index.test.js.snap +9 -0
- package/v2Containers/MobilePush/Create/index.js +1 -1
- package/v2Containers/MobilePush/Edit/index.js +10 -6
- package/v2Containers/Rcs/tests/__snapshots__/index.test.js.snap +12 -0
- package/v2Containers/SmsTrai/Edit/tests/__snapshots__/index.test.js.snap +4 -0
- package/v2Containers/TagList/index.js +62 -19
- package/v2Containers/Templates/_templates.scss +60 -1
- package/v2Containers/Templates/index.js +89 -4
- package/v2Containers/Templates/messages.js +4 -0
- package/v2Containers/TemplatesV2/TemplatesV2.style.js +4 -2
- package/v2Containers/Whatsapp/tests/__snapshots__/index.test.js.snap +34 -0
- package/v2Components/HtmlEditor/components/ValidationErrorDisplay/__tests__/index.test.js +0 -152
- package/v2Containers/EmailWrapper/tests/EmailWrapperView.test.js +0 -214
|
@@ -0,0 +1,277 @@
|
|
|
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
|
+
border-radius: 0.25rem;
|
|
11
|
+
box-sizing: border-box;
|
|
12
|
+
|
|
13
|
+
&__header {
|
|
14
|
+
display: flex;
|
|
15
|
+
align-items: flex-start;
|
|
16
|
+
justify-content: space-between;
|
|
17
|
+
width: 100%;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
&__tabs {
|
|
21
|
+
flex: 1;
|
|
22
|
+
|
|
23
|
+
// Override CapTab styles for proper spacing
|
|
24
|
+
.cap-tab-v2 {
|
|
25
|
+
.ant-tabs-bar {
|
|
26
|
+
padding-right: 0.5rem;
|
|
27
|
+
padding-top: 0.5rem;
|
|
28
|
+
}
|
|
29
|
+
.ant-tabs-nav {
|
|
30
|
+
margin-bottom: 0;
|
|
31
|
+
|
|
32
|
+
&::before {
|
|
33
|
+
border-bottom: none; // Remove bottom border
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.ant-tabs-tab {
|
|
38
|
+
padding: 0.5rem 0.75rem; // Add horizontal padding for spacing
|
|
39
|
+
margin-right: 0; // Remove margin, use padding instead
|
|
40
|
+
|
|
41
|
+
color: $CAP_G04;
|
|
42
|
+
font-size: 0.875rem;
|
|
43
|
+
font-weight: 400;
|
|
44
|
+
line-height: 1;
|
|
45
|
+
letter-spacing: 0;
|
|
46
|
+
border-radius: 0.25rem;
|
|
47
|
+
border-bottom: none; // Remove bottom border
|
|
48
|
+
|
|
49
|
+
&.ant-tabs-tab {
|
|
50
|
+
margin-left: 1rem; // Add space between tabs
|
|
51
|
+
font-weight: 400;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
&:hover {
|
|
55
|
+
color: $CAP_COLOR_05;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
&.ant-tabs-tab-active {
|
|
59
|
+
color: $FONT_COLOR_01;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
|
|
62
|
+
.ant-tabs-tab-btn {
|
|
63
|
+
color: $FONT_COLOR_01;
|
|
64
|
+
font-weight: 500;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.ant-tabs-ink-bar {
|
|
70
|
+
display: none; // Hide the ink bar (bottom border indicator)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.ant-tabs-content-holder {
|
|
74
|
+
padding-top: 0.25rem; // Reduced from 0.5rem
|
|
75
|
+
padding-bottom: 0; // No bottom padding
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
.ant-tabs-content {
|
|
80
|
+
margin-top: $CAP_SPACE_08;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
&__tab-label {
|
|
85
|
+
display: flex;
|
|
86
|
+
align-items: center;
|
|
87
|
+
gap: 0.25rem;
|
|
88
|
+
|
|
89
|
+
&--warnings {
|
|
90
|
+
color: $CAP_G01;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
&__tab-count {
|
|
95
|
+
color: inherit;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
&__actions {
|
|
99
|
+
display: flex;
|
|
100
|
+
align-items: center;
|
|
101
|
+
flex-shrink: 0;
|
|
102
|
+
padding-top: 0.5rem;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
&__collapse-toggle {
|
|
106
|
+
display: flex;
|
|
107
|
+
align-items: center;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
width: 1.5rem;
|
|
110
|
+
height: 1.5rem;
|
|
111
|
+
padding: 0;
|
|
112
|
+
background: transparent;
|
|
113
|
+
border: none;
|
|
114
|
+
border-radius: 0.25rem;
|
|
115
|
+
cursor: pointer;
|
|
116
|
+
color: $CAP_G03;
|
|
117
|
+
transition: all 0.2s ease;
|
|
118
|
+
|
|
119
|
+
.cap-icon-v2 {
|
|
120
|
+
font-size: 0.875rem;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// When collapsed: hide tab content so only header (tabs + arrow) sticks to footer
|
|
125
|
+
&--collapsed {
|
|
126
|
+
.ant-tabs-content-holder,
|
|
127
|
+
.ant-tabs-content,
|
|
128
|
+
.ant-tabs-tabpane {
|
|
129
|
+
display: none !important;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
&__content {
|
|
134
|
+
height: 8rem; // Limit height for many errors
|
|
135
|
+
max-height: 12.5rem;
|
|
136
|
+
overflow-y: auto;
|
|
137
|
+
padding-bottom: 0; // Remove bottom padding completely
|
|
138
|
+
margin-left:2%;
|
|
139
|
+
|
|
140
|
+
// Custom scrollbar
|
|
141
|
+
&::-webkit-scrollbar {
|
|
142
|
+
width: 0.375rem;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
&::-webkit-scrollbar-track {
|
|
146
|
+
background: transparent;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
&::-webkit-scrollbar-thumb {
|
|
150
|
+
background-color: $CAP_G06;
|
|
151
|
+
border-radius: 0.1875rem;
|
|
152
|
+
|
|
153
|
+
&:hover {
|
|
154
|
+
background-color: $CAP_G04;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
&__item {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: flex-start;
|
|
162
|
+
padding: 0.1rem 0.5rem; // Reduced from 0.375rem
|
|
163
|
+
margin-bottom: 3px;
|
|
164
|
+
|
|
165
|
+
&:last-child {
|
|
166
|
+
border-bottom: none;
|
|
167
|
+
padding-bottom: 0.25rem; // Minimal padding on last item
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
&--error {
|
|
171
|
+
.validation-tabs__icon--error {
|
|
172
|
+
color: $CAP_RED;
|
|
173
|
+
}
|
|
174
|
+
.validation-tabs__item-location,
|
|
175
|
+
.validation-tabs__item-message {
|
|
176
|
+
color: $CAP_RED;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
&--warning {
|
|
181
|
+
.validation-tabs__icon--warning {
|
|
182
|
+
color: $CAP_G01;
|
|
183
|
+
}
|
|
184
|
+
.validation-tabs__item-message {
|
|
185
|
+
color: $CAP_G01;
|
|
186
|
+
}
|
|
187
|
+
.validation-tabs__item-location {
|
|
188
|
+
color: $CAP_G03;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
&__item-icon {
|
|
194
|
+
flex-shrink: 0;
|
|
195
|
+
display: flex;
|
|
196
|
+
align-items: center;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
&__icon {
|
|
200
|
+
font-size: 0.875rem;
|
|
201
|
+
|
|
202
|
+
&--error {
|
|
203
|
+
color: $CAP_RED;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
&--warning {
|
|
207
|
+
color: $CAP_G01;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
&__item-content {
|
|
212
|
+
flex: 1;
|
|
213
|
+
display: flex;
|
|
214
|
+
flex-wrap: wrap;
|
|
215
|
+
align-items: baseline;
|
|
216
|
+
gap: 0.25rem;
|
|
217
|
+
font-size: 0.75rem;
|
|
218
|
+
color: $CAP_G01;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
&__item-message {
|
|
222
|
+
color: $CAP_G01;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
&__item-location {
|
|
226
|
+
color: $CAP_G03;
|
|
227
|
+
white-space: nowrap;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
&__item-rule {
|
|
231
|
+
color: $CAP_G04;
|
|
232
|
+
font-family: monospace;
|
|
233
|
+
font-size: 0.6875rem;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
&__item-navigate {
|
|
237
|
+
flex-shrink: 0;
|
|
238
|
+
display: flex;
|
|
239
|
+
align-items: center;
|
|
240
|
+
width: 1.7rem;
|
|
241
|
+
height: 1.7rem;
|
|
242
|
+
padding: 0;
|
|
243
|
+
background: transparent;
|
|
244
|
+
border: none;
|
|
245
|
+
border-radius: 0.25rem;
|
|
246
|
+
cursor: pointer;
|
|
247
|
+
color: $CAP_G04;
|
|
248
|
+
transition: all 0.2s ease;
|
|
249
|
+
|
|
250
|
+
.anticon {
|
|
251
|
+
font-size: 1rem; // Increased icon size
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Responsive adjustments
|
|
257
|
+
@media (max-width: 768px) {
|
|
258
|
+
.validation-tabs {
|
|
259
|
+
padding: 0.375rem 0.5rem;
|
|
260
|
+
|
|
261
|
+
&__tabs {
|
|
262
|
+
.ant-tabs-tab {
|
|
263
|
+
margin-right: 1rem;
|
|
264
|
+
font-size: 0.8125rem;
|
|
265
|
+
font-weight: 400;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
&__content {
|
|
270
|
+
max-height: 10rem;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
&__item-content {
|
|
274
|
+
font-size: 0.6875rem;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ValidationTabs Component
|
|
3
|
+
*
|
|
4
|
+
* Displays validation issues in a tabbed interface with 2 categories:
|
|
5
|
+
* - Errors: Blocking issues (API errors, Rule Group #1, client-side Liquid errors)
|
|
6
|
+
* - Warnings: Non-blocking issues
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import React, { useState, useMemo, useEffect } from 'react';
|
|
10
|
+
import PropTypes from 'prop-types';
|
|
11
|
+
import { FormattedMessage, injectIntl, intlShape } from 'react-intl';
|
|
12
|
+
|
|
13
|
+
// Cap UI Library
|
|
14
|
+
import CapRow from '@capillarytech/cap-ui-library/CapRow';
|
|
15
|
+
import CapIcon from '@capillarytech/cap-ui-library/CapIcon';
|
|
16
|
+
import CapTooltip from '@capillarytech/cap-ui-library/CapTooltip';
|
|
17
|
+
|
|
18
|
+
// Messages
|
|
19
|
+
import messages from './messages';
|
|
20
|
+
import { BLOCKING_ERROR_RULE_IDS, VALIDATION_SEVERITY } from '../../constants';
|
|
21
|
+
|
|
22
|
+
// Styles
|
|
23
|
+
import './_validationTabs.scss';
|
|
24
|
+
import { StyledCapTab } from '../../../../v2Containers/MobilePushNew/style';
|
|
25
|
+
import { ISSUE_SOURCES } from '../../utils/validationConstants';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Group issues into Errors (blocking) and Warnings (non-blocking)
|
|
29
|
+
*/
|
|
30
|
+
const groupByErrorsAndWarnings = (allIssues) => {
|
|
31
|
+
const errors = [];
|
|
32
|
+
const warnings = [];
|
|
33
|
+
|
|
34
|
+
allIssues.forEach((issue) => {
|
|
35
|
+
if (isBlockingError(issue)) {
|
|
36
|
+
errors.push(issue);
|
|
37
|
+
} else {
|
|
38
|
+
warnings.push(issue);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
return { errors, warnings };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Check if an issue is a blocking error (API error, Rule Group #1, or client-side Liquid validation errors)
|
|
47
|
+
*/
|
|
48
|
+
const isBlockingError = (issue) => {
|
|
49
|
+
const { rule, source, severity } = issue || {};
|
|
50
|
+
// API errors are blocking
|
|
51
|
+
if (rule === 'liquid-api-validation' || rule === 'standard-api-validation') {
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
// Client-side Liquid validation errors are blocking (genuine syntax errors)
|
|
55
|
+
if (source === ISSUE_SOURCES.LIQUID && severity === VALIDATION_SEVERITY.ERROR) {
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
// Rule Group #1 errors are blocking
|
|
59
|
+
if (BLOCKING_ERROR_RULE_IDS.includes(rule)) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
return false;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* ValidationTabContent - Renders the content for each tab
|
|
67
|
+
*/
|
|
68
|
+
const ValidationTabContent = ({
|
|
69
|
+
issues,
|
|
70
|
+
onErrorClick,
|
|
71
|
+
onExpand,
|
|
72
|
+
}) => {
|
|
73
|
+
if (!issues || issues.length === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const handleNavigateClick = (issue, e) => {
|
|
78
|
+
e.stopPropagation();
|
|
79
|
+
// Expand panel when redirection is clicked so it does not stay stuck to footer
|
|
80
|
+
if (onExpand) {
|
|
81
|
+
onExpand();
|
|
82
|
+
}
|
|
83
|
+
if (onErrorClick) {
|
|
84
|
+
// Always call onErrorClick to acknowledge the error (enables buttons)
|
|
85
|
+
// If line number exists, navigate to it; otherwise just acknowledge and focus editor
|
|
86
|
+
onErrorClick({
|
|
87
|
+
line: issue.line || 1, // Default to line 1 if no line number (for API errors)
|
|
88
|
+
column: issue.column || 1,
|
|
89
|
+
message: issue.message,
|
|
90
|
+
severity: issue.severity,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="validation-tabs__content">
|
|
97
|
+
{issues.map((issue, index) => {
|
|
98
|
+
const {
|
|
99
|
+
message, line, column,
|
|
100
|
+
} = issue;
|
|
101
|
+
const key = `${message}-${line}-${column}-${index}`;
|
|
102
|
+
const isBlocking = isBlockingError(issue);
|
|
103
|
+
const displaySeverity = isBlocking ? 'error' : 'warning';
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<div
|
|
107
|
+
key={key}
|
|
108
|
+
className={`validation-tabs__item validation-tabs__item--${displaySeverity}`}
|
|
109
|
+
>
|
|
110
|
+
<div className="validation-tabs__item-content">
|
|
111
|
+
<span className="validation-tabs__item-message">
|
|
112
|
+
{message}
|
|
113
|
+
</span>
|
|
114
|
+
{line && (
|
|
115
|
+
<span className="validation-tabs__item-location">
|
|
116
|
+
<FormattedMessage
|
|
117
|
+
{...messages.lineChar}
|
|
118
|
+
values={{ line, char: column || 1 }}
|
|
119
|
+
/>
|
|
120
|
+
</span>
|
|
121
|
+
)}
|
|
122
|
+
</div>
|
|
123
|
+
{/* Always show redirection icon for errors (even API errors without line numbers) */}
|
|
124
|
+
{/* Clicking it acknowledges the error and enables buttons */}
|
|
125
|
+
<CapTooltip title={line ? `Line ${line}, Char ${column || 1}` : 'Click to acknowledge error and focus editor'}>
|
|
126
|
+
<button
|
|
127
|
+
type="button"
|
|
128
|
+
className="validation-tabs__item-navigate"
|
|
129
|
+
onClick={(e) => handleNavigateClick(issue, e)}
|
|
130
|
+
aria-label={line ? `Line ${line}, Char ${column || 1}` : 'Acknowledge error'}
|
|
131
|
+
>
|
|
132
|
+
<CapIcon type="redirection" />
|
|
133
|
+
</button>
|
|
134
|
+
</CapTooltip>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
})}
|
|
138
|
+
</div>
|
|
139
|
+
);
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
ValidationTabContent.propTypes = {
|
|
143
|
+
issues: PropTypes.array,
|
|
144
|
+
onErrorClick: PropTypes.func,
|
|
145
|
+
onExpand: PropTypes.func,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
ValidationTabContent.defaultProps = {
|
|
149
|
+
issues: [],
|
|
150
|
+
onErrorClick: null,
|
|
151
|
+
onExpand: null,
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* ValidationTabs Component
|
|
156
|
+
*/
|
|
157
|
+
const ValidationTabs = ({
|
|
158
|
+
intl,
|
|
159
|
+
validation,
|
|
160
|
+
onErrorClick,
|
|
161
|
+
isCollapsed = false,
|
|
162
|
+
onToggleCollapse,
|
|
163
|
+
onExpand,
|
|
164
|
+
className,
|
|
165
|
+
}) => {
|
|
166
|
+
const [activeKey, setActiveKey] = useState('errors');
|
|
167
|
+
|
|
168
|
+
// Group issues into Errors (blocking) and Warnings (non-blocking)
|
|
169
|
+
const { errors, warnings } = useMemo(() => {
|
|
170
|
+
if (!validation) {
|
|
171
|
+
return { errors: [], warnings: [] };
|
|
172
|
+
}
|
|
173
|
+
const allIssues = validation.getAllIssues ? validation.getAllIssues() : [];
|
|
174
|
+
return groupByErrorsAndWarnings(allIssues);
|
|
175
|
+
}, [validation]);
|
|
176
|
+
|
|
177
|
+
const errorsCount = errors.length;
|
|
178
|
+
const warningsCount = warnings.length;
|
|
179
|
+
const totalCount = errorsCount + warningsCount;
|
|
180
|
+
|
|
181
|
+
// Default active tab: errors if any, else warnings
|
|
182
|
+
useEffect(() => {
|
|
183
|
+
if (errorsCount > 0) {
|
|
184
|
+
setActiveKey('errors');
|
|
185
|
+
} else if (warningsCount > 0) {
|
|
186
|
+
setActiveKey('warnings');
|
|
187
|
+
}
|
|
188
|
+
}, [errorsCount, warningsCount]);
|
|
189
|
+
|
|
190
|
+
if (totalCount === 0) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const tabPanes = [];
|
|
195
|
+
|
|
196
|
+
if (errorsCount > 0) {
|
|
197
|
+
tabPanes.push({
|
|
198
|
+
key: 'errors',
|
|
199
|
+
tab: (
|
|
200
|
+
<CapTooltip title={`${intl.formatMessage(messages.errors)} (${errorsCount})`}>
|
|
201
|
+
<span className="validation-tabs__tab-label validation-tabs__tab-label--errors">
|
|
202
|
+
<FormattedMessage {...messages.errors} />
|
|
203
|
+
<span className="validation-tabs__tab-count">({errorsCount})</span>
|
|
204
|
+
</span>
|
|
205
|
+
</CapTooltip>
|
|
206
|
+
),
|
|
207
|
+
content: (
|
|
208
|
+
<ValidationTabContent
|
|
209
|
+
issues={errors}
|
|
210
|
+
onErrorClick={onErrorClick}
|
|
211
|
+
onExpand={onExpand}
|
|
212
|
+
/>
|
|
213
|
+
),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (warningsCount > 0) {
|
|
218
|
+
tabPanes.push({
|
|
219
|
+
key: 'warnings',
|
|
220
|
+
tab: (
|
|
221
|
+
<CapTooltip title={`${intl.formatMessage(messages.warnings)} (${warningsCount})`}>
|
|
222
|
+
<span className="validation-tabs__tab-label validation-tabs__tab-label--warnings">
|
|
223
|
+
<FormattedMessage {...messages.warnings} />
|
|
224
|
+
<span className="validation-tabs__tab-count">({warningsCount})</span>
|
|
225
|
+
</span>
|
|
226
|
+
</CapTooltip>
|
|
227
|
+
),
|
|
228
|
+
content: (
|
|
229
|
+
<ValidationTabContent
|
|
230
|
+
issues={warnings}
|
|
231
|
+
onErrorClick={onErrorClick}
|
|
232
|
+
onExpand={onExpand}
|
|
233
|
+
/>
|
|
234
|
+
),
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const handleToggleCollapse = () => {
|
|
239
|
+
if (onToggleCollapse) {
|
|
240
|
+
onToggleCollapse();
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const collapseLabel = isCollapsed
|
|
245
|
+
? intl.formatMessage(messages.expandPanel)
|
|
246
|
+
: intl.formatMessage(messages.collapsePanel);
|
|
247
|
+
|
|
248
|
+
return (
|
|
249
|
+
<div className={`validation-tabs ${isCollapsed ? 'validation-tabs--collapsed' : ''} ${className || ''}`}>
|
|
250
|
+
<CapRow className="validation-tabs__header">
|
|
251
|
+
<StyledCapTab
|
|
252
|
+
className="validation-tabs__tabs"
|
|
253
|
+
activeKey={activeKey || (tabPanes[0]?.key)}
|
|
254
|
+
onChange={setActiveKey}
|
|
255
|
+
panes={tabPanes}
|
|
256
|
+
/>
|
|
257
|
+
<CapRow className="validation-tabs__actions">
|
|
258
|
+
<CapTooltip title={collapseLabel}>
|
|
259
|
+
<button
|
|
260
|
+
type="button"
|
|
261
|
+
className="validation-tabs__collapse-toggle"
|
|
262
|
+
onClick={handleToggleCollapse}
|
|
263
|
+
aria-label={collapseLabel}
|
|
264
|
+
>
|
|
265
|
+
<CapIcon type={isCollapsed ? 'chevron-up' : 'chevron-down'} />
|
|
266
|
+
</button>
|
|
267
|
+
</CapTooltip>
|
|
268
|
+
</CapRow>
|
|
269
|
+
</CapRow>
|
|
270
|
+
</div>
|
|
271
|
+
);
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
ValidationTabs.propTypes = {
|
|
275
|
+
intl: intlShape.isRequired,
|
|
276
|
+
validation: PropTypes.shape({
|
|
277
|
+
getAllIssues: PropTypes.func,
|
|
278
|
+
}),
|
|
279
|
+
onErrorClick: PropTypes.func,
|
|
280
|
+
isCollapsed: PropTypes.bool,
|
|
281
|
+
onToggleCollapse: PropTypes.func,
|
|
282
|
+
onExpand: PropTypes.func,
|
|
283
|
+
className: PropTypes.string,
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
ValidationTabs.defaultProps = {
|
|
287
|
+
validation: null,
|
|
288
|
+
onErrorClick: null,
|
|
289
|
+
isCollapsed: false,
|
|
290
|
+
onToggleCollapse: null,
|
|
291
|
+
onExpand: null,
|
|
292
|
+
className: '',
|
|
293
|
+
};
|
|
294
|
+
|
|
295
|
+
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 (Errors = blocking, Warnings = non-blocking)
|
|
13
|
+
errors: {
|
|
14
|
+
id: `${scope}.errors`,
|
|
15
|
+
defaultMessage: 'Errors',
|
|
16
|
+
},
|
|
17
|
+
warnings: {
|
|
18
|
+
id: `${scope}.warnings`,
|
|
19
|
+
defaultMessage: 'Warnings',
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
// Error item labels
|
|
23
|
+
syntaxError: {
|
|
24
|
+
id: `${scope}.syntaxError`,
|
|
25
|
+
defaultMessage: 'Syntax Error',
|
|
26
|
+
},
|
|
27
|
+
lineChar: {
|
|
28
|
+
id: `${scope}.lineChar`,
|
|
29
|
+
defaultMessage: 'Line {line}, Char {char}.',
|
|
30
|
+
},
|
|
31
|
+
|
|
32
|
+
// Tooltips
|
|
33
|
+
navigateToError: {
|
|
34
|
+
id: `${scope}.navigateToError`,
|
|
35
|
+
defaultMessage: 'Go to error location',
|
|
36
|
+
},
|
|
37
|
+
collapsePanel: {
|
|
38
|
+
id: `${scope}.collapsePanel`,
|
|
39
|
+
defaultMessage: 'Collapse validation panel',
|
|
40
|
+
},
|
|
41
|
+
expandPanel: {
|
|
42
|
+
id: `${scope}.expandPanel`,
|
|
43
|
+
defaultMessage: 'Expand validation panel',
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
// Liquid documentation
|
|
47
|
+
liquidDocumentation: {
|
|
48
|
+
id: `${scope}.liquidDocumentation`,
|
|
49
|
+
defaultMessage: 'Liquid documentation',
|
|
50
|
+
},
|
|
51
|
+
});
|