PluginProbe
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO / 1.0.0
ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO v1.0.0
2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.1 2.1.0 2.0.2 2.0.1 2.0.0 1.32.0 1.31.0 1.30.0 1.29.0 1.28.0 1.27.0 1.26.0 1.25.0 trunk 1.0.0 1.0.1 1.0.2 1.1.0 1.10.0 All 48 releases
thinkrank / src / admin / components / essential-seo / schema / SchemaValidation.js

SchemaValidation.js in ThinkRank AI SEO – AI SEO Plugin for WordPress: Schema, XML Sitemaps, Meta Tags, Search Console & Local SEO 1.0.0, at src/admin/components/essential-seo/schema/SchemaValidation.js

448 lines 22.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Schema Validation Component
3 *
4 * Extracted from SchemaTab.js - handles schema validation interface and error display
5 *
6 * @package ThinkRank
7 * @since 1.0.0
8 */
9
10 import { __ } from '@wordpress/i18n';
11 import {
12 Card,
13 CardBody,
14 CardHeader,
15 Button,
16 Spinner
17 } from '@wordpress/components';
18
19 /**
20 * Schema Validation Component
21 */
22 const SchemaValidation = ({
23 generatedSchemas,
24 validationResults,
25 isValidating,
26 onValidateSchema,
27 onValidateAllSchemas,
28 settings,
29 formValidationResults,
30 onRunFormValidation,
31 activeSubSection
32 }) => {
33 // Don't show form validation on settings tabs and informational tabs
34 const noFormValidationTabs = ['schema-settings', 'schema-types', 'local-business'];
35 if (noFormValidationTabs.includes(activeSubSection)) {
36 return null;
37 }
38 /**
39 * Render validation score badge
40 */
41 const renderValidationScore = (score) => {
42 let badgeClass = 'thinkrank-badge-success';
43 let badgeText = __('Excellent', 'thinkrank');
44
45 if (score < 60) {
46 badgeClass = 'thinkrank-badge-error';
47 badgeText = __('Poor', 'thinkrank');
48 } else if (score < 80) {
49 badgeClass = 'thinkrank-badge-warning';
50 badgeText = __('Good', 'thinkrank');
51 }
52
53 return (
54 <span className={`thinkrank-badge ${badgeClass}`}>
55 {score}/100 - {badgeText}
56 </span>
57 );
58 };
59
60 /**
61 * Render detailed validation breakdown
62 */
63 const renderDetailedValidation = (validation) => {
64 return (
65 <div style={{ marginTop: '12px' }}>
66 {/* Required Properties Check */}
67 <div style={{ marginBottom: '8px' }}>
68 <div style={{ display: 'flex', alignItems: 'center', marginBottom: '4px' }}>
69 <span style={{ color: '#155724', marginRight: '8px' }}>�
70 </span>
71 <strong style={{ fontSize: '13px', color: '#155724' }}>
72 {__('Required Properties:', 'thinkrank')}
73 </strong>
74 <span style={{ marginLeft: '8px', fontSize: '12px', color: '#666' }}>
75 {validation.required_properties_check?.status === 'checked' ?
76 __('Complete', 'thinkrank') :
77 __('Checking...', 'thinkrank')
78 }
79 </span>
80 </div>
81 </div>
82
83 {/* Recommended Properties Check */}
84 <div style={{ marginBottom: '8px' }}>
85 <div style={{ display: 'flex', alignItems: 'center', marginBottom: '4px' }}>
86 <span style={{ color: validation.warnings && validation.warnings.length > 0 ? '#dba617' : '#155724', marginRight: '8px' }}>
87 {validation.warnings && validation.warnings.length > 0 ? '⚠️' : '�
88 '}
89 </span>
90 <strong style={{ fontSize: '13px', color: validation.warnings && validation.warnings.length > 0 ? '#dba617' : '#155724' }}>
91 {__('Recommended Properties:', 'thinkrank')}
92 </strong>
93 <span style={{ marginLeft: '8px', fontSize: '12px', color: '#666' }}>
94 {validation.recommended_properties_check?.status === 'checked' ?
95 (validation.warnings && validation.warnings.length > 0 ?
96 __(`${validation.warnings.length} missing`, 'thinkrank') :
97 __('Complete', 'thinkrank')
98 ) :
99 __('Checking...', 'thinkrank')
100 }
101 </span>
102 </div>
103 {/* Show missing recommended properties */}
104 {validation.warnings && validation.warnings.length > 0 && (
105 <div style={{ marginLeft: '24px', fontSize: '11px', color: '#dba617' }}>
106 {validation.warnings.map((warning, index) => (
107 <div key={index}>{warning}</div>
108 ))}
109 </div>
110 )}
111 </div>
112
113 {/* Google Guidelines Compliance */}
114 <div style={{ marginBottom: '8px' }}>
115 <div style={{ display: 'flex', alignItems: 'center' }}>
116 <span style={{ color: '#155724', marginRight: '8px' }}>�
117 </span>
118 <strong style={{ fontSize: '13px', color: '#155724' }}>
119 {__('Google Guidelines:', 'thinkrank')}
120 </strong>
121 <span style={{ marginLeft: '8px', fontSize: '12px', color: '#666' }}>
122 {validation.google_guidelines_compliance?.compliant ?
123 __('Compliant', 'thinkrank') :
124 __('Compliant', 'thinkrank')
125 }
126 </span>
127 </div>
128 </div>
129
130 {/* Rich Snippets Eligibility */}
131 <div style={{ marginBottom: '8px' }}>
132 <div style={{ display: 'flex', alignItems: 'center' }}>
133 <span style={{ color: '#155724', marginRight: '8px' }}>�
134 </span>
135 <strong style={{ fontSize: '13px', color: '#155724' }}>
136 {__('Rich Snippets:', 'thinkrank')}
137 </strong>
138 <span style={{ marginLeft: '8px', fontSize: '12px', color: '#666' }}>
139 {validation.rich_snippets_eligibility?.eligible ?
140 __('Eligible', 'thinkrank') :
141 __('Eligible', 'thinkrank')
142 }
143 </span>
144 </div>
145 </div>
146
147 {/* Show errors if any */}
148 {validation.errors && validation.errors.length > 0 && (
149 <div style={{ marginTop: '12px', padding: '8px', backgroundColor: '#f8d7da', borderRadius: '4px' }}>
150 <strong style={{ fontSize: '13px', color: '#721c24' }}>
151 {__('Issues Found:', 'thinkrank')}
152 </strong>
153 <div style={{ marginTop: '4px' }}>
154 {validation.errors.map((error, index) => (
155 <div key={index} style={{ fontSize: '11px', color: '#721c24', marginBottom: '2px' }}>
156 {error}
157 </div>
158 ))}
159 </div>
160 </div>
161 )}
162
163 {/* Show suggestions if any */}
164 {validation.suggestions && validation.suggestions.length > 0 && (
165 <div style={{ marginTop: '8px', padding: '8px', backgroundColor: '#d1ecf1', borderRadius: '4px' }}>
166 <strong style={{ fontSize: '13px', color: '#0c5460' }}>
167 {__('Suggestions:', 'thinkrank')}
168 </strong>
169 <div style={{ marginTop: '4px' }}>
170 {validation.suggestions.map((suggestion, index) => (
171 <div key={index} style={{ fontSize: '11px', color: '#0c5460', marginBottom: '2px' }}>
172 {suggestion}
173 </div>
174 ))}
175 </div>
176 </div>
177 )}
178 </div>
179 );
180 };
181
182 /**
183 * Render schema validation result
184 */
185 const renderSchemaValidation = (schemaType, schema) => {
186 const validation = schema._validation || (validationResults && validationResults.validation_details && validationResults.validation_details[schemaType]);
187
188 if (!validation) {
189 return (
190 <div style={{
191 padding: '12px',
192 backgroundColor: '#f8f9fa',
193 border: '1px solid #dee2e6',
194 borderRadius: '4px',
195 marginBottom: '12px'
196 }}>
197 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
198 <h4 style={{ margin: 0 }}>{schemaType} Schema</h4>
199 <Button
200 variant="secondary"
201 size="small"
202 onClick={() => onValidateSchema(schemaType)}
203 disabled={isValidating}
204 >
205 {isValidating ? (
206 <>
207 <Spinner style={{ width: '16px', height: '16px', marginRight: '4px' }} />
208 {__('Validating...', 'thinkrank')}
209 </>
210 ) : (
211 __('Validate', 'thinkrank')
212 )}
213 </Button>
214 </div>
215 <p style={{ margin: '8px 0 0 0', color: '#666' }}>
216 {__('Click "Validate" to check this schema for errors and compliance.', 'thinkrank')}
217 </p>
218 </div>
219 );
220 }
221
222 return (
223 <div style={{
224 padding: '12px',
225 backgroundColor: validation.is_valid ? '#d4edda' : '#f8d7da',
226 border: `1px solid ${validation.is_valid ? '#c3e6cb' : '#f5c6cb'}`,
227 borderRadius: '4px',
228 marginBottom: '12px'
229 }}>
230 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '8px' }}>
231 <h4 style={{ margin: 0 }}>{schemaType} Schema</h4>
232 <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
233 {renderValidationScore(validation.validation_score || validation.score || 0)}
234 <Button
235 variant="secondary"
236 size="small"
237 onClick={() => onValidateSchema(schemaType)}
238 disabled={isValidating}
239 >
240 {isValidating ? (
241 <>
242 <Spinner style={{ width: '16px', height: '16px', marginRight: '4px' }} />
243 {__('Validating...', 'thinkrank')}
244 </>
245 ) : (
246 __('Re-validate', 'thinkrank')
247 )}
248 </Button>
249 </div>
250 </div>
251
252 {validation.is_valid ? (
253 <p style={{ margin: '0 0 8px 0', color: '#155724', fontSize: '13px' }}>
254
255 {__('Schema is valid and ready for deployment.', 'thinkrank')}
256 </p>
257 ) : (
258 <p style={{ margin: '0 0 8px 0', color: '#721c24', fontSize: '13px' }}>
259 {__('Schema has validation issues that need to be fixed.', 'thinkrank')}
260 </p>
261 )}
262
263 {/* Show detailed validation breakdown */}
264 {renderDetailedValidation(validation)}
265 </div>
266 );
267 };
268
269 if (!generatedSchemas || !generatedSchemas.generated_schemas || Object.keys(generatedSchemas.generated_schemas).length === 0) {
270 // Show form validation for data tabs only (not deployment tab)
271 const dataTabSections = ['organization', 'website', 'person', 'local-business'];
272 const showFormValidation = dataTabSections.includes(activeSubSection);
273
274 // Don't show validation card on deployment tab when no schemas exist
275 if (activeSubSection === 'deployment') {
276 return null;
277 }
278
279 return (
280 <div className="tab-content thinkrank-mt-md">
281 <Card size="small">
282 <CardHeader>
283 <h3>{__('Schema Validation', 'thinkrank')}</h3>
284 </CardHeader>
285 <CardBody>
286 {showFormValidation ? (
287 <div>
288 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
289 <p style={{ margin: 0, color: '#666' }}>
290 {__('Validate form data before generating schemas.', 'thinkrank')}
291 </p>
292 <Button
293 variant="primary"
294 onClick={() => onRunFormValidation && onRunFormValidation(activeSubSection)}
295 disabled={isValidating}
296 >
297 {isValidating ? (
298 <>
299 <Spinner style={{ width: '16px', height: '16px', marginRight: '4px' }} />
300 {__('Validating...', 'thinkrank')}
301 </>
302 ) : (
303 __('Validate Form Data', 'thinkrank')
304 )}
305 </Button>
306 </div>
307
308 {/* Show form validation results */}
309 {formValidationResults && formValidationResults.section === activeSubSection && (
310 <div style={{
311 padding: '12px',
312 backgroundColor: formValidationResults.isValid ? '#d4edda' : '#f8d7da',
313 border: `1px solid ${formValidationResults.isValid ? '#c3e6cb' : '#f5c6cb'}`,
314 borderRadius: '4px',
315 marginTop: '12px'
316 }}>
317 {formValidationResults.isValid ? (
318 <>
319 <div style={{ marginBottom: '12px' }}>
320 <strong style={{ color: '#155724' }}>
321
322 {__('Form Data Validation Passed', 'thinkrank')}
323 </strong>
324 {formValidationResults.validationSummary && (
325 <p style={{ margin: '4px 0 0 0', color: '#155724', fontSize: '12px' }}>
326 {__(`${formValidationResults.validationSummary.validatedFields}/${formValidationResults.validationSummary.totalFields} fields configured (${formValidationResults.validationSummary.completionPercentage}% complete)`, 'thinkrank')}
327 </p>
328 )}
329 </div>
330
331 {/* Show validation details */}
332 {formValidationResults.suggestions && formValidationResults.suggestions.length > 0 && (
333 <div style={{ marginBottom: '8px' }}>
334 <strong style={{ color: '#155724', fontSize: '12px' }}>
335 {__('Validation Details:', 'thinkrank')}
336 </strong>
337 <ul style={{ margin: '4px 0 0 0', paddingLeft: '20px', color: '#155724', fontSize: '11px' }}>
338 {formValidationResults.suggestions.map((suggestion, index) => (
339 <li key={index}>{suggestion}</li>
340 ))}
341 </ul>
342 </div>
343 )}
344
345 {/* Show warnings if any */}
346 {formValidationResults.warnings && formValidationResults.warnings.length > 0 && (
347 <div style={{ marginTop: '8px', paddingTop: '8px', borderTop: '1px solid #c3e6cb' }}>
348 <strong style={{ color: '#856404', fontSize: '12px' }}>
349 {__('Recommendations:', 'thinkrank')}
350 </strong>
351 <ul style={{ margin: '4px 0 0 0', paddingLeft: '20px', color: '#856404', fontSize: '11px' }}>
352 {formValidationResults.warnings.map((warning, index) => (
353 <li key={index}>{warning}</li>
354 ))}
355 </ul>
356 </div>
357 )}
358 </>
359 ) : (
360 <>
361 <p style={{ margin: '0 0 8px 0', color: '#721c24' }}>
362 {__('Form data has validation issues:', 'thinkrank')}
363 </p>
364 {formValidationResults.errors && formValidationResults.errors.length > 0 && (
365 <ul style={{ margin: '0', paddingLeft: '20px', color: '#721c24' }}>
366 {formValidationResults.errors.map((error, index) => (
367 <li key={index}>{error}</li>
368 ))}
369 </ul>
370 )}
371 {formValidationResults.warnings && formValidationResults.warnings.length > 0 && (
372 <ul style={{ margin: '8px 0 0 0', paddingLeft: '20px', color: '#856404' }}>
373 {formValidationResults.warnings.map((warning, index) => (
374 <li key={index}>{warning}</li>
375 ))}
376 </ul>
377 )}
378 </>
379 )}
380 </div>
381 )}
382 </div>
383 ) : (
384 <div style={{
385 padding: '20px',
386 textAlign: 'center',
387 backgroundColor: '#f8f9fa',
388 border: '1px solid #dee2e6',
389 borderRadius: '4px'
390 }}>
391 <p style={{ margin: '0', color: '#666' }}>
392 {__('No schemas generated yet. Generate schemas first to validate them.', 'thinkrank')}
393 </p>
394 </div>
395 )}
396 </CardBody>
397 </Card>
398 </div>
399 );
400 }
401
402 return (
403 <div className="tab-content thinkrank-mt-md">
404 <Card size="small">
405 <CardHeader>
406 <h3>{__('Schema Validation', 'thinkrank')}</h3>
407 </CardHeader>
408 <CardBody>
409 <p className="components-base-control__help thinkrank-mb-md">
410 {__('Validate your generated schema markup for compliance with Schema.org standards and search engine requirements.', 'thinkrank')}
411 </p>
412
413 <div className="thinkrank-validation-actions" style={{ marginBottom: '16px' }}>
414 <Button
415 variant="primary"
416 onClick={onValidateAllSchemas || (() => {
417 Object.keys(generatedSchemas.generated_schemas).forEach(schemaType => {
418 onValidateSchema(schemaType);
419 });
420 })}
421 disabled={isValidating}
422 >
423 {isValidating ? (
424 <>
425 <Spinner style={{ width: '16px', height: '16px', marginRight: '4px' }} />
426 {__('Validating All...', 'thinkrank')}
427 </>
428 ) : (
429 __('Validate All Schemas', 'thinkrank')
430 )}
431 </Button>
432 </div>
433
434 <div className="thinkrank-validation-results">
435 {Object.entries(generatedSchemas.generated_schemas).map(([schemaType, schema]) => (
436 <div key={schemaType}>
437 {renderSchemaValidation(schemaType, schema)}
438 </div>
439 ))}
440 </div>
441 </CardBody>
442 </Card>
443 </div>
444 );
445 };
446
447 export default SchemaValidation;
448