@pixelated-tech/components 3.5.13 → 3.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +2 -0
  2. package/dist/components/admin/site-health/google.api.integration.js +258 -0
  3. package/dist/components/admin/site-health/google.api.utils.js +47 -0
  4. package/dist/components/admin/site-health/site-health-accessibility.js +5 -103
  5. package/dist/components/admin/site-health/site-health-axe-core.js +4 -10
  6. package/dist/components/admin/site-health/site-health-cloudwatch.js +24 -26
  7. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +4 -10
  8. package/dist/components/admin/site-health/site-health-github.js +9 -15
  9. package/dist/components/admin/site-health/site-health-google-analytics.integration.js +1 -107
  10. package/dist/components/admin/site-health/site-health-google-analytics.js +21 -29
  11. package/dist/components/admin/site-health/site-health-google-search-console.integration.js +1 -113
  12. package/dist/components/admin/site-health/site-health-google-search-console.js +22 -28
  13. package/dist/components/admin/site-health/site-health-on-site-seo.js +8 -82
  14. package/dist/components/admin/site-health/site-health-overview.js +5 -19
  15. package/dist/components/admin/site-health/site-health-performance.js +5 -167
  16. package/dist/components/admin/site-health/site-health-security.js +7 -148
  17. package/dist/components/admin/site-health/site-health-seo.js +5 -103
  18. package/dist/components/admin/site-health/site-health-template.js +68 -43
  19. package/dist/components/admin/site-health/site-health-uptime.js +4 -9
  20. package/dist/components/admin/site-health/site-health-utils.js +170 -0
  21. package/dist/components/admin/site-health/site-health.css +8 -9
  22. package/dist/index.adminclient.js +2 -5
  23. package/dist/index.adminserver.js +6 -4
  24. package/dist/index.js +39 -45
  25. package/dist/types/components/admin/site-health/google.api.integration.d.ts +82 -0
  26. package/dist/types/components/admin/site-health/google.api.integration.d.ts.map +1 -0
  27. package/dist/types/components/admin/site-health/google.api.utils.d.ts +32 -0
  28. package/dist/types/components/admin/site-health/google.api.utils.d.ts.map +1 -0
  29. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
  30. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
  31. package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -1
  32. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -1
  33. package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -1
  34. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -1
  35. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts +1 -21
  36. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts.map +1 -1
  37. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -1
  38. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts +1 -41
  39. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts.map +1 -1
  40. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
  41. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
  42. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
  43. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
  44. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
  45. package/dist/types/components/admin/site-health/site-health-template.d.ts +8 -1
  46. package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
  47. package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -1
  48. package/dist/types/components/admin/site-health/site-health-utils.d.ts +17 -0
  49. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -0
  50. package/dist/types/index.adminclient.d.ts +2 -5
  51. package/dist/types/index.adminserver.d.ts +6 -4
  52. package/dist/types/index.d.ts +38 -44
  53. package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
  54. package/dist/types/tests/google.api.integration.test.d.ts +2 -0
  55. package/dist/types/tests/google.api.integration.test.d.ts.map +1 -0
  56. package/dist/types/tests/google.api.utils.test.d.ts +5 -0
  57. package/dist/types/tests/google.api.utils.test.d.ts.map +1 -0
  58. package/package.json +1 -1
  59. package/dist/components/admin/site-health/google-api-auth.js +0 -69
  60. package/dist/types/components/admin/site-health/google-api-auth.d.ts +0 -37
  61. package/dist/types/components/admin/site-health/google-api-auth.d.ts.map +0 -1
@@ -3,110 +3,4 @@
3
3
  * Server-side utilities for Google Analytics data retrieval
4
4
  */
5
5
  "use server";
6
- import { RouteCache } from './site-health-cache';
7
- import { createAnalyticsClient } from './google-api-auth';
8
- // Cache for analytics data (1 hour)
9
- const analyticsCache = new RouteCache();
10
- /**
11
- * Get Google Analytics data for a site with current/previous period comparison
12
- */
13
- export async function getGoogleAnalyticsData(config, siteName, startDate, endDate) {
14
- try {
15
- // Check cache first
16
- const cacheKey = `analytics-${siteName}-${startDate || 'default'}-${endDate || 'default'}`;
17
- const cached = analyticsCache.get(cacheKey);
18
- if (cached) {
19
- return { success: true, data: cached };
20
- }
21
- if (!config.ga4PropertyId || config.ga4PropertyId === 'GA4_PROPERTY_ID_HERE') {
22
- return {
23
- success: false,
24
- error: 'GA4 Property ID not configured for this site'
25
- };
26
- }
27
- // Set up authentication
28
- const authResult = await createAnalyticsClient(config);
29
- if (!authResult.success) {
30
- return {
31
- success: false,
32
- error: authResult.error || 'Authentication failed'
33
- };
34
- }
35
- const analyticsData = authResult.client;
36
- // Calculate date ranges
37
- const currentEndDate = endDate ? new Date(endDate) : new Date();
38
- const currentStartDate = startDate ? new Date(startDate) : new Date(currentEndDate.getTime() - 30 * 24 * 60 * 60 * 1000);
39
- // Calculate previous period (same duration before the current period)
40
- const periodDuration = currentEndDate.getTime() - currentStartDate.getTime();
41
- const previousEndDate = new Date(currentStartDate.getTime() - 24 * 60 * 60 * 1000); // One day before start
42
- const previousStartDate = new Date(previousEndDate.getTime() - periodDuration);
43
- const currentStartStr = currentStartDate.toISOString().split('T')[0];
44
- const currentEndStr = currentEndDate.toISOString().split('T')[0];
45
- const previousStartStr = previousStartDate.toISOString().split('T')[0];
46
- const previousEndStr = previousEndDate.toISOString().split('T')[0];
47
- // Fetch current period data
48
- const currentResponse = await analyticsData.properties.runReport({
49
- property: `properties/${config.ga4PropertyId}`,
50
- requestBody: {
51
- dateRanges: [{ startDate: currentStartStr, endDate: currentEndStr }],
52
- dimensions: [{ name: 'date' }],
53
- metrics: [{ name: 'screenPageViews' }],
54
- orderBys: [{ dimension: { dimensionName: 'date' } }],
55
- },
56
- });
57
- // Fetch previous period data
58
- const previousResponse = await analyticsData.properties.runReport({
59
- property: `properties/${config.ga4PropertyId}`,
60
- requestBody: {
61
- dateRanges: [{ startDate: previousStartStr, endDate: previousEndStr }],
62
- dimensions: [{ name: 'date' }],
63
- metrics: [{ name: 'screenPageViews' }],
64
- orderBys: [{ dimension: { dimensionName: 'date' } }],
65
- },
66
- });
67
- // Create a map of previous period data by date
68
- const previousDataMap = new Map();
69
- previousResponse.data.rows?.forEach((row) => {
70
- const dateStr = row.dimensionValues?.[0]?.value || '';
71
- if (dateStr) {
72
- previousDataMap.set(dateStr, parseInt(row.metricValues?.[0]?.value || '0'));
73
- }
74
- });
75
- // Combine current and previous period data
76
- const chartData = [];
77
- const daysInRange = Math.ceil((currentEndDate.getTime() - currentStartDate.getTime()) / (24 * 60 * 60 * 1000));
78
- for (let i = daysInRange - 1; i >= 0; i--) {
79
- const currentDate = new Date(currentEndDate);
80
- currentDate.setDate(currentDate.getDate() - i);
81
- const currentDateStr = currentDate.toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD format
82
- // Calculate corresponding previous period date
83
- const previousDate = new Date(currentDate.getTime() - periodDuration);
84
- const previousDateStr = previousDate.toISOString().split('T')[0].replace(/-/g, ''); // YYYYMMDD format
85
- // Get current period data
86
- const currentRow = currentResponse.data.rows?.find((row) => row.dimensionValues?.[0]?.value === currentDateStr);
87
- const currentPageViews = parseInt(currentRow?.metricValues?.[0]?.value || '0');
88
- // Get previous period data
89
- const previousPageViews = previousDataMap.get(previousDateStr) || 0;
90
- // Format date for display
91
- const formattedDate = currentDate.toLocaleDateString('en-US', {
92
- month: 'short',
93
- day: 'numeric'
94
- });
95
- chartData.push({
96
- date: formattedDate,
97
- currentPageViews: currentPageViews,
98
- previousPageViews: previousPageViews,
99
- });
100
- }
101
- // Cache the result
102
- analyticsCache.set(cacheKey, chartData);
103
- return { success: true, data: chartData };
104
- }
105
- catch (error) {
106
- console.error('Google Analytics error:', error);
107
- return {
108
- success: false,
109
- error: error.message
110
- };
111
- }
112
- }
6
+ export {};
@@ -9,41 +9,33 @@ SiteHealthGoogleAnalytics.propTypes = {
9
9
  endDate: PropTypes.string,
10
10
  };
11
11
  export function SiteHealthGoogleAnalytics({ siteName, startDate, endDate }) {
12
- const fetchAnalyticsData = async (site) => {
13
- const params = new URLSearchParams({ siteName: site });
14
- if (startDate)
15
- params.append('startDate', startDate);
16
- if (endDate)
17
- params.append('endDate', endDate);
18
- const response = await fetch(`/api/site-health/google-analytics?${params.toString()}`);
19
- if (!response.ok) {
20
- throw new Error(`Failed to fetch analytics data: ${response.status}`);
21
- }
22
- const result = await response.json();
23
- if (!result.success) {
24
- // Handle specific error types
25
- if (result.error?.includes('invalid_grant') || result.error?.includes('authentication')) {
26
- throw new Error('Google Analytics authentication expired. Please re-authorize the application.');
27
- }
28
- else if (result.error?.includes('GA4 Property ID not configured')) {
29
- throw new Error('GA4 Property ID not configured for this site');
30
- }
31
- else {
32
- throw new Error(result.error || 'Failed to load analytics data');
33
- }
34
- }
35
- return result.data;
36
- };
37
- return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Analytics", columnSpan: 2, fetchData: fetchAnalyticsData, children: (data) => {
38
- if (!data || data.length === 0) {
12
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Analytics", columnSpan: 2, endpoint: {
13
+ endpoint: '/api/site-health/google-analytics',
14
+ params: {
15
+ ...(startDate && { startDate }),
16
+ ...(endDate && { endDate }),
17
+ },
18
+ responseTransformer: (result) => result.data, // Extract the data array from the response
19
+ }, children: (data) => {
20
+ // Ensure data is an array
21
+ if (!data || !Array.isArray(data) || data.length === 0) {
39
22
  return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "No data available for the selected date range" }) }));
40
23
  }
41
- return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: data, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Page Views (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
24
+ // Filter out any invalid data points
25
+ const validData = data.filter((point) => point &&
26
+ typeof point === 'object' &&
27
+ typeof point.date === 'string' &&
28
+ typeof point.currentPageViews === 'number' &&
29
+ typeof point.previousPageViews === 'number');
30
+ if (validData.length === 0) {
31
+ return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "Invalid data format received from Google Analytics API." }) }));
32
+ }
33
+ return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: validData, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Page Views (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
42
34
  value?.toLocaleString() || '0',
43
35
  name || 'Unknown'
44
36
  ], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
45
37
  fontSize: '12px',
46
38
  paddingTop: '10px'
47
- } }), _jsx(Bar, { dataKey: "currentPageViews", fill: "#3b82f6", name: "Current Period", radius: [2, 2, 0, 0] }), _jsx(Line, { type: "monotone", dataKey: "previousPageViews", stroke: "#ef4444", strokeWidth: 2, strokeDasharray: "5 5", dot: { fill: '#ef4444', strokeWidth: 2, r: 3 }, activeDot: { r: 5, stroke: '#ef4444', strokeWidth: 2 }, name: "Previous Period" })] }, `chart-${data.length}`) }) }) }));
39
+ } }), _jsx(Bar, { dataKey: "currentPageViews", fill: "#3b82f6", name: "Current Period", radius: [2, 2, 0, 0] }), _jsx(Line, { type: "monotone", dataKey: "previousPageViews", stroke: "#ef4444", strokeWidth: 2, strokeDasharray: "5 5", dot: { fill: '#ef4444', strokeWidth: 2, r: 3 }, activeDot: { r: 5, stroke: '#ef4444', strokeWidth: 2 }, name: "Previous Period" })] }, `chart-${validData.length}`) }) }) }));
48
40
  } }));
49
41
  }
@@ -3,116 +3,4 @@
3
3
  * Server-side utilities for Google Search Console data retrieval
4
4
  */
5
5
  "use server";
6
- import { RouteCache } from './site-health-cache';
7
- import { createSearchConsoleClient } from './google-api-auth';
8
- // Cache for search console data (1 hour)
9
- const searchConsoleCache = new RouteCache();
10
- /**
11
- * Get Google Search Console data for a site with current/previous period comparison
12
- */
13
- export async function getSearchConsoleData(config, siteName, startDate, endDate) {
14
- try {
15
- // Check cache first
16
- const cacheKey = `searchconsole-${siteName}-${startDate || 'default'}-${endDate || 'default'}`;
17
- const cached = searchConsoleCache.get(cacheKey);
18
- if (cached) {
19
- return { success: true, data: cached };
20
- }
21
- if (!config.siteUrl) {
22
- return {
23
- success: false,
24
- error: 'Site URL not configured for Search Console'
25
- };
26
- }
27
- // Set up authentication
28
- const authResult = await createSearchConsoleClient(config);
29
- if (!authResult.success) {
30
- return {
31
- success: false,
32
- error: authResult.error || 'Authentication failed'
33
- };
34
- }
35
- const searchconsole = authResult.client;
36
- // Calculate date ranges
37
- const currentEndDate = endDate ? new Date(endDate) : new Date();
38
- const currentStartDate = startDate ? new Date(startDate) : new Date(currentEndDate.getTime() - 30 * 24 * 60 * 60 * 1000);
39
- // Calculate previous period (same duration before the current period)
40
- const periodDuration = currentEndDate.getTime() - currentStartDate.getTime();
41
- const previousEndDate = new Date(currentStartDate.getTime() - 24 * 60 * 60 * 1000); // One day before start
42
- const previousStartDate = new Date(previousEndDate.getTime() - periodDuration);
43
- const currentStartStr = currentStartDate.toISOString().split('T')[0];
44
- const currentEndStr = currentEndDate.toISOString().split('T')[0];
45
- const previousStartStr = previousStartDate.toISOString().split('T')[0];
46
- const previousEndStr = previousEndDate.toISOString().split('T')[0];
47
- // Fetch current period data
48
- const currentResponse = await searchconsole.searchanalytics.query({
49
- siteUrl: config.siteUrl,
50
- requestBody: {
51
- startDate: currentStartStr,
52
- endDate: currentEndStr,
53
- dimensions: ['date'],
54
- rowLimit: 10000,
55
- },
56
- });
57
- // Fetch previous period data
58
- const previousResponse = await searchconsole.searchanalytics.query({
59
- siteUrl: config.siteUrl,
60
- requestBody: {
61
- startDate: previousStartStr,
62
- endDate: previousEndStr,
63
- dimensions: ['date'],
64
- rowLimit: 10000,
65
- },
66
- });
67
- // Create a map of previous period data by date
68
- const previousDataMap = new Map();
69
- previousResponse.data.rows?.forEach((row) => {
70
- const dateStr = row.keys?.[0] || '';
71
- if (dateStr) {
72
- previousDataMap.set(dateStr, {
73
- clicks: parseFloat(String(row.clicks || '0')),
74
- impressions: parseFloat(String(row.impressions || '0'))
75
- });
76
- }
77
- });
78
- // Combine current and previous period data
79
- const chartData = [];
80
- const daysInRange = Math.ceil((currentEndDate.getTime() - currentStartDate.getTime()) / (24 * 60 * 60 * 1000));
81
- for (let i = daysInRange - 1; i >= 0; i--) {
82
- const currentDate = new Date(currentEndDate);
83
- currentDate.setDate(currentDate.getDate() - i);
84
- const currentDateStr = currentDate.toISOString().split('T')[0];
85
- // Calculate corresponding previous period date
86
- const previousDate = new Date(currentDate.getTime() - periodDuration);
87
- const previousDateStr = previousDate.toISOString().split('T')[0];
88
- // Get current period data
89
- const currentRow = currentResponse.data.rows?.find((row) => row.keys?.[0] === currentDateStr);
90
- const currentClicks = parseFloat(String(currentRow?.clicks || '0'));
91
- const currentImpressions = parseFloat(String(currentRow?.impressions || '0'));
92
- // Get previous period data
93
- const previousData = previousDataMap.get(previousDateStr) || { clicks: 0, impressions: 0 };
94
- // Format date for display
95
- const formattedDate = currentDate.toLocaleDateString('en-US', {
96
- month: 'short',
97
- day: 'numeric'
98
- });
99
- chartData.push({
100
- date: formattedDate,
101
- currentImpressions: Math.round(currentImpressions),
102
- currentClicks: Math.round(currentClicks),
103
- previousImpressions: Math.round(previousData.impressions),
104
- previousClicks: Math.round(previousData.clicks),
105
- });
106
- }
107
- // Cache the result
108
- searchConsoleCache.set(cacheKey, chartData);
109
- return { success: true, data: chartData };
110
- }
111
- catch (error) {
112
- console.error('Google Search Console error:', error);
113
- return {
114
- success: false,
115
- error: error.message
116
- };
117
- }
118
- }
6
+ export {};
@@ -9,36 +9,30 @@ SiteHealthGoogleSearchConsole.propTypes = {
9
9
  endDate: PropTypes.string,
10
10
  };
11
11
  export function SiteHealthGoogleSearchConsole({ siteName, startDate, endDate }) {
12
- const fetchSearchConsoleData = async (site) => {
13
- const params = new URLSearchParams({ siteName: site });
14
- if (startDate)
15
- params.append('startDate', startDate);
16
- if (endDate)
17
- params.append('endDate', endDate);
18
- const response = await fetch(`/api/site-health/google-search-console?${params.toString()}`);
19
- if (!response.ok) {
20
- throw new Error(`Failed to fetch search console data: ${response.status}`);
21
- }
22
- const result = await response.json();
23
- if (!result.success) {
24
- // Handle specific error types
25
- if (result.error?.includes('invalid_grant') || result.error?.includes('authentication')) {
26
- throw new Error('Google Search Console authentication expired. Please re-authorize the application.');
27
- }
28
- else if (result.error?.includes('GSC Site URL not configured')) {
29
- throw new Error('GSC Site URL not configured for this site');
30
- }
31
- else {
32
- throw new Error(result.error || 'Failed to load search console data');
33
- }
34
- }
35
- return result.data;
36
- };
37
- return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Search Console", columnSpan: 2, fetchData: fetchSearchConsoleData, children: (data) => {
38
- if (!data || data.length === 0) {
12
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "Google Search Console", columnSpan: 2, endpoint: {
13
+ endpoint: '/api/site-health/google-search-console',
14
+ params: {
15
+ ...(startDate && { startDate }),
16
+ ...(endDate && { endDate }),
17
+ },
18
+ responseTransformer: (result) => result.data, // Extract the data array from the response
19
+ }, children: (data) => {
20
+ // Ensure data is an array
21
+ if (!data || !Array.isArray(data) || data.length === 0) {
39
22
  return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "No indexing data available for the selected date range" }) }));
40
23
  }
41
- return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: data, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Impressions vs Clicks (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
24
+ // Filter out any invalid data points
25
+ const validData = data.filter((point) => point &&
26
+ typeof point === 'object' &&
27
+ typeof point.date === 'string' &&
28
+ typeof point.currentImpressions === 'number' &&
29
+ typeof point.currentClicks === 'number' &&
30
+ typeof point.previousImpressions === 'number' &&
31
+ typeof point.previousClicks === 'number');
32
+ if (validData.length === 0) {
33
+ return (_jsx("div", { className: "health-visualization-placeholder", children: _jsx("div", { className: "health-text-secondary", children: "Invalid data format received from Google Search Console API." }) }));
34
+ }
35
+ return (_jsx("div", { children: _jsx("div", { style: { width: '100%', height: '400px', border: '1px solid #ddd' }, children: _jsx(ResponsiveContainer, { width: "100%", height: "100%", children: _jsxs(ComposedChart, { data: validData, margin: { top: 40, right: 30, left: 20, bottom: 5 }, children: [_jsx("text", { x: "50%", y: 20, textAnchor: "middle", fontSize: "16", fontWeight: "bold", fill: "#374151", children: "Impressions vs Clicks (Current vs Previous Period)" }), _jsx(CartesianGrid, { strokeDasharray: "3 3" }), _jsx(XAxis, { dataKey: "date", tick: { fontSize: 12 }, angle: -45, textAnchor: "end", height: 60 }), _jsx(YAxis, { tick: { fontSize: 12 } }), _jsx(Tooltip, { formatter: (value, name) => [
42
36
  value?.toLocaleString() || '0',
43
37
  name || 'Unknown'
44
38
  ], labelFormatter: (label) => `Date: ${label}` }), _jsx(Legend, { wrapperStyle: {
@@ -2,7 +2,7 @@
2
2
  import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
3
3
  import PropTypes from 'prop-types';
4
4
  import { SiteHealthTemplate } from './site-health-template';
5
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatAuditItem, getAuditScoreIcon } from './site-health-utils';
6
6
  /**
7
7
  * Restructure audits by type with all pages and their individual results
8
8
  */
@@ -61,42 +61,14 @@ function restructureAuditsByType(pagesAnalyzed) {
61
61
  });
62
62
  return restructuredAudits.sort((a, b) => (b.score || 0) - (a.score || 0));
63
63
  }
64
- // Fetch real SEO data from API
65
- async function fetchOnSiteSEOData(siteName) {
66
- try {
67
- const response = await fetch(`/api/site-health/on-site-seo?siteName=${encodeURIComponent(siteName)}`);
68
- if (!response.ok) {
69
- throw new Error(`API request failed: ${response.status} ${response.statusText}`);
70
- }
71
- const data = await response.json();
72
- if (data.status === 'error') {
73
- throw new Error(data.error || 'SEO analysis failed');
74
- }
75
- // Process data to aggregate audits by type across all pages
76
- // const aggregatedOnPageAudits = restructureAuditsByType(data.pagesAnalyzed);
77
- return data;
78
- }
79
- catch (error) {
80
- console.error('Error fetching SEO data:', error);
81
- // Return error structure that the template can display
82
- return {
83
- site: siteName,
84
- url: '',
85
- overallScore: null,
86
- pagesAnalyzed: [],
87
- onSiteAudits: [],
88
- totalPages: 0,
89
- timestamp: new Date().toISOString(),
90
- status: 'error',
91
- error: error instanceof Error ? error.message : 'Failed to fetch SEO data'
92
- };
93
- }
94
- }
95
64
  SiteHealthOnSiteSEO.propTypes = {
96
65
  siteName: PropTypes.string.isRequired,
97
66
  };
98
67
  export function SiteHealthOnSiteSEO({ siteName }) {
99
- return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "On-Site SEO", fetchData: fetchOnSiteSEOData, children: (data) => {
68
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "On-Site SEO", endpoint: {
69
+ endpoint: '/api/site-health/on-site-seo',
70
+ responseTransformer: (result) => result.data, // Extract the data from the response
71
+ }, children: (data) => {
100
72
  if (!data)
101
73
  return null;
102
74
  if (data.status === 'error') {
@@ -104,12 +76,6 @@ export function SiteHealthOnSiteSEO({ siteName }) {
104
76
  }
105
77
  // Process data to aggregate audits by type across all pages
106
78
  const aggregatedOnPageAudits = restructureAuditsByType(data.pagesAnalyzed);
107
- const getScoreColor = (score) => {
108
- return getScoreIndicator(score).color;
109
- };
110
- const getAuditScoreIcon = (score) => {
111
- return getScoreIndicator(score).icon;
112
- };
113
79
  const formatPageIssue = (item) => {
114
80
  // Handle page-specific results from restructured data
115
81
  if (item.page && typeof item.page === 'string') {
@@ -153,52 +119,12 @@ export function SiteHealthOnSiteSEO({ siteName }) {
153
119
  // Fallback to original formatting
154
120
  return formatAuditItem(item);
155
121
  };
156
- const formatAuditItem = (item) => {
157
- // Handle specific SEO element formatting based on the user's list
158
- // Title Tags
159
- if (item.title && typeof item.title === 'string') {
160
- return `Title: "${item.title}" (${item.length || 'unknown'} chars)`;
161
- }
162
- // Meta tags
163
- if (item.name && typeof item.name === 'string' && item.content) {
164
- return `${item.name}: ${String(item.content)}`;
165
- }
166
- // Headings
167
- if (item.tag && typeof item.tag === 'string' && item.tag.match(/^h[1-6]$/i)) {
168
- return `${item.tag}: "${item.text || ''}"`;
169
- }
170
- // Links and URLs
171
- if (item.url && typeof item.url === 'string') {
172
- if (item.issue && typeof item.issue === 'string') {
173
- return `${item.url} - ${item.issue}`;
174
- }
175
- return item.url;
176
- }
177
- // Images with alt tags
178
- if (item.src && typeof item.src === 'string') {
179
- return `Image: ${item.src} ${item.alt ? '(has alt)' : '(missing alt)'}`;
180
- }
181
- // Robots.txt directives
182
- if (item.directive && typeof item.directive === 'string') {
183
- return `${item.directive}: ${item.value || ''}`;
184
- }
185
- // Sitemap entries
186
- if (item.loc && typeof item.loc === 'string') {
187
- return item.loc;
188
- }
189
- // Default formatting
190
- return Object.entries(item)
191
- .map(([key, value]) => `${key}: ${String(value)}`)
192
- .join(', ');
193
- };
194
- return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: data.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", data.url] }), data.overallScore !== null && (_jsx("div", { className: "health-score-container", children: _jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: "On-Site SEO Score" }), _jsxs("div", { className: "health-score-value", style: { color: getScoreColor(data.overallScore) }, children: [Math.round((data.overallScore || 0) * 100), "%"] }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
195
- width: `${(data.overallScore || 0) * 100}%`,
196
- backgroundColor: getScoreColor(data.overallScore)
197
- } }) })] }) })), aggregatedOnPageAudits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Page SEO Audits" }), _jsx("div", { className: "health-audit-list", children: aggregatedOnPageAudits
122
+ return (_jsxs(_Fragment, { children: [aggregatedOnPageAudits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Page SEO Audits" }), _jsx("div", { className: "health-audit-list", children: aggregatedOnPageAudits
198
123
  .filter(audit => audit.scoreDisplayMode !== 'notApplicable')
124
+ .sort((a, b) => (b.score || 0) - (a.score || 0))
199
125
  .map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: [audit.score === null ? '(N/A)' : `(${Math.round((audit.score || 0) * 100)}%)`, " ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
200
126
  .filter((item) => item.score !== 1)
201
- .map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatPageIssue(item) }, idx))) }) }))] })] }, audit.id))) })] })), data.onSiteAudits.length > 0 && (_jsxs("div", { style: { marginTop: data.pagesAnalyzed.length > 0 ? '2rem' : '0' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Site SEO Audits" }), _jsx("div", { className: "health-audit-list", children: data.onSiteAudits
127
+ .map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatPageIssue(item) }, idx))) }) }))] })] }, audit.id))) })] })), data.onSiteAudits.length > 0 && (_jsxs("div", { style: { marginTop: aggregatedOnPageAudits.length > 0 ? '2rem' : '0' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "On-Site SEO Audits" }), _jsx("div", { className: "health-audit-list", children: data.onSiteAudits
202
128
  .filter(audit => audit.scoreDisplayMode !== 'notApplicable')
203
129
  .sort((a, b) => (b.score || 0) - (a.score || 0))
204
130
  .map((audit) => (_jsxs("div", { className: "health-audit-item", children: [_jsx("span", { className: "health-audit-icon", children: getAuditScoreIcon(audit.score) }), _jsxs("div", { className: "health-audit-content", children: [_jsxs("span", { className: "health-audit-title", children: [audit.score === null ? '(N/A)' : `(${Math.round((audit.score || 0) * 100)}%)`, " ", audit.title] }), audit.displayValue && audit.score !== 1 && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details && audit.details.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && audit.score !== 1 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items
@@ -1,22 +1,16 @@
1
1
  'use client';
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { useCallback } from 'react';
4
3
  import PropTypes from 'prop-types';
5
4
  import { SiteHealthTemplate } from './site-health-template';
6
- import { getScoreIndicator } from './site-health-indicators';
5
+ import { formatScore, getScoreColor } from './site-health-utils';
7
6
  SiteHealthOverview.propTypes = {
8
7
  siteName: PropTypes.string.isRequired,
9
8
  };
10
9
  export function SiteHealthOverview({ siteName }) {
11
- const fetchCWVData = useCallback(async (site) => {
12
- const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
13
- const result = await response.json();
14
- if (!result.success) {
15
- throw new Error(result.error || 'Failed to fetch Core Web Vitals data');
16
- }
17
- return result;
18
- }, []);
19
- return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Overview", fetchData: fetchCWVData, children: (data) => {
10
+ return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Overview", endpoint: {
11
+ endpoint: '/api/site-health/core-web-vitals',
12
+ responseTransformer: (result) => result, // Result is already in the correct format
13
+ }, children: (data) => {
20
14
  if (!data?.data || data.data.length === 0) {
21
15
  return (_jsx("p", { style: { color: '#6b7280' }, children: "No site health data available for this site." }));
22
16
  }
@@ -25,14 +19,6 @@ export function SiteHealthOverview({ siteName }) {
25
19
  return (_jsxs("p", { style: { color: '#ef4444', fontSize: '0.875rem' }, children: ["Error: ", siteData.error] }));
26
20
  }
27
21
  // Helper functions
28
- const getScoreColor = (score) => {
29
- return getScoreIndicator(score).color;
30
- };
31
- const formatScore = (score) => {
32
- if (score === null)
33
- return 'N/A';
34
- return `${Math.round(score * 100)}%`;
35
- };
36
22
  const getStatusColor = (status) => {
37
23
  switch (status) {
38
24
  case 'good': return '#10b981'; // green