@pixelated-tech/components 3.5.12 → 3.5.14

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 (37) hide show
  1. package/dist/components/admin/site-health/seo-metrics.config.json +38 -18
  2. package/dist/components/admin/site-health/site-health-accessibility.js +2 -94
  3. package/dist/components/admin/site-health/site-health-axe-core.js +2 -2
  4. package/dist/components/admin/site-health/site-health-cloudwatch.js +2 -2
  5. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +1 -1
  6. package/dist/components/admin/site-health/site-health-github.js +3 -3
  7. package/dist/components/admin/site-health/site-health-google-analytics.js +1 -1
  8. package/dist/components/admin/site-health/site-health-google-search-console.js +1 -1
  9. package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +275 -3
  10. package/dist/components/admin/site-health/site-health-on-site-seo.js +5 -56
  11. package/dist/components/admin/site-health/site-health-overview.js +1 -9
  12. package/dist/components/admin/site-health/site-health-performance.js +2 -158
  13. package/dist/components/admin/site-health/site-health-security.js +2 -139
  14. package/dist/components/admin/site-health/site-health-seo.js +2 -94
  15. package/dist/components/admin/site-health/site-health-utils.js +170 -0
  16. package/dist/components/admin/site-health/site-health.css +33 -3
  17. package/dist/components/cms/smartimage.js +28 -8
  18. package/dist/components/general/table.js +2 -2
  19. package/dist/components/sitebuilder/config/ConfigBuilder.js +12 -3
  20. package/dist/index.adminclient.js +1 -0
  21. package/dist/index.adminserver.js +1 -0
  22. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
  23. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
  24. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
  25. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
  26. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
  27. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
  28. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
  29. package/dist/types/components/admin/site-health/site-health-utils.d.ts +17 -0
  30. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -0
  31. package/dist/types/components/cms/smartimage.d.ts.map +1 -1
  32. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  33. package/dist/types/index.adminclient.d.ts +1 -0
  34. package/dist/types/index.adminserver.d.ts +1 -0
  35. package/dist/types/tests/test-seo-logic.d.ts +2 -0
  36. package/dist/types/tests/test-seo-logic.d.ts.map +1 -0
  37. package/package.json +4 -4
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback } from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { SiteHealthTemplate } from './site-health-template';
6
- import { getScoreIndicator } from './site-health-indicators';
6
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
7
7
  SiteHealthSecurity.propTypes = {
8
8
  siteName: PropTypes.string.isRequired,
9
9
  };
@@ -17,143 +17,6 @@ export function SiteHealthSecurity({ siteName }) {
17
17
  };
18
18
  }, []);
19
19
  return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - Site Security", fetchData: fetchSecurityData, children: (data) => {
20
- const getScoreColor = (score) => {
21
- return getScoreIndicator(score).color;
22
- };
23
- const getAuditScoreIcon = (score) => {
24
- return getScoreIndicator(score).icon;
25
- };
26
- // Helper function to display audit item details
27
- const formatAuditItem = (item, auditTitle) => {
28
- // Handle URLs
29
- if (item.url && typeof item.url === 'string') {
30
- return item.url;
31
- }
32
- // Handle sources (like JavaScript files)
33
- if (item.source && typeof item.source === 'string') {
34
- return item.source;
35
- }
36
- // Handle text descriptions
37
- if (item.text && typeof item.text === 'string') {
38
- return item.text;
39
- }
40
- // Handle entities (like "Google Tag Manager")
41
- if (item.entity && typeof item.entity === 'string') {
42
- return item.entity;
43
- }
44
- // Handle nodes with selectors
45
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
46
- return `Element: ${item.node.selector}`;
47
- }
48
- // Handle nodes with snippets
49
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
50
- const snippet = item.node.snippet;
51
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
52
- }
53
- // Handle origins (like domains)
54
- if (item.origin && typeof item.origin === 'string') {
55
- return item.origin;
56
- }
57
- // Handle labels
58
- if (item.label && typeof item.label === 'string') {
59
- return item.label;
60
- }
61
- // Handle numeric values with units
62
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
63
- const value = item.value;
64
- return `${value.value}${item.unit || ''}`;
65
- }
66
- // Handle statistics
67
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
68
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
69
- const value = item.value;
70
- return `${item.statistic}: ${value.value}`;
71
- }
72
- return item.statistic;
73
- }
74
- // Handle timing data with more context
75
- if (item.duration && typeof item.duration === 'number') {
76
- const duration = item.duration;
77
- let context = '';
78
- if (item.url && typeof item.url === 'string') {
79
- context = ` for ${item.url}`;
80
- }
81
- else if (item.source && typeof item.source === 'string') {
82
- context = ` for ${item.source}`;
83
- }
84
- else if (item.name && typeof item.name === 'string') {
85
- context = ` for ${item.name}`;
86
- }
87
- return `${duration.toFixed(2)}ms${context}`;
88
- }
89
- // Handle response times
90
- if (item.responseTime && typeof item.responseTime === 'number') {
91
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
92
- return `${item.responseTime.toFixed(2)}ms response time${url}`;
93
- }
94
- // Handle start/end times
95
- if ((item.startTime || item.endTime) && typeof (item.startTime || item.endTime) === 'number') {
96
- const start = item.startTime && typeof item.startTime === 'number' ? item.startTime.toFixed(2) : '?';
97
- const end = item.endTime && typeof item.endTime === 'number' ? item.endTime.toFixed(2) : '?';
98
- const url = (item.url && typeof item.url === 'string') ? ` for ${item.url}` : '';
99
- return `${start}ms - ${end}ms${url}`;
100
- }
101
- // Handle transfer size with timing
102
- if (item.transferSize && typeof item.transferSize === 'number' && item.duration && typeof item.duration === 'number') {
103
- const size = (item.transferSize / 1024).toFixed(1);
104
- const time = item.duration.toFixed(2);
105
- const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
106
- return `${size} KB in ${time}ms${url}`;
107
- }
108
- // Handle main thread time
109
- if (item.mainThreadTime && typeof item.mainThreadTime === 'number') {
110
- return `${item.mainThreadTime.toFixed(1)}ms`;
111
- }
112
- // For other objects, try to find a meaningful display
113
- if (item.group && typeof item.group === 'string') {
114
- return item.group;
115
- }
116
- if (item.type && typeof item.type === 'string') {
117
- return item.type;
118
- }
119
- // If we can't find anything meaningful, provide a generic description
120
- // This handles raw timing data that might be from various performance metrics
121
- if (typeof item === 'number') {
122
- let context = '';
123
- if (auditTitle) {
124
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
125
- context = ' server response';
126
- }
127
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
128
- context = ' network request';
129
- }
130
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
131
- context = ' render blocking';
132
- }
133
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
134
- context = ' JavaScript';
135
- }
136
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
137
- context = ' media resource';
138
- }
139
- }
140
- return `${item.toFixed(2)}ms${context}`;
141
- }
142
- if (item.value && typeof item.value === 'number') {
143
- const unit = item.unit || 'ms';
144
- let context = '';
145
- if (auditTitle && unit === 'ms') {
146
- if (auditTitle.toLowerCase().includes('server')) {
147
- context = ' server time';
148
- }
149
- else if (auditTitle.toLowerCase().includes('network')) {
150
- context = ' network time';
151
- }
152
- }
153
- return `${item.value.toFixed(2)}${unit}${context}`;
154
- }
155
- return 'Details available';
156
- };
157
20
  const psiData = data?.psiData?.data?.[0];
158
21
  if (!psiData) {
159
22
  return (_jsx("p", { style: { color: '#6b7280' }, children: "No security data available for this site." }));
@@ -164,7 +27,7 @@ export function SiteHealthSecurity({ siteName }) {
164
27
  return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteName.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", psiData.url] }), psiData.scores['best-practices'] !== null && (_jsx("div", { className: "health-score-container", children: _jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: "Best Practices Score" }), _jsxs("div", { className: "health-score-value", style: { color: getScoreColor(psiData.scores['best-practices']) }, children: [Math.round((psiData.scores['best-practices'] || 0) * 100), "%"] }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
165
28
  width: `${(psiData.scores['best-practices'] || 0) * 100}%`,
166
29
  backgroundColor: getScoreColor(psiData.scores['best-practices'])
167
- } }) })] }) })), psiData.categories['best-practices'] && psiData.categories['best-practices'].audits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "Security Best Practices" }), _jsx("div", { className: "space-y-2", children: psiData.categories['best-practices'].audits
30
+ } }) })] }) })), psiData.categories['best-practices'] && psiData.categories['best-practices'].audits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "Security Best Practices" }), _jsx("div", { className: "health-audit-list", children: psiData.categories['best-practices'].audits
168
31
  .filter((audit) => audit.scoreDisplayMode !== 'notApplicable')
169
32
  .sort((a, b) => (b.score || 0) - (a.score || 0))
170
33
  .slice(0, 20)
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { useCallback } from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import { SiteHealthTemplate } from './site-health-template';
6
- import { getScoreIndicator } from './site-health-indicators';
6
+ import { formatAuditItem, getAuditScoreIcon, getScoreColor } from './site-health-utils';
7
7
  SiteHealthSEO.propTypes = {
8
8
  siteName: PropTypes.string.isRequired,
9
9
  };
@@ -17,98 +17,6 @@ export function SiteHealthSEO({ siteName }) {
17
17
  return result;
18
18
  }, []);
19
19
  return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "PageSpeed - SEO", fetchData: fetchSEOData, children: (data) => {
20
- const getScoreColor = (score) => {
21
- return getScoreIndicator(score).color;
22
- };
23
- const getAuditScoreIcon = (score) => {
24
- return getScoreIndicator(score).icon;
25
- };
26
- // Helper function to display audit item details
27
- const formatAuditItem = (item, auditTitle) => {
28
- // Handle URLs
29
- if (item.url && typeof item.url === 'string') {
30
- return item.url;
31
- }
32
- // Handle sources (like JavaScript files)
33
- if (item.source && typeof item.source === 'string') {
34
- return item.source;
35
- }
36
- // Handle text descriptions
37
- if (item.text && typeof item.text === 'string') {
38
- return item.text;
39
- }
40
- // Handle entities (like "Google Tag Manager")
41
- if (item.entity && typeof item.entity === 'string') {
42
- return item.entity;
43
- }
44
- // Handle nodes with selectors
45
- if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
46
- return `Element: ${item.node.selector}`;
47
- }
48
- // Handle nodes with snippets
49
- if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
50
- const snippet = item.node.snippet;
51
- return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
52
- }
53
- // Handle origins (like domains)
54
- if (item.origin && typeof item.origin === 'string') {
55
- return item.origin;
56
- }
57
- // Handle labels
58
- if (item.label && typeof item.label === 'string') {
59
- return item.label;
60
- }
61
- // Handle numeric values with units
62
- if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
63
- const value = item.value;
64
- return `${value.value}${item.unit || ''}`;
65
- }
66
- // Handle statistics
67
- if (item.statistic && typeof item.statistic === 'string' && item.value) {
68
- if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
69
- const value = item.value;
70
- return `${item.statistic}: ${value.value}`;
71
- }
72
- return item.statistic;
73
- }
74
- // Handle timing data with audit context
75
- if (typeof item === 'number') {
76
- let context = '';
77
- if (auditTitle) {
78
- if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
79
- context = ' server response';
80
- }
81
- else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
82
- context = ' network request';
83
- }
84
- else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
85
- context = ' render blocking';
86
- }
87
- else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
88
- context = ' JavaScript';
89
- }
90
- else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
91
- context = ' media resource';
92
- }
93
- }
94
- return `${item.toFixed(2)}ms${context}`;
95
- }
96
- if (item.value && typeof item.value === 'number') {
97
- const unit = item.unit || 'ms';
98
- let context = '';
99
- if (auditTitle && unit === 'ms') {
100
- if (auditTitle.toLowerCase().includes('server')) {
101
- context = ' server time';
102
- }
103
- else if (auditTitle.toLowerCase().includes('network')) {
104
- context = ' network time';
105
- }
106
- }
107
- return `${item.value.toFixed(2)}${unit}${context}`;
108
- }
109
- // If we can't find anything meaningful, show a generic message
110
- return 'Details available';
111
- };
112
20
  if (!data?.data || data.data.length === 0) {
113
21
  return (_jsx("p", { style: { color: '#6b7280' }, children: "No SEO data available for this site." }));
114
22
  }
@@ -119,7 +27,7 @@ export function SiteHealthSEO({ siteName }) {
119
27
  return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteData.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", siteData.url] }), siteData.scores.seo !== null && (_jsx("div", { className: "health-score-container", children: _jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: "SEO Score" }), _jsxs("div", { className: "health-score-value", style: { color: getScoreColor(siteData.scores.seo) }, children: [Math.round((siteData.scores.seo || 0) * 100), "%"] }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
120
28
  width: `${(siteData.scores.seo || 0) * 100}%`,
121
29
  backgroundColor: getScoreColor(siteData.scores.seo)
122
- } }) })] }) })), siteData.categories.seo && siteData.categories.seo.audits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "SEO Issues & Recommendations" }), _jsx("div", { className: "space-y-2", children: siteData.categories.seo.audits
30
+ } }) })] }) })), siteData.categories.seo && siteData.categories.seo.audits.length > 0 && (_jsxs("div", { children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "SEO Issues & Recommendations" }), _jsx("div", { className: "health-audit-list", children: siteData.categories.seo.audits
123
31
  .filter((audit) => audit.scoreDisplayMode !== 'notApplicable')
124
32
  .sort((a, b) => (b.score || 0) - (a.score || 0))
125
33
  .slice(0, 20)
@@ -0,0 +1,170 @@
1
+ // Shared utilities for site-health components
2
+ // This file contains common functions used across multiple site-health components
3
+ import { getScoreIndicator } from './site-health-indicators';
4
+ /**
5
+ * Gets the icon for a score
6
+ */
7
+ export function getAuditScoreIcon(score) {
8
+ return getScoreIndicator(score).icon;
9
+ }
10
+ /**
11
+ * Gets the color for a score
12
+ */
13
+ export function getScoreColor(score) {
14
+ return getScoreIndicator(score).color;
15
+ }
16
+ /**
17
+ * Formats a score for display
18
+ */
19
+ export function formatScore(score) {
20
+ if (score === null)
21
+ return 'N/A';
22
+ return `${Math.round(score * 100)}%`;
23
+ }
24
+ /**
25
+ * Formats audit item details for display
26
+ */
27
+ export function formatAuditItem(item, auditTitle) {
28
+ // Handle URLs
29
+ if (item.url && typeof item.url === 'string') {
30
+ return item.url;
31
+ }
32
+ // Handle sources (like JavaScript files)
33
+ if (item.source && typeof item.source === 'string') {
34
+ return item.source;
35
+ }
36
+ // Handle text descriptions
37
+ if (item.text && typeof item.text === 'string') {
38
+ return item.text;
39
+ }
40
+ // Handle entities (like "Google Tag Manager")
41
+ if (item.entity && typeof item.entity === 'string') {
42
+ return item.entity;
43
+ }
44
+ // Handle nodes with selectors
45
+ if (item.node && typeof item.node === 'object' && 'selector' in item.node) {
46
+ return `Element: ${item.node.selector}`;
47
+ }
48
+ // Handle nodes with snippets
49
+ if (item.node && typeof item.node === 'object' && 'snippet' in item.node) {
50
+ const snippet = item.node.snippet;
51
+ return `Element: ${snippet.length > 50 ? snippet.substring(0, 50) + '...' : snippet}`;
52
+ }
53
+ // Handle origins (like domains)
54
+ if (item.origin && typeof item.origin === 'string') {
55
+ return item.origin;
56
+ }
57
+ // Handle labels
58
+ if (item.label && typeof item.label === 'string') {
59
+ return item.label;
60
+ }
61
+ // Handle numeric values with units
62
+ if (item.value && typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
63
+ const value = item.value;
64
+ return `${value.value}${item.unit || ''}`;
65
+ }
66
+ // Handle statistics
67
+ if (item.statistic && typeof item.statistic === 'string' && item.value) {
68
+ if (typeof item.value === 'object' && 'type' in item.value && item.value.type === 'numeric') {
69
+ const value = item.value;
70
+ return `${item.statistic}: ${value.value}`;
71
+ }
72
+ return item.statistic;
73
+ }
74
+ // Handle timing data with audit context
75
+ if (typeof item === 'number') {
76
+ let context = '';
77
+ if (auditTitle) {
78
+ if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
79
+ context = ' server response';
80
+ }
81
+ else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
82
+ context = ' network request';
83
+ }
84
+ else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
85
+ context = ' render blocking';
86
+ }
87
+ else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
88
+ context = ' JavaScript';
89
+ }
90
+ else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
91
+ context = ' media resource';
92
+ }
93
+ }
94
+ return `${item.toFixed(2)}ms${context}`;
95
+ }
96
+ if (item.value && typeof item.value === 'number') {
97
+ const unit = item.unit || 'ms';
98
+ let context = '';
99
+ if (auditTitle && unit === 'ms') {
100
+ if (auditTitle.toLowerCase().includes('server')) {
101
+ context = ' server time';
102
+ }
103
+ else if (auditTitle.toLowerCase().includes('network')) {
104
+ context = ' network time';
105
+ }
106
+ }
107
+ return `${item.value.toFixed(2)}${unit}${context}`;
108
+ }
109
+ // Handle timing data with more context
110
+ if (item.duration && typeof item.duration === 'number') {
111
+ const duration = item.duration;
112
+ let context = '';
113
+ if (item.url && typeof item.url === 'string') {
114
+ context = ` for ${item.url}`;
115
+ }
116
+ else if (item.source && typeof item.source === 'string') {
117
+ context = ` for ${item.source}`;
118
+ }
119
+ else if (item.name && typeof item.name === 'string') {
120
+ context = ` for ${item.name}`;
121
+ }
122
+ else if (item.path && typeof item.path === 'string') {
123
+ context = ` for ${item.path}`;
124
+ }
125
+ else if (item.request && typeof item.request === 'string') {
126
+ context = ` for ${item.request}`;
127
+ }
128
+ return `${duration.toFixed(2)}ms${context}`;
129
+ }
130
+ // Handle response times
131
+ if (item.responseTime && typeof item.responseTime === 'number') {
132
+ const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
133
+ return `${item.responseTime.toFixed(2)}ms response time${url}`;
134
+ }
135
+ // Handle start/end times
136
+ if ((item.startTime || item.endTime) && typeof (item.startTime || item.endTime) === 'number') {
137
+ const start = item.startTime && typeof item.startTime === 'number' ? item.startTime.toFixed(2) : '?';
138
+ const end = item.endTime && typeof item.endTime === 'number' ? item.endTime.toFixed(2) : '?';
139
+ const url = (item.url && typeof item.url === 'string') ? ` for ${item.url}` : '';
140
+ return `${start}ms - ${end}ms${url}`;
141
+ }
142
+ // Handle transfer size with timing
143
+ if (item.transferSize && typeof item.transferSize === 'number' && item.duration && typeof item.duration === 'number') {
144
+ const size = (item.transferSize / 1024).toFixed(1);
145
+ const time = item.duration.toFixed(2);
146
+ const url = (item.url && typeof item.url === 'string') ? ` (${item.url})` : '';
147
+ return `${size} KB in ${time}ms${url}`;
148
+ }
149
+ // Handle main thread time
150
+ if (item.mainThreadTime && typeof item.mainThreadTime === 'number') {
151
+ return `${item.mainThreadTime.toFixed(1)}ms`;
152
+ }
153
+ // For other objects, try to find a meaningful display
154
+ if (item.group && typeof item.group === 'string') {
155
+ return item.group;
156
+ }
157
+ if (item.type && typeof item.type === 'string') {
158
+ return item.type;
159
+ }
160
+ // If we can't find anything meaningful, provide a generic description
161
+ // This handles raw timing data that might be from various performance metrics
162
+ if (typeof item === 'number') {
163
+ return `${item.toFixed(2)}ms`;
164
+ }
165
+ if (item.value && typeof item.value === 'number') {
166
+ const unit = item.unit || 'ms';
167
+ return `${item.value.toFixed(2)}${unit}`;
168
+ }
169
+ return 'Performance metric data available';
170
+ }
@@ -67,8 +67,7 @@
67
67
 
68
68
  .site-health-select select:focus {
69
69
  outline: none;
70
- ring: 2px;
71
- ring-color: var(--color-primary);
70
+ box-shadow: 0 0 0 2px var(--color-primary);
72
71
  border-color: var(--color-primary);
73
72
  }
74
73
 
@@ -418,10 +417,41 @@ td {
418
417
 
419
418
  table {
420
419
  width: 100%;
421
- table-layout: fixed;
422
420
  }
423
421
 
424
422
  th, td {
425
423
  word-wrap: break-word;
426
424
  overflow-wrap: break-word;
427
425
  }
426
+
427
+ /* Helper & Semantic classes */
428
+ .health-audit-list > * + * { margin-top: 0.0rem; }
429
+ .health-section-list > * + * { margin-top: 1rem; }
430
+
431
+ .health-text-muted { color: #9ca3af; }
432
+ .health-text-secondary { color: var(--color-text-secondary); }
433
+ .health-text-error { color: var(--color-error); }
434
+
435
+ .health-visualization-placeholder {
436
+ display: flex;
437
+ align-items: center;
438
+ justify-content: center;
439
+ height: 16rem;
440
+ }
441
+
442
+ .health-version-tag {
443
+ display: inline-block;
444
+ padding: 0.25rem 0.5rem;
445
+ font-size: 0.75rem;
446
+ line-height: 1;
447
+ background-color: #dcfce7;
448
+ color: #166534;
449
+ border-radius: 0.25rem;
450
+ font-weight: 500;
451
+ }
452
+
453
+ .health-empty-state {
454
+ text-align: center;
455
+ padding: 1rem 0;
456
+ color: var(--color-text-secondary);
457
+ }
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import { jsx as _jsx } from "react/jsx-runtime";
3
- import React from 'react';
3
+ import React, { useState } from 'react';
4
4
  import PropTypes from 'prop-types';
5
5
  import Image from 'next/image';
6
6
  import { buildCloudinaryUrl } from './cloudinary';
@@ -67,8 +67,27 @@ SmartImage.propTypes = {
67
67
  export function SmartImage(props) {
68
68
  const config = usePixelatedConfig();
69
69
  const cloudCfg = config?.cloudinary;
70
- const variant = props.variant || 'cloudinary';
70
+ // State to track current variant - only changes on actual errors (rare)
71
+ const [currentVariant, setCurrentVariant] = useState(props.variant || 'cloudinary');
72
+ const handleError = (error) => {
73
+ if (currentVariant === 'cloudinary') {
74
+ console.warn(`SmartImage: Cloudinary variant failed for "${props.src}", falling back to Next.js Image`, error);
75
+ setCurrentVariant('nextjs');
76
+ }
77
+ else if (currentVariant === 'nextjs') {
78
+ console.warn(`SmartImage: Next.js Image variant failed for "${props.src}", falling back to HTML img`, error);
79
+ setCurrentVariant('img');
80
+ }
81
+ // No more fallbacks after 'img'
82
+ };
83
+ // Reset variant if props change (different image)
84
+ React.useEffect(() => {
85
+ setCurrentVariant(props.variant || 'cloudinary');
86
+ }, [props.src, props.variant]);
87
+ const variant = currentVariant;
71
88
  const newProps = { ...props };
89
+ // Always create ref to maintain consistent hook count across re-renders
90
+ const imgRef = React.useRef(null);
72
91
  newProps.cloudinaryEnv = safeString(props.cloudinaryEnv ?? cloudCfg?.product_env);
73
92
  newProps.cloudinaryDomain = safeString(cloudCfg?.baseUrl ?? CLOUDINARY_DOMAIN);
74
93
  newProps.cloudinaryTransforms = safeString(CLOUDINARY_TRANSFORMS ?? cloudCfg?.transforms);
@@ -102,7 +121,7 @@ export function SmartImage(props) {
102
121
  });
103
122
  if (newProps.width) {
104
123
  const widths = [Math.ceil(newProps.width * 0.5), newProps.width, Math.ceil(newProps.width * 1.5), Math.ceil(newProps.width * 2)];
105
- newProps.srcSet = generateSrcSet(String(newProps.src), newProps.cloudinaryEnv, widths, {
124
+ newProps.srcSet = generateSrcSet(newProps.src, newProps.cloudinaryEnv, widths, {
106
125
  quality: newProps.quality,
107
126
  transforms: newProps.cloudinaryTransforms ?? undefined,
108
127
  cloudinaryDomain: newProps.cloudinaryDomain
@@ -111,7 +130,7 @@ export function SmartImage(props) {
111
130
  }
112
131
  else {
113
132
  const breakpoints = [320, 640, 768, 1024, 1280, 1536];
114
- newProps.srcSet = generateSrcSet(String(newProps.src), newProps.cloudinaryEnv, breakpoints, {
133
+ newProps.srcSet = generateSrcSet(newProps.src, newProps.cloudinaryEnv, breakpoints, {
115
134
  quality: newProps.quality,
116
135
  transforms: newProps.cloudinaryTransforms ?? undefined,
117
136
  cloudinaryDomain: newProps.cloudinaryDomain
@@ -135,13 +154,14 @@ export function SmartImage(props) {
135
154
  delete newProps.cloudinaryTransforms;
136
155
  if (variant !== 'img') {
137
156
  try {
138
- return (_jsx(Image, { ...newProps, src: newProps.src, alt: newProps.alt }));
157
+ return (_jsx(Image, { ...newProps, src: newProps.src, alt: newProps.alt, onError: handleError }));
139
158
  }
140
159
  catch (e) {
141
- if (typeof console !== 'undefined')
142
- console.warn('next/image unavailable, falling back to <img>', e);
160
+ console.warn(`SmartImage: Next.js Image threw exception for "${props.src}", falling back to plain img`, e);
161
+ // Force fallback to img variant
162
+ setCurrentVariant('img');
143
163
  }
144
164
  }
145
165
  /* ===== IMG VARIANT ===== */
146
- return (_jsx("img", { ...newProps, ref: React.useRef(null), alt: newProps.alt }));
166
+ return (_jsx("img", { ...newProps, ref: imgRef, alt: newProps.alt }));
147
167
  }
@@ -25,8 +25,8 @@ export function Table(props) {
25
25
  function getHeadings(data) {
26
26
  const headings = Object.keys(data[0]).map((key, i) => {
27
27
  return (props.sortable && props.sortable == true)
28
- ? _jsxs("th", { onClick: () => { sortTable(key); }, children: [key, " ", _jsx("span", { className: "sortArrow" })] }, i)
29
- : _jsx("th", { children: key }, i);
28
+ ? _jsxs("th", { onClick: () => { sortTable(key); }, children: [_jsx("span", { children: key }), " ", _jsx("span", { className: "sortArrow" })] }, i)
29
+ : _jsx("th", { children: _jsx("span", { children: key }) }, i);
30
30
  });
31
31
  return _jsx("tr", { children: headings });
32
32
  }
@@ -229,13 +229,22 @@ export function ConfigBuilder(props) {
229
229
  ...field.props,
230
230
  value: (config.visualdesign && config.visualdesign[field.props.name]) ? (config.visualdesign[field.props.name].value ?? config.visualdesign[field.props.name]) : '',
231
231
  defaultValue: (config.visualdesign && config.visualdesign[field.props.name]) ? (config.visualdesign[field.props.name].value ?? config.visualdesign[field.props.name]) : field.props.defaultValue || '',
232
- onChange: (event) => {
233
- const value = event.target.value;
232
+ onChange: (value) => {
233
+ // Handle both direct values and event objects
234
+ let actualValue = value;
235
+ if (value && typeof value === 'object' && value.target) {
236
+ actualValue = value.target.value;
237
+ }
234
238
  setConfig((prev) => ({
235
239
  ...prev,
236
240
  visualdesign: {
237
241
  ...(prev.visualdesign || {}),
238
- [field.props.name]: { value }
242
+ [field.props.name]: {
243
+ ...(prev.visualdesign && prev.visualdesign[field.props.name]
244
+ ? prev.visualdesign[field.props.name]
245
+ : {}),
246
+ value: actualValue
247
+ }
239
248
  }
240
249
  }));
241
250
  }
@@ -23,6 +23,7 @@ export * from './components/admin/site-health/site-health-indicators';
23
23
  export * from './components/admin/site-health/site-health-on-site-seo.integration';
24
24
  export * from './components/admin/site-health/site-health-types';
25
25
  export * from './components/admin/site-health/site-health-performance';
26
+ export * from './components/admin/site-health/site-health-utils';
26
27
  export * from './components/admin/site-health/site-health-cache';
27
28
  export * from './components/admin/site-health/site-health-indicators';
28
29
  export * from './components/admin/site-health/site-health-on-site-seo.integration';
@@ -14,6 +14,7 @@ export * from './components/admin/site-health/site-health-cloudwatch.integration
14
14
  export * from './components/admin/site-health/seo-constants';
15
15
  export * from './components/admin/site-health/site-health-security.integration';
16
16
  export * from './components/admin/site-health/site-health-types';
17
+ export * from './components/admin/site-health/site-health-utils';
17
18
  export * from './components/admin/site-health/site-health-uptime.integration';
18
19
  export * from './components/admin/sites/sites.integration';
19
20
  export * from './components/cms/contentful.management';
@@ -1 +1 @@
1
- {"version":3,"file":"site-health-accessibility.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-accessibility.tsx"],"names":[],"mappings":"AAGA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAQnD,MAAM,MAAM,2BAA2B,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAC;AAC/F,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,EAAE,2BAA2B,2CA4PhF;yBA5Pe,uBAAuB"}
1
+ {"version":3,"file":"site-health-accessibility.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-accessibility.tsx"],"names":[],"mappings":"AAGA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AASnD,MAAM,MAAM,2BAA2B,GAAG,UAAU,CAAC,OAAO,uBAAuB,CAAC,SAAS,CAAC,CAAC;AAC/F,wBAAgB,uBAAuB,CAAC,EAAE,QAAQ,EAAE,EAAE,2BAA2B,2CAsJhF;yBAtJe,uBAAuB"}
@@ -1 +1 @@
1
- {"version":3,"file":"site-health-on-site-seo.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-on-site-seo.tsx"],"names":[],"mappings":"AAGA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAqJnD,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,SAAS,CAAC,CAAC;AACvF,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CA8PxE;yBA9Pe,mBAAmB"}
1
+ {"version":3,"file":"site-health-on-site-seo.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-on-site-seo.tsx"],"names":[],"mappings":"AAGA,OAAO,SAAS,EAAE,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAsJnD,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,OAAO,mBAAmB,CAAC,SAAS,CAAC,CAAC;AACvF,wBAAgB,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,uBAAuB,2CAgIxE;yBAhIe,mBAAmB"}