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 / PerformanceTab.js

PerformanceTab.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/PerformanceTab.js

1,482 lines 67.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 /**
2 * Performance Tab Component
3 *
4 * Core Web Vitals monitoring and SEO performance insights.
5 * Leverages the existing Performance Monitoring Manager for comprehensive
6 * performance tracking and analysis.
7 *
8 * Features:
9 * - Core Web Vitals dashboard (LCP, FID, CLS, INP)
10 * - SEO performance correlation
11 * - Historical tracking
12 * - Performance recommendations
13 *
14 * @package ThinkRank
15 * @since 1.0.0
16 */
17
18 import { __ } from '@wordpress/i18n';
19 import { useState, useEffect, memo, useMemo, useCallback } from '@wordpress/element';
20 import {
21 Card,
22 CardHeader,
23 CardBody,
24 Button,
25 Spinner,
26 Notice,
27 Flex,
28 FlexItem,
29 __experimentalGrid as Grid,
30 __experimentalText as Text,
31 __experimentalHeading as Heading,
32 __experimentalSpacer as Spacer,
33 ProgressBar
34 } from '@wordpress/components';
35 import apiFetch from '@wordpress/api-fetch';
36 import PerformanceChart from '../common/PerformanceChart';
37
38 /**
39 * Performance Tab Component
40 */
41 const PerformanceTab = ({ activeSubSection = 'core-web-vitals', onNavigate }) => {
42 const [performanceData, setPerformanceData] = useState(null);
43 const [isLoading, setIsLoading] = useState(false);
44 const [notice, setNotice] = useState(null);
45 const [lastUpdated, setLastUpdated] = useState(null);
46 const [recommendations, setRecommendations] = useState(null);
47 const [historicalData, setHistoricalData] = useState(null);
48 const [deviceType, setDeviceType] = useState('mobile'); // Mobile-first approach
49 const [historicalPeriod, setHistoricalPeriod] = useState(28); // Default to 28 days
50
51 // Opportunities state
52 const [opportunities, setOpportunities] = useState([]);
53 const [isLoadingOpportunities, setIsLoadingOpportunities] = useState(false);
54 const [opportunitiesError, setOpportunitiesError] = useState(null);
55
56 // Diagnostics state
57 const [diagnostics, setDiagnostics] = useState([]);
58 const [isLoadingDiagnostics, setIsLoadingDiagnostics] = useState(false);
59 const [diagnosticsError, setDiagnosticsError] = useState(null);
60
61 /**
62 * Load performance data
63 * Phase 5: Enhanced error handling for Google API integration
64 */
65 const loadPerformanceData = async () => {
66 try {
67 setIsLoading(true);
68 setNotice(null);
69
70 const response = await apiFetch({
71 path: '/thinkrank/v1/performance/monitor',
72 method: 'GET'
73 });
74
75 if (response.success) {
76 setPerformanceData(response.data);
77 setLastUpdated(new Date().toLocaleString());
78
79 // Check if data contains API configuration errors
80 if (response.data?.core_web_vitals?.error) {
81 setNotice({
82 status: 'warning',
83 message: response.data.core_web_vitals.message || __('Google PageSpeed Insights API key is required. Please configure it in Integrations > Google Services to view real performance data.', 'thinkrank')
84 });
85 } else {
86 setNotice({
87 status: 'success',
88 message: __('Performance data loaded successfully', 'thinkrank')
89 });
90 }
91 } else {
92 throw new Error(response.error || 'Failed to load performance data');
93 }
94 } catch (error) {
95 // Enhanced error handling for specific API issues
96 if (error.message.includes('API key is required') || error.message.includes('not configured')) {
97 setNotice({
98 status: 'warning',
99 message: __('Google PageSpeed Insights API key is required. Please configure it in Integrations > Google Services to view real performance data.', 'thinkrank')
100 });
101 } else if (error.message.includes('rate limit exceeded')) {
102 setNotice({
103 status: 'warning',
104 message: __('Google API rate limit exceeded. Please try again later.', 'thinkrank')
105 });
106 } else if (error.message.includes('invalid API key') || error.message.includes('unauthorized')) {
107 setNotice({
108 status: 'error',
109 message: __('Invalid Google PageSpeed Insights API key. Please check your API configuration in Analytics > Platforms.', 'thinkrank')
110 });
111 } else {
112 setNotice({
113 status: 'error',
114 message: __('Failed to load performance data. Please check your API configuration in Integrations > Google Services.', 'thinkrank')
115 });
116 }
117
118 // Set empty performance data instead of showing mock data
119 setPerformanceData(null);
120 } finally {
121 setIsLoading(false);
122 }
123 };
124
125 /**
126 * Load recommendations data
127 */
128 const loadRecommendations = async () => {
129 try {
130 const response = await apiFetch({
131 path: '/thinkrank/v1/performance/recommendations',
132 method: 'GET'
133 });
134
135 if (response.success) {
136 setRecommendations(response.data);
137 }
138 } catch (error) {
139 // Silently fail for recommendations - not critical
140 }
141 };
142
143 /**
144 * Load historical data
145 */
146 const loadHistoricalData = async () => {
147 try {
148 const response = await apiFetch({
149 path: '/thinkrank/v1/performance/history?days=30',
150 method: 'GET'
151 });
152
153 if (response.success) {
154 setHistoricalData(response.data);
155 }
156 } catch (error) {
157 // Silently fail for historical data - not critical
158 }
159 };
160
161 /**
162 * Get status color for Core Web Vitals
163 */
164 const getVitalStatus = (value, thresholds) => {
165 if (value <= thresholds.good) return 'good';
166 if (value <= thresholds.needs_improvement) return 'needs-improvement';
167 return 'poor';
168 };
169
170 /**
171 * Get status color class
172 */
173 const getStatusColor = (status) => {
174 switch (status) {
175 case 'good': return '#00a32a';
176 case 'needs-improvement': return '#dba617';
177 case 'poor': return '#d63638';
178 default: return '#50575e';
179 }
180 };
181
182 /**
183 * Render Core Web Vitals card
184 * Phase 5: Enhanced error handling for missing data
185 */
186 const renderVitalCard = (vital, data) => {
187 if (!data) return null;
188
189 // Handle error states for individual metrics
190 if (data.status === 'unknown' || data.value === 0) {
191 return (
192 <Card key={vital} size="small">
193 <CardHeader>
194 <Flex justify="space-between" align="center">
195 <Heading level={4}>{data.name}</Heading>
196 <div
197 style={{
198 padding: '4px 8px',
199 borderRadius: '4px',
200 backgroundColor: '#666',
201 color: 'white',
202 fontSize: '12px',
203 fontWeight: 'bold',
204 textTransform: 'uppercase'
205 }}
206 >
207 {__('NO DATA', 'thinkrank')}
208 </div>
209 </Flex>
210 </CardHeader>
211 <CardBody>
212 <div style={{ textAlign: 'center', padding: '20px' }}>
213 <div style={{ fontSize: '24px', color: '#666', marginBottom: '8px' }}></div>
214 <Text variant="muted" style={{ fontSize: '14px' }}>
215 {__('Data not available', 'thinkrank')}
216 </Text>
217 </div>
218 </CardBody>
219 </Card>
220 );
221 }
222
223 const status = getVitalStatus(data.value, {
224 good: data.good_threshold,
225 needs_improvement: data.needs_improvement_threshold
226 });
227
228 return (
229 <Card key={vital} size="small">
230 <CardHeader>
231 <Flex justify="space-between" align="center">
232 <Heading level={4}>{data.name}</Heading>
233 <div
234 style={{
235 padding: '4px 8px',
236 borderRadius: '4px',
237 backgroundColor: getStatusColor(status),
238 color: 'white',
239 fontSize: '12px',
240 fontWeight: 'bold',
241 textTransform: 'uppercase'
242 }}
243 >
244 {status.replace('-', ' ')}
245 </div>
246 </Flex>
247 </CardHeader>
248 <CardBody>
249 <div style={{ textAlign: 'center', marginBottom: '16px' }}>
250 <div style={{
251 fontSize: '32px',
252 fontWeight: 'bold',
253 color: getStatusColor(status)
254 }}>
255 {data.value}{data.unit}
256 </div>
257 <Text variant="muted">{data.description}</Text>
258 </div>
259
260 <div style={{ marginBottom: '8px' }}>
261 <Text size="small">
262 <strong>{__('Good:', 'thinkrank')}</strong>{data.good_threshold}{data.unit}
263 </Text>
264 </div>
265 <div>
266 <Text size="small">
267 <strong>{__('Poor:', 'thinkrank')}</strong> &gt; {data.needs_improvement_threshold}{data.unit}
268 </Text>
269 </div>
270 </CardBody>
271 </Card>
272 );
273 };
274
275 /**
276 * Get performance score color
277 */
278 const getScoreColor = (score) => {
279 if (score >= 90) return '#00a32a'; // Green
280 if (score >= 50) return '#dba617'; // Orange
281 return '#d63638'; // Red
282 };
283
284 /**
285 * Render performance score with circular indicator
286 */
287 const renderPerformanceScore = () => {
288 if (!performanceData?.performance_score) return null;
289
290 const score = performanceData.performance_score;
291 const color = getScoreColor(score);
292 const circumference = 2 * Math.PI * 45; // radius = 45
293 const strokeDasharray = circumference;
294 const strokeDashoffset = circumference - (score / 100) * circumference;
295
296 return (
297 <Card>
298 <CardHeader>
299 <Flex justify="space-between" align="center">
300 <Heading level={3}>{__('Performance', 'thinkrank')}</Heading>
301 <div style={{ display: 'flex', gap: '8px' }}>
302 <button
303 onClick={() => setDeviceType('mobile')}
304 style={{
305 padding: '6px 12px',
306 border: '1px solid #ddd',
307 borderRadius: '4px',
308 background: deviceType === 'mobile' ? '#0073aa' : 'white',
309 color: deviceType === 'mobile' ? 'white' : '#333',
310 cursor: 'pointer',
311 fontSize: '12px'
312 }}
313 >
314 {__('Mobile', 'thinkrank')}
315 </button>
316 <button
317 onClick={() => setDeviceType('desktop')}
318 style={{
319 padding: '6px 12px',
320 border: '1px solid #ddd',
321 borderRadius: '4px',
322 background: deviceType === 'desktop' ? '#0073aa' : 'white',
323 color: deviceType === 'desktop' ? 'white' : '#333',
324 cursor: 'pointer',
325 fontSize: '12px'
326 }}
327 >
328 {__('Desktop', 'thinkrank')}
329 </button>
330 </div>
331 </Flex>
332 </CardHeader>
333 <CardBody>
334 <Flex justify="flex-start" align="center" gap={6}>
335 <FlexItem>
336 <div style={{ position: 'relative', width: '100px', height: '100px' }}>
337 <svg width="100" height="100" style={{ transform: 'rotate(-90deg)' }}>
338 {/* Background circle */}
339 <circle
340 cx="50"
341 cy="50"
342 r="45"
343 stroke="#e0e0e0"
344 strokeWidth="6"
345 fill="none"
346 />
347 {/* Progress circle */}
348 <circle
349 cx="50"
350 cy="50"
351 r="45"
352 stroke={color}
353 strokeWidth="6"
354 fill="none"
355 strokeDasharray={strokeDasharray}
356 strokeDashoffset={strokeDashoffset}
357 strokeLinecap="round"
358 style={{ transition: 'stroke-dashoffset 0.5s ease' }}
359 />
360 </svg>
361 <div style={{
362 position: 'absolute',
363 top: '50%',
364 left: '50%',
365 transform: 'translate(-50%, -50%)',
366 fontSize: '24px',
367 fontWeight: 'bold',
368 color: color
369 }}>
370 {score}
371 </div>
372 </div>
373 </FlexItem>
374 <FlexItem>
375 <div>
376 <Text style={{ fontSize: '16px', fontWeight: '500', marginBottom: '4px' }}>
377 {deviceType === 'mobile' ? __('Mobile Performance', 'thinkrank') : __('Desktop Performance', 'thinkrank')}
378 </Text>
379 <Text variant="muted" style={{ fontSize: '14px' }}>
380 {score >= 90 && __('Fast - Performance is good', 'thinkrank')}
381 {score >= 50 && score < 90 && __('Average - Performance needs improvement', 'thinkrank')}
382 {score < 50 && __('Slow - Performance is poor', 'thinkrank')}
383 </Text>
384 </div>
385 </FlexItem>
386 </Flex>
387 </CardBody>
388 </Card>
389 );
390 };
391
392 /**
393 * Get opportunity priority color
394 */
395 const getOpportunityColor = (savings) => {
396 if (savings >= 1000) return '#d63638'; // Red - High impact
397 if (savings >= 500) return '#dba617'; // Orange - Medium impact
398 return '#00a32a'; // Green - Low impact
399 };
400
401 /**
402 * Get difficulty badge style - Memoized for performance
403 */
404 const getDifficultyStyle = useCallback((difficulty) => {
405 const colors = {
406 'Easy': { bg: '#e7f5e7', color: '#00a32a', border: '#00a32a' },
407 'Medium': { bg: '#fff8e1', color: '#dba617', border: '#dba617' },
408 'Hard': { bg: '#ffeaea', color: '#d63638', border: '#d63638' }
409 };
410 return colors[difficulty] || colors['Medium'];
411 }, []);
412
413 /**
414 * Render opportunity card - Memoized for performance
415 */
416 const renderOpportunityCard = useCallback((opportunity) => {
417 const difficultyStyle = getDifficultyStyle(opportunity.difficulty);
418 const impactColor = getOpportunityColor(opportunity.estimated_savings);
419
420 return (
421 <Card key={opportunity.id} style={{ marginBottom: '16px', border: `1px solid ${impactColor}20` }}>
422 <CardBody>
423 <Flex justify="space-between" align="flex-start" style={{ marginBottom: '12px' }}>
424 <FlexItem style={{ flex: 1 }}>
425 <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
426 <Heading level={4} style={{ margin: 0, fontSize: '16px' }}>
427 {opportunity.title}
428 </Heading>
429 <span style={{
430 padding: '2px 8px',
431 borderRadius: '12px',
432 fontSize: '12px',
433 fontWeight: 'bold',
434 backgroundColor: difficultyStyle.bg,
435 color: difficultyStyle.color,
436 border: `1px solid ${difficultyStyle.border}40`
437 }}>
438 {opportunity.difficulty}
439 </span>
440 </div>
441 <Text variant="muted" style={{ fontSize: '14px' }}>
442 {opportunity.description}
443 </Text>
444 </FlexItem>
445 <FlexItem>
446 <div style={{ textAlign: 'right' }}>
447 <div style={{
448 fontSize: '18px',
449 fontWeight: 'bold',
450 color: impactColor,
451 marginBottom: '2px'
452 }}>
453 {opportunity.estimated_savings >= 1000
454 ? `${(opportunity.estimated_savings / 1000).toFixed(1)}s`
455 : `${opportunity.estimated_savings}ms`
456 }
457 </div>
458 <Text variant="muted" style={{ fontSize: '12px' }}>
459 {__('Potential savings', 'thinkrank')}
460 </Text>
461 </div>
462 </FlexItem>
463 </Flex>
464
465 {opportunity.details && (
466 <div style={{
467 backgroundColor: '#f8f9fa',
468 padding: '12px',
469 borderRadius: '4px',
470 marginTop: '12px'
471 }}>
472 <Text style={{ fontSize: '14px' }}>
473 <strong>{__('How to fix:', 'thinkrank')}</strong> {opportunity.details}
474 </Text>
475 </div>
476 )}
477 </CardBody>
478 </Card>
479 );
480 }, [getDifficultyStyle, getOpportunityColor]);
481
482 /**
483 * Load opportunities data
484 */
485 const loadOpportunities = async () => {
486 setIsLoadingOpportunities(true);
487 setOpportunitiesError(null);
488
489 try {
490 const response = await apiFetch({
491 path: '/thinkrank/v1/performance/opportunities',
492 method: 'GET'
493 });
494
495 if (response.success) {
496 setOpportunities(response.data || []);
497 } else {
498 setOpportunitiesError(__('Failed to load opportunities data', 'thinkrank'));
499 }
500 } catch (error) {
501 setOpportunitiesError(__('Unable to connect to performance API', 'thinkrank'));
502 } finally {
503 setIsLoadingOpportunities(false);
504 }
505 };
506
507 /**
508 * Load diagnostics data
509 */
510 const loadDiagnostics = async () => {
511 setIsLoadingDiagnostics(true);
512 setDiagnosticsError(null);
513
514 try {
515 const response = await apiFetch({
516 path: '/thinkrank/v1/performance/diagnostics',
517 method: 'GET'
518 });
519
520 if (response.success) {
521 setDiagnostics(response.data || []);
522 } else {
523 setDiagnosticsError(__('Failed to load diagnostics data', 'thinkrank'));
524 }
525 } catch (error) {
526 setDiagnosticsError(__('Unable to connect to performance API', 'thinkrank'));
527 } finally {
528 setIsLoadingDiagnostics(false);
529 }
530 };
531
532 /**
533 * Memoize total savings calculation for performance
534 */
535 const totalSavings = useMemo(() => {
536 return opportunities.reduce((sum, opp) => sum + (opp.estimated_savings || 0), 0);
537 }, [opportunities]);
538
539 /**
540 * Render Opportunities content with real PageSpeed data
541 */
542 const renderOpportunities = () => {
543
544 // Show loading state
545 if (isLoadingOpportunities) {
546 return (
547 <Card>
548 <CardBody style={{ textAlign: 'center', padding: '48px' }}>
549 <Spinner />
550 <Text>{__('Loading performance opportunities...', 'thinkrank')}</Text>
551 </CardBody>
552 </Card>
553 );
554 }
555
556 // Show error state
557 if (opportunitiesError) {
558 return (
559 <Card>
560 <CardHeader>
561 <Heading level={3}>{__('Performance Opportunities', 'thinkrank')}</Heading>
562 </CardHeader>
563 <CardBody>
564 <Notice status="error" isDismissible={false}>
565 {opportunitiesError}
566 </Notice>
567 <Text variant="muted" style={{ marginTop: '16px' }}>
568 {__('Configure Google PageSpeed Insights API in Integrations > Google Services to view real performance opportunities.', 'thinkrank')}
569 </Text>
570 </CardBody>
571 </Card>
572 );
573 }
574
575 // Show empty state - check if it's due to API configuration or actually no opportunities
576 if (opportunities.length === 0) {
577 // Check if Core Web Vitals data indicates API configuration issue
578 const hasApiConfigIssue = performanceData?.core_web_vitals?.error ||
579 performanceData?.core_web_vitals?.message?.includes('API key');
580
581 if (hasApiConfigIssue) {
582 // Show API configuration message
583 return (
584 <Card>
585 <CardHeader>
586 <Heading level={3}>{__('Performance Opportunities', 'thinkrank')}</Heading>
587 </CardHeader>
588 <CardBody>
589 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
590 <div style={{ fontSize: '48px', marginBottom: '24px' }}></div>
591
592 <div style={{ marginBottom: '32px' }}>
593 <Heading level={4} style={{ marginBottom: '16px' }}>
594 {__('No Performance Data Available', 'thinkrank')}
595 </Heading>
596
597 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
598 {__('Configure Google PageSpeed Insights API to view performance optimization opportunities.', 'thinkrank')}
599 </Text>
600
601 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
602 {__('Performance opportunities help identify specific areas where your site can be optimized for better speed and user experience.', 'thinkrank')}
603 </Text>
604 </div>
605
606 <div style={{ display: 'flex', justifyContent: 'center', gap: '12px', flexWrap: 'wrap' }}>
607 <Button
608 variant="primary"
609 onClick={() => {
610 // Navigate to Integrations > Google Services tab
611 if (onNavigate) {
612 onNavigate('integrations', 'google-services');
613 }
614 }}
615 >
616 {__('Configure API Settings', 'thinkrank')}
617 </Button>
618 <Button
619 variant="secondary"
620 onClick={loadOpportunities}
621 disabled={isLoadingOpportunities}
622 >
623 {isLoadingOpportunities ? <Spinner /> : __('Retry', 'thinkrank')}
624 </Button>
625 </div>
626 </div>
627 </CardBody>
628 </Card>
629 );
630 } else {
631 // Show "well-optimized" message
632 return (
633 <Card>
634 <CardHeader>
635 <Heading level={3}>{__('Performance Opportunities', 'thinkrank')}</Heading>
636 </CardHeader>
637 <CardBody>
638 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
639 <div style={{ fontSize: '48px', marginBottom: '24px' }}>🎉</div>
640
641 <div>
642 <Heading level={4} style={{ marginBottom: '16px' }}>
643 {__('Great job! No performance opportunities found.', 'thinkrank')}
644 </Heading>
645
646 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
647 {__('Your site is already well-optimized for performance.', 'thinkrank')}
648 </Text>
649
650 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
651 {__('Keep monitoring your performance metrics to maintain optimal user experience.', 'thinkrank')}
652 </Text>
653 </div>
654 </div>
655 </CardBody>
656 </Card>
657 );
658 }
659 }
660
661 return (
662 <>
663 <Card style={{ marginBottom: '24px' }}>
664 <CardHeader>
665 <Flex justify="space-between" align="center">
666 <Heading level={3}>{__('Performance Opportunities', 'thinkrank')}</Heading>
667 <div style={{ textAlign: 'right' }}>
668 <div style={{ fontSize: '18px', fontWeight: 'bold', color: '#d63638' }}>
669 {totalSavings >= 1000
670 ? `${(totalSavings / 1000).toFixed(1)}s`
671 : `${totalSavings}ms`
672 }
673 </div>
674 <Text variant="muted" style={{ fontSize: '12px' }}>
675 {__('Total potential savings', 'thinkrank')}
676 </Text>
677 </div>
678 </Flex>
679 </CardHeader>
680 <CardBody>
681 <Text variant="muted">
682 {__('These suggestions can help your page load faster. They don\'t directly affect the Performance score.', 'thinkrank')}
683 </Text>
684 </CardBody>
685 </Card>
686
687 <div>
688 {opportunities.map(opportunity => renderOpportunityCard(opportunity))}
689 </div>
690
691 <Card style={{ backgroundColor: '#f0f6ff', border: '1px solid #0073aa20' }}>
692 <CardBody>
693 <Flex align="center" gap={3}>
694 <div style={{ fontSize: '20px' }}>💡</div>
695 <div>
696 <Text style={{ fontWeight: '500', marginBottom: '4px' }}>
697 {__('Pro Tip', 'thinkrank')}
698 </Text>
699 <Text variant="muted" style={{ fontSize: '14px' }}>
700 {__('Focus on opportunities with high estimated savings and easy difficulty first. These provide the best return on investment for your performance optimization efforts.', 'thinkrank')}
701 </Text>
702 </div>
703 </Flex>
704 </CardBody>
705 </Card>
706 </>
707 );
708 };
709
710
711
712 /**
713 * Render simple trend chart
714 */
715 const renderTrendChart = (title, data, unit, thresholds) => {
716 // Determine chart color based on performance
717 const getChartColor = () => {
718 if (!data || data.length === 0) return '#0073aa';
719
720 const currentValue = data[data.length - 1]?.value || 0;
721
722 if (thresholds?.good && currentValue <= thresholds.good) {
723 return '#34a853'; // Green for good
724 } else if (thresholds?.needs_improvement && currentValue <= thresholds.needs_improvement) {
725 return '#fbbc04'; // Yellow for needs improvement
726 } else {
727 return '#ea4335'; // Red for poor
728 }
729 };
730
731 return (
732 <Card className="thinkrank-mb-4">
733 <CardBody>
734 <PerformanceChart
735 title={title}
736 data={data}
737 unit={unit}
738 thresholds={thresholds}
739 height={320}
740 showArea={true}
741 color={getChartColor()}
742 />
743
744 {/* Current value and trend */}
745 <div className="thinkrank-chart-stats">
746 <div>
747 <div className="thinkrank-current-value">
748 {data[data.length - 1]?.value?.toFixed(unit === 'score' ? 3 : unit === 's' ? 2 : 0)}{unit}
749 </div>
750 <div className="thinkrank-current-label">
751 {__('Current', 'thinkrank')}
752 </div>
753 </div>
754 <div className="thinkrank-trend">
755 <div className={`thinkrank-trend-value ${data.length > 1 && data[data.length - 1]?.value < data[data.length - 2]?.value ? 'status-good' : 'status-poor'}`}>
756 {data.length > 1 ? (
757 data[data.length - 1]?.value < data[data.length - 2]?.value ? '↓ Better' : '↑ Worse'
758 ) : ''}
759 </div>
760 <div className="thinkrank-trend-label">
761 {__('vs. Previous', 'thinkrank')}
762 </div>
763 </div>
764 </div>
765 </CardBody>
766 </Card>
767 );
768 };
769
770 /**
771 * Render Historical Data content with real trend charts
772 */
773 const renderHistoricalData = () => {
774 // Show loading state
775 if (!historicalData) {
776 return (
777 <Card>
778 <CardBody style={{ textAlign: 'center', padding: '48px' }}>
779 <Spinner />
780 <Text>{__('Loading historical performance data...', 'thinkrank')}</Text>
781 </CardBody>
782 </Card>
783 );
784 }
785
786 // Check if we have real historical data or if API configuration is needed
787 const hasRealData = historicalData && (
788 historicalData.lcp ||
789 historicalData.fid ||
790 historicalData.cls ||
791 historicalData.inp
792 );
793
794 // Check if Core Web Vitals data indicates API configuration issue
795 const hasApiConfigIssue = performanceData?.core_web_vitals?.error ||
796 performanceData?.core_web_vitals?.message?.includes('API key');
797
798 // If no real data and API config issue, show configuration message
799 if (!hasRealData && hasApiConfigIssue) {
800 return (
801 <Card>
802 <CardHeader>
803 <Heading level={3}>{__('Historical Performance Data', 'thinkrank')}</Heading>
804 </CardHeader>
805 <CardBody>
806 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
807 <div style={{ fontSize: '48px', marginBottom: '24px' }}>📈</div>
808
809 <div style={{ marginBottom: '32px' }}>
810 <Heading level={4} style={{ marginBottom: '16px' }}>
811 {__('No Historical Data Available', 'thinkrank')}
812 </Heading>
813
814 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
815 {__('Configure Google PageSpeed Insights API to start collecting historical performance data.', 'thinkrank')}
816 </Text>
817
818 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
819 {__('Historical data helps track your site\'s performance trends over time and identify patterns in Core Web Vitals metrics.', 'thinkrank')}
820 </Text>
821 </div>
822
823 <div style={{ display: 'flex', justifyContent: 'center', gap: '12px', flexWrap: 'wrap' }}>
824 <Button
825 variant="primary"
826 onClick={() => {
827 // Navigate to Integrations > Google Services tab
828 if (onNavigate) {
829 onNavigate('integrations', 'google-services');
830 }
831 }}
832 >
833 {__('Configure API Settings', 'thinkrank')}
834 </Button>
835 <Button
836 variant="secondary"
837 onClick={loadHistoricalData}
838 disabled={!historicalData}
839 >
840 {!historicalData ? <Spinner /> : __('Retry', 'thinkrank')}
841 </Button>
842 </div>
843 </div>
844 </CardBody>
845 </Card>
846 );
847 }
848
849 // If no real data but API is configured, show data collection message
850 if (!hasRealData) {
851 return (
852 <Card>
853 <CardHeader>
854 <Heading level={3}>{__('Historical Performance Data', 'thinkrank')}</Heading>
855 </CardHeader>
856 <CardBody>
857 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
858 <div style={{ fontSize: '48px', marginBottom: '24px' }}></div>
859
860 <div style={{ marginBottom: '32px' }}>
861 <Heading level={4} style={{ marginBottom: '16px' }}>
862 {__('Collecting Historical Data', 'thinkrank')}
863 </Heading>
864
865 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
866 {__('Historical performance data will appear here as it\'s collected over time.', 'thinkrank')}
867 </Text>
868
869 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
870 {__('Data collection runs daily. Check back in a few days to see your performance trends.', 'thinkrank')}
871 </Text>
872 </div>
873
874 <div style={{ display: 'flex', justifyContent: 'center' }}>
875 <Button
876 variant="secondary"
877 onClick={loadHistoricalData}
878 disabled={!historicalData}
879 >
880 {!historicalData ? <Spinner /> : __('Refresh Data', 'thinkrank')}
881 </Button>
882 </div>
883 </div>
884 </CardBody>
885 </Card>
886 );
887 }
888
889 // Use real historical data
890 const lcpData = historicalData.lcp;
891 const fidData = historicalData.fid;
892 const clsData = historicalData.cls;
893 const inpData = historicalData.inp;
894
895 return (
896 <>
897 <Card style={{ marginBottom: '24px' }}>
898 <CardHeader>
899 <Flex justify="space-between" align="center">
900 <Heading level={3}>{__('Historical Performance Data', 'thinkrank')}</Heading>
901 <div style={{ display: 'flex', gap: '8px' }}>
902 <button
903 onClick={() => setHistoricalPeriod(28)}
904 style={{
905 padding: '6px 12px',
906 border: '1px solid #ddd',
907 borderRadius: '4px',
908 background: historicalPeriod === 28 ? '#0073aa' : 'white',
909 color: historicalPeriod === 28 ? 'white' : '#333',
910 cursor: 'pointer',
911 fontSize: '12px'
912 }}
913 >
914 {__('28 Days', 'thinkrank')}
915 </button>
916 <button
917 onClick={() => setHistoricalPeriod(90)}
918 style={{
919 padding: '6px 12px',
920 border: '1px solid #ddd',
921 borderRadius: '4px',
922 background: historicalPeriod === 90 ? '#0073aa' : 'white',
923 color: historicalPeriod === 90 ? 'white' : '#333',
924 cursor: 'pointer',
925 fontSize: '12px'
926 }}
927 >
928 {__('90 Days', 'thinkrank')}
929 </button>
930 </div>
931 </Flex>
932 </CardHeader>
933 <CardBody>
934 <Text variant="muted">
935 {__('Track your Core Web Vitals performance over time to identify trends and regressions.', 'thinkrank')}
936 {__(' Showing data for the last %d days.', 'thinkrank').replace('%d', historicalPeriod)}
937 </Text>
938 </CardBody>
939 </Card>
940
941 <div className="thinkrank-grid thinkrank-grid-cols-1 thinkrank-grid-cols-2-lg thinkrank-gap-4 thinkrank-mb-6">
942 {renderTrendChart(
943 __('Largest Contentful Paint (LCP)', 'thinkrank'),
944 lcpData,
945 's',
946 { good: 2.5, needs_improvement: 4.0 }
947 )}
948 {renderTrendChart(
949 __('First Input Delay (FID)', 'thinkrank'),
950 fidData,
951 'ms',
952 { good: 100, needs_improvement: 300 }
953 )}
954 {renderTrendChart(
955 __('Cumulative Layout Shift (CLS)', 'thinkrank'),
956 clsData,
957 'score',
958 { good: 0.1, needs_improvement: 0.25 }
959 )}
960 {renderTrendChart(
961 __('Interaction to Next Paint (INP)', 'thinkrank'),
962 inpData,
963 'ms',
964 { good: 200, needs_improvement: 500 }
965 )}
966 </div>
967
968 <Card style={{ marginBottom: '16px' }}>
969 <CardHeader>
970 <Heading level={4}>{__('Performance Alerts & Monitoring', 'thinkrank')}</Heading>
971 </CardHeader>
972 <CardBody>
973 <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))', gap: '16px' }}>
974 <div>
975 <Text style={{ fontWeight: '500', marginBottom: '8px' }}>
976 {__('Monitoring Status', 'thinkrank')}
977 </Text>
978 <div style={{ fontSize: '14px' }}>
979 <div style={{ marginBottom: '4px' }}>�
980 {__('Core Web Vitals tracking: Active', 'thinkrank')}</div>
981 <div style={{ marginBottom: '4px' }}>�
982 {__('Performance alerts: Enabled', 'thinkrank')}</div>
983 <div style={{ marginBottom: '4px' }}>�
984 {__('Data retention: 90 days', 'thinkrank')}</div>
985 </div>
986 </div>
987
988 <div>
989 <Text style={{ fontWeight: '500', marginBottom: '8px' }}>
990 {__('Recent Activity', 'thinkrank')}
991 </Text>
992 <div style={{ fontSize: '14px' }}>
993 <div style={{ marginBottom: '4px' }}>{__('Performance data collected successfully', 'thinkrank')}</div>
994 <div style={{ marginBottom: '4px' }}>{__('No critical alerts in the last 24 hours', 'thinkrank')}</div>
995 <div style={{ marginBottom: '4px' }}>{__('LCP improved by 0.2s since last week', 'thinkrank')}</div>
996 </div>
997 </div>
998 </div>
999 </CardBody>
1000 </Card>
1001
1002 <Card style={{ backgroundColor: '#fff8e1', border: '1px solid #dba61720' }}>
1003 <CardBody>
1004 <Flex align="center" gap={3}>
1005 <div style={{ fontSize: '20px' }}>📊</div>
1006 <div>
1007 <Text style={{ fontWeight: '500', marginBottom: '4px' }}>
1008 {__('Historical Data Integration', 'thinkrank')}
1009 </Text>
1010 <Text variant="muted" style={{ fontSize: '14px' }}>
1011 {__('When Google PageSpeed API is integrated, this section will show real historical performance data from your actual website visitors, helping you track improvements and identify performance regressions over time.', 'thinkrank')}
1012 </Text>
1013 </div>
1014 </Flex>
1015 </CardBody>
1016 </Card>
1017 </>
1018 );
1019 };
1020
1021 /**
1022 * Render diagnostic card
1023 */
1024 const renderDiagnosticCard = (diagnostic) => {
1025 const statusColors = {
1026 'passed': { bg: '#e7f5e7', color: '#00a32a', icon: '�
1027 ' },
1028 'warning': { bg: '#fff8e1', color: '#dba617', icon: '⚠️' },
1029 'failed': { bg: '#ffeaea', color: '#d63638', icon: '' }
1030 };
1031
1032 const statusStyle = statusColors[diagnostic.status] || statusColors['warning'];
1033
1034 return (
1035 <Card key={diagnostic.id} style={{ marginBottom: '16px', border: `1px solid ${statusStyle.color}20` }}>
1036 <CardBody>
1037 <Flex justify="space-between" align="flex-start" style={{ marginBottom: '12px' }}>
1038 <FlexItem style={{ flex: 1 }}>
1039 <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '4px' }}>
1040 <span style={{ fontSize: '16px' }}>{statusStyle.icon}</span>
1041 <Heading level={4} style={{ margin: 0, fontSize: '16px' }}>
1042 {diagnostic.title}
1043 </Heading>
1044 </div>
1045 <Text variant="muted" style={{ fontSize: '14px', marginBottom: '8px' }}>
1046 {diagnostic.description}
1047 </Text>
1048
1049 {diagnostic.details && (
1050 <Text style={{ fontSize: '14px' }}>
1051 {diagnostic.details}
1052 </Text>
1053 )}
1054 </FlexItem>
1055
1056 {diagnostic.impact && (
1057 <FlexItem>
1058 <div style={{ textAlign: 'right' }}>
1059 <div style={{
1060 fontSize: '14px',
1061 fontWeight: 'bold',
1062 color: statusStyle.color,
1063 marginBottom: '2px'
1064 }}>
1065 {diagnostic.impact}
1066 </div>
1067 <Text variant="muted" style={{ fontSize: '12px' }}>
1068 {__('Impact', 'thinkrank')}
1069 </Text>
1070 </div>
1071 </FlexItem>
1072 )}
1073 </Flex>
1074
1075 {diagnostic.resources && diagnostic.resources.length > 0 && (
1076 <div style={{
1077 backgroundColor: '#f8f9fa',
1078 padding: '12px',
1079 borderRadius: '4px',
1080 marginTop: '12px'
1081 }}>
1082 <Text style={{ fontSize: '14px', fontWeight: '500', marginBottom: '8px' }}>
1083 {__('Affected Resources:', 'thinkrank')}
1084 </Text>
1085 <div style={{ fontSize: '13px', fontFamily: 'monospace' }}>
1086 {diagnostic.resources.slice(0, 3).map((resource, index) => (
1087 <div key={index} style={{ marginBottom: '4px', color: '#666' }}>
1088 {resource}
1089 </div>
1090 ))}
1091 {diagnostic.resources.length > 3 && (
1092 <Text variant="muted" style={{ fontSize: '12px' }}>
1093 {__('... and %d more', 'thinkrank').replace('%d', diagnostic.resources.length - 3)}
1094 </Text>
1095 )}
1096 </div>
1097 </div>
1098 )}
1099 </CardBody>
1100 </Card>
1101 );
1102 };
1103
1104 /**
1105 * Render Diagnostics content with PageSpeed-style diagnostic cards
1106 */
1107 const renderDiagnostics = () => {
1108 // Show loading state
1109 if (isLoadingDiagnostics) {
1110 return (
1111 <Card>
1112 <CardBody style={{ textAlign: 'center', padding: '48px' }}>
1113 <Spinner />
1114 <Text>{__('Loading performance diagnostics...', 'thinkrank')}</Text>
1115 </CardBody>
1116 </Card>
1117 );
1118 }
1119
1120 // Show error state
1121 if (diagnosticsError) {
1122 return (
1123 <Card>
1124 <CardHeader>
1125 <Heading level={3}>{__('Performance Diagnostics', 'thinkrank')}</Heading>
1126 </CardHeader>
1127 <CardBody>
1128 <Notice status="error" isDismissible={false}>
1129 {diagnosticsError}
1130 </Notice>
1131 <Text variant="muted" style={{ marginTop: '16px' }}>
1132 {__('Configure Google PageSpeed Insights API in Integrations > Google Services to view real performance diagnostics.', 'thinkrank')}
1133 </Text>
1134 </CardBody>
1135 </Card>
1136 );
1137 }
1138
1139 // Show empty state
1140 if (diagnostics.length === 0) {
1141 return (
1142 <Card>
1143 <CardHeader>
1144 <Heading level={3}>{__('Performance Diagnostics', 'thinkrank')}</Heading>
1145 </CardHeader>
1146 <CardBody>
1147 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
1148 <div style={{ fontSize: '48px', marginBottom: '24px' }}>🔍</div>
1149
1150 <div style={{ marginBottom: '32px' }}>
1151 <Heading level={4} style={{ marginBottom: '16px' }}>
1152 {__('No Diagnostic Data Available', 'thinkrank')}
1153 </Heading>
1154
1155 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
1156 {__('Configure Google PageSpeed Insights API to view detailed performance diagnostics.', 'thinkrank')}
1157 </Text>
1158
1159 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
1160 {__('Performance diagnostics provide detailed insights into specific performance issues and optimization recommendations.', 'thinkrank')}
1161 </Text>
1162 </div>
1163
1164 <div style={{ display: 'flex', justifyContent: 'center', gap: '12px', flexWrap: 'wrap' }}>
1165 <Button
1166 variant="primary"
1167 onClick={() => {
1168 // Navigate to Integrations > Google Services tab
1169 if (onNavigate) {
1170 onNavigate('integrations', 'google-services');
1171 }
1172 }}
1173 >
1174 {__('Configure API Settings', 'thinkrank')}
1175 </Button>
1176 <Button
1177 variant="secondary"
1178 onClick={loadDiagnostics}
1179 disabled={isLoadingDiagnostics}
1180 >
1181 {isLoadingDiagnostics ? <Spinner /> : __('Retry', 'thinkrank')}
1182 </Button>
1183 </div>
1184 </div>
1185 </CardBody>
1186 </Card>
1187 );
1188 }
1189
1190 const passedCount = diagnostics.filter(d => d.status === 'passed').length;
1191 const warningCount = diagnostics.filter(d => d.status === 'warning').length;
1192 const failedCount = diagnostics.filter(d => d.status === 'failed').length;
1193
1194 return (
1195 <>
1196 <Card style={{ marginBottom: '24px' }}>
1197 <CardHeader>
1198 <Flex justify="space-between" align="center">
1199 <Heading level={3}>{__('Performance Diagnostics', 'thinkrank')}</Heading>
1200 <div style={{ display: 'flex', gap: '16px', fontSize: '14px' }}>
1201 <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
1202 <span style={{ color: '#00a32a' }}>�
1203 </span>
1204 <span>{passedCount} {__('Passed', 'thinkrank')}</span>
1205 </div>
1206 <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
1207 <span style={{ color: '#dba617' }}>⚠️</span>
1208 <span>{warningCount} {__('Warnings', 'thinkrank')}</span>
1209 </div>
1210 <div style={{ display: 'flex', alignItems: 'center', gap: '4px' }}>
1211 <span style={{ color: '#d63638' }}></span>
1212 <span>{failedCount} {__('Failed', 'thinkrank')}</span>
1213 </div>
1214 </div>
1215 </Flex>
1216 </CardHeader>
1217 <CardBody>
1218 <Text variant="muted">
1219 {__('These checks highlight opportunities to improve your page\'s performance and user experience.', 'thinkrank')}
1220 </Text>
1221 </CardBody>
1222 </Card>
1223
1224 <div>
1225 {diagnostics.map(diagnostic => renderDiagnosticCard(diagnostic))}
1226 </div>
1227
1228 <Card style={{ backgroundColor: '#f0f6ff', border: '1px solid #0073aa20' }}>
1229 <CardBody>
1230 <Flex align="center" gap={3}>
1231 <div style={{ fontSize: '20px' }}>🔍</div>
1232 <div>
1233 <Text style={{ fontWeight: '500', marginBottom: '4px' }}>
1234 {__('Diagnostic Information', 'thinkrank')}
1235 </Text>
1236 <Text variant="muted" style={{ fontSize: '14px' }}>
1237 {__('These diagnostics are collected from various performance audits. When integrated with Google PageSpeed API, you\'ll get real-time diagnostic data specific to your website\'s actual performance characteristics.', 'thinkrank')}
1238 </Text>
1239 </div>
1240 </Flex>
1241 </CardBody>
1242 </Card>
1243 </>
1244 );
1245 };
1246
1247 /**
1248 * Render Core Web Vitals content with device toggle
1249 * Phase 5: Enhanced error handling and "No Data Available" states
1250 */
1251 const renderCoreWebVitals = () => {
1252 // Check if Core Web Vitals data has API configuration errors
1253 if (!performanceData || !performanceData.core_web_vitals || performanceData.core_web_vitals.error) {
1254 return (
1255 <Card>
1256 <CardHeader>
1257 <Heading level={3}>{__('Core Web Vitals', 'thinkrank')}</Heading>
1258 <Text variant="muted">
1259 {__('Google ranking factors that measure user experience', 'thinkrank')}
1260 </Text>
1261 </CardHeader>
1262 <CardBody>
1263 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
1264 <div style={{ fontSize: '48px', marginBottom: '24px' }}>📊</div>
1265
1266 <div style={{ marginBottom: '32px' }}>
1267 <Heading level={4} style={{ marginBottom: '16px' }}>
1268 {__('No Core Web Vitals Data Available', 'thinkrank')}
1269 </Heading>
1270
1271 <Text style={{ color: '#666', fontSize: '16px', marginBottom: '12px', display: 'block' }}>
1272 {performanceData?.core_web_vitals?.message ||
1273 __('No Core Web Vitals data available. PageSpeed Insights provides real-time data, so please check your API key configuration.', 'thinkrank')}
1274 </Text>
1275
1276 <Text variant="muted" style={{ fontSize: '14px', display: 'block' }}>
1277 {__('Core Web Vitals are essential metrics that Google uses for ranking. Get real data by setting up the PageSpeed Insights API.', 'thinkrank')}
1278 </Text>
1279 </div>
1280
1281 <div style={{ display: 'flex', justifyContent: 'center', gap: '12px', flexWrap: 'wrap' }}>
1282 <Button
1283 variant="primary"
1284 onClick={() => {
1285 // Navigate to Integrations > Google Services tab
1286 if (onNavigate) {
1287 onNavigate('integrations', 'google-services');
1288 }
1289 }}
1290 >
1291 {__('Configure API Settings', 'thinkrank')}
1292 </Button>
1293 <Button
1294 variant="secondary"
1295 onClick={loadPerformanceData}
1296 disabled={isLoading}
1297 >
1298 {isLoading ? <Spinner /> : __('Retry', 'thinkrank')}
1299 </Button>
1300 </div>
1301 </div>
1302 </CardBody>
1303 </Card>
1304 );
1305 }
1306
1307 return (
1308 <>
1309 {renderPerformanceScore()}
1310
1311 <Spacer marginY={6} />
1312
1313 <Flex justify="space-between" align="center" style={{ marginBottom: '16px' }}>
1314 <div>
1315 <Heading level={3}>{__('Core Web Vitals', 'thinkrank')}</Heading>
1316 <Text variant="muted">
1317 {__('Google ranking factors that measure user experience', 'thinkrank')}
1318 </Text>
1319 </div>
1320 <div style={{ fontSize: '14px', color: '#666' }}>
1321 {deviceType === 'mobile' ? __('📱 Mobile Data', 'thinkrank') : __('🖥️ Desktop Data', 'thinkrank')}
1322 </div>
1323 </Flex>
1324
1325 <Grid columns={2} gap={4}>
1326 {performanceData.core_web_vitals && Object.entries(performanceData.core_web_vitals)
1327 .filter(([vital, data]) => vital !== 'error' && vital !== 'message') // Filter out error properties
1328 .map(([vital, data]) => renderVitalCard(vital, data))
1329 }
1330 </Grid>
1331
1332 <div style={{ marginTop: '16px', padding: '12px', backgroundColor: '#f8f9fa', borderRadius: '4px', fontSize: '14px' }}>
1333 <Text variant="muted">
1334 <strong>{__('Note:', 'thinkrank')}</strong> {deviceType === 'mobile'
1335 ? __('Mobile performance data reflects real user experience on mobile devices. Mobile-first indexing makes this data crucial for SEO.', 'thinkrank')
1336 : __('Desktop performance data shows how your site performs on desktop devices. While important, mobile performance takes priority for SEO.', 'thinkrank')
1337 }
1338 </Text>
1339 </div>
1340 </>
1341 );
1342 };
1343
1344 /**
1345 * Render content based on active sub-section
1346 * Phase 5: Enhanced error handling for Google API integration
1347 */
1348 const renderSubSectionContent = () => {
1349 if (isLoading && !performanceData) {
1350 return (
1351 <div style={{ textAlign: 'center', padding: '48px' }}>
1352 <Spinner />
1353 <Text>{__('Loading performance data...', 'thinkrank')}</Text>
1354 </div>
1355 );
1356 }
1357
1358 // Enhanced "No Data Available" state with API configuration guidance
1359 if (!performanceData && activeSubSection !== 'recommendations') {
1360 return (
1361 <Card>
1362 <CardHeader>
1363 <Heading level={3}>{__('Performance Data Not Available', 'thinkrank')}</Heading>
1364 </CardHeader>
1365 <CardBody>
1366 <div style={{ textAlign: 'center', padding: '48px 24px' }}>
1367 <div style={{ fontSize: '48px', marginBottom: '24px' }}></div>
1368
1369 <div style={{ marginBottom: '32px' }}>
1370 <Text style={{ fontSize: '16px', marginBottom: '12px', display: 'block' }}>
1371 {__('No performance data available. This could be due to missing API configuration.', 'thinkrank')}
1372 </Text>
1373
1374 <Text variant="muted" style={{ display: 'block' }}>
1375 {__('Configure Google PageSpeed Insights API in Integrations > Google Services to view real performance metrics.', 'thinkrank')}
1376 </Text>
1377 </div>
1378
1379 <div style={{ display: 'flex', justifyContent: 'center', gap: '12px', flexWrap: 'wrap' }}>
1380 <Button
1381 variant="primary"
1382 onClick={() => {
1383 // Navigate to Integrations > Google Services tab
1384 if (onNavigate) {
1385 onNavigate('integrations', 'google-services');
1386 }
1387 }}
1388 >
1389 {__('Configure API Settings', 'thinkrank')}
1390 </Button>
1391 <Button
1392 variant="secondary"
1393 onClick={loadPerformanceData}
1394 disabled={isLoading}
1395 >
1396 {isLoading ? <Spinner /> : __('Refresh Data', 'thinkrank')}
1397 </Button>
1398 </div>
1399 </div>
1400 </CardBody>
1401 </Card>
1402 );
1403 }
1404
1405 switch (activeSubSection) {
1406 case 'core-web-vitals':
1407 return renderCoreWebVitals();
1408 case 'seo-performance':
1409 return renderOpportunities();
1410 case 'monitoring':
1411 return renderHistoricalData();
1412 case 'recommendations':
1413 return renderDiagnostics();
1414 default:
1415 return renderCoreWebVitals();
1416 }
1417 };
1418
1419 // Load data on component mount and when activeSubSection changes
1420 useEffect(() => {
1421 loadPerformanceData();
1422
1423 // Load additional data based on active sub-section
1424 if (activeSubSection === 'recommendations' && !recommendations) {
1425 loadRecommendations();
1426 }
1427 if (activeSubSection === 'monitoring' && !historicalData) {
1428 loadHistoricalData();
1429 }
1430 if (activeSubSection === 'seo-performance' && opportunities.length === 0 && !isLoadingOpportunities) {
1431 loadOpportunities();
1432 }
1433 if (activeSubSection === 'recommendations' && diagnostics.length === 0 && !isLoadingDiagnostics) {
1434 loadDiagnostics();
1435 }
1436 }, [activeSubSection]);
1437
1438 return (
1439 <div className="thinkrank-performance-tab">
1440 <div style={{ marginBottom: '24px' }}>
1441 <Flex justify="space-between" align="center">
1442 <FlexItem>
1443 <Heading level={2}>{__('Performance Monitoring', 'thinkrank')}</Heading>
1444 <Text variant="muted">
1445 {__('Core Web Vitals and SEO performance insights', 'thinkrank')}
1446 </Text>
1447 </FlexItem>
1448 <FlexItem>
1449 <Button
1450 variant="secondary"
1451 onClick={loadPerformanceData}
1452 disabled={isLoading}
1453 >
1454 {isLoading ? <Spinner /> : __('Refresh Data', 'thinkrank')}
1455 </Button>
1456 </FlexItem>
1457 </Flex>
1458 </div>
1459
1460 {notice && (
1461 <Notice
1462 status={notice.status}
1463 onRemove={() => setNotice(null)}
1464 style={{ marginBottom: '16px' }}
1465 >
1466 {notice.message}
1467 </Notice>
1468 )}
1469
1470 {lastUpdated && (
1471 <Text variant="muted" className="thinkrank-mb-4 thinkrank-block">
1472 {__('Last updated:', 'thinkrank')} {lastUpdated}
1473 </Text>
1474 )}
1475
1476 {renderSubSectionContent()}
1477 </div>
1478 );
1479 };
1480
1481 export default PerformanceTab;
1482