@pixelated-tech/components 3.4.2 → 3.5.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.
- package/README.md +32 -191
- package/dist/components/admin/componentusage/componentAnalysis.js +12 -4
- package/dist/components/admin/componentusage/componentDiscovery.js +20 -6
- package/dist/components/admin/site-health/seo-metrics.config.json +111 -0
- package/dist/components/admin/site-health/site-health-accessibility.js +5 -1
- package/dist/components/admin/site-health/site-health-axe-core.js +4 -0
- package/dist/components/admin/site-health/site-health-cloudwatch.integration.js +0 -5
- package/dist/components/admin/site-health/site-health-cloudwatch.js +7 -1
- package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +4 -0
- package/dist/components/admin/site-health/site-health-github.js +6 -0
- package/dist/components/admin/site-health/site-health-google-analytics.js +6 -0
- package/dist/components/admin/site-health/site-health-google-search-console.js +6 -0
- package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +379 -12
- package/dist/components/admin/site-health/site-health-on-site-seo.js +4 -0
- package/dist/components/admin/site-health/site-health-overview.js +11 -4
- package/dist/components/admin/site-health/site-health-performance.js +4 -0
- package/dist/components/admin/site-health/site-health-security.js +5 -1
- package/dist/components/admin/site-health/site-health-seo.js +5 -1
- package/dist/components/admin/site-health/site-health-template.js +19 -9
- package/dist/components/admin/site-health/site-health-uptime.js +4 -0
- package/dist/components/callout/callout.js +0 -10
- package/dist/components/carousel/carousel.js +15 -4
- package/dist/components/carousel/tiles.js +1 -1
- package/dist/components/cms/contentful.items.components.js +3 -4
- package/dist/components/cms/flickr.js +1 -1
- package/dist/components/cms/google.reviews.components.js +3 -3
- package/dist/components/cms/instagram.components.js +15 -5
- package/dist/components/cms/smartimage.js +2 -2
- package/dist/components/cms/wordpress.components.js +32 -6
- package/dist/components/cms/yelp.js +5 -0
- package/dist/components/config/config.server.js +7 -1
- package/dist/components/general/css.js +0 -1
- package/dist/components/general/image.js +0 -1
- package/dist/components/general/loading.js +2 -1
- package/dist/components/general/microinteractions.js +0 -1
- package/dist/components/general/modal.css +2 -4
- package/dist/components/general/modal.js +72 -30
- package/dist/components/general/sidepanel.js +16 -0
- package/dist/components/general/tab.js +1 -0
- package/dist/components/menu/menu-accordion.css +1 -1
- package/dist/components/menu/menu-accordion.js +15 -4
- package/dist/components/menu/menu-expando.js +21 -19
- package/dist/components/menu/menu-simple.js +14 -14
- package/dist/components/nerdjoke/nerdjoke.js +1 -1
- package/dist/components/seo/googlesearch.js +0 -1
- package/dist/components/seo/schema-blogposting.js +6 -1
- package/dist/components/seo/schema-recipe.js +34 -1
- package/dist/components/seo/schema-services.js +20 -2
- package/dist/components/shoppingcart/ebay.components.js +3 -3
- package/dist/components/shoppingcart/shoppingcart.components.js +76 -28
- package/dist/components/shoppingcart/shoppingcart.functions.js +4 -4
- package/dist/components/sitebuilder/config/CompoundFontSelector.js +13 -4
- package/dist/components/sitebuilder/config/ConfigBuilder.css +194 -5
- package/dist/components/sitebuilder/config/ConfigBuilder.js +183 -17
- package/dist/components/sitebuilder/config/FontSelector.js +13 -2
- package/dist/components/sitebuilder/config/routes-form.json +67 -0
- package/dist/components/sitebuilder/config/siteinfo-form.json +28 -14
- package/dist/components/sitebuilder/config/visualdesignform.json +4 -4
- package/dist/components/sitebuilder/form/formbuilder.js +1 -0
- package/dist/components/sitebuilder/form/formcomponents.js +2 -3
- package/dist/components/sitebuilder/form/formengine.js +6 -5
- package/dist/components/sitebuilder/form/formvalidator.js +5 -0
- package/dist/components/sitebuilder/page/components/PageBuilderUI.js +5 -1
- package/dist/components/structured/buzzwordbingo.css +0 -1
- package/dist/components/structured/recipe.js +1 -1
- package/dist/components/structured/socialcard.js +2 -2
- package/dist/components/utilities/functions.js +82 -1
- package/dist/components/utilities/gemini-api.client.js +76 -0
- package/dist/components/utilities/gemini-api.server.js +185 -0
- package/dist/data/routes.json +5 -5
- package/dist/index.adminclient.js +30 -0
- package/dist/index.adminserver.js +21 -0
- package/dist/index.js +4 -18
- package/dist/index.server.js +15 -28
- package/dist/types/components/admin/componentusage/componentAnalysis.d.ts.map +1 -1
- package/dist/types/components/admin/componentusage/componentDiscovery.d.ts +1 -1
- package/dist/types/components/admin/componentusage/componentDiscovery.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-accessibility.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-axe-core.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts +9 -6
- package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-cloudwatch.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-github.d.ts +9 -6
- package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts +9 -6
- package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts +9 -6
- package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts +8 -3
- package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-overview.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-performance.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-security.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-seo.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-template.d.ts +12 -10
- package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-uptime.d.ts +7 -4
- package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -1
- package/dist/types/components/callout/callout.d.ts +3 -3
- package/dist/types/components/callout/callout.d.ts.map +1 -1
- package/dist/types/components/carousel/carousel.d.ts +16 -7
- package/dist/types/components/carousel/carousel.d.ts.map +1 -1
- package/dist/types/components/carousel/tiles.d.ts +3 -6
- package/dist/types/components/carousel/tiles.d.ts.map +1 -1
- package/dist/types/components/cms/flickr.d.ts +3 -6
- package/dist/types/components/cms/flickr.d.ts.map +1 -1
- package/dist/types/components/cms/google.reviews.components.d.ts +1 -7
- package/dist/types/components/cms/google.reviews.components.d.ts.map +1 -1
- package/dist/types/components/cms/hubspot.components.d.ts +1 -2
- package/dist/types/components/cms/hubspot.components.d.ts.map +1 -1
- package/dist/types/components/cms/instagram.components.d.ts +14 -9
- package/dist/types/components/cms/instagram.components.d.ts.map +1 -1
- package/dist/types/components/cms/smartimage.d.ts +2 -28
- package/dist/types/components/cms/smartimage.d.ts.map +1 -1
- package/dist/types/components/cms/wordpress.components.d.ts +33 -14
- package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
- package/dist/types/components/cms/yelp.d.ts +9 -4
- package/dist/types/components/cms/yelp.d.ts.map +1 -1
- package/dist/types/components/config/config.server.d.ts +9 -6
- package/dist/types/components/config/config.server.d.ts.map +1 -1
- package/dist/types/components/general/loading.d.ts +5 -1
- package/dist/types/components/general/loading.d.ts.map +1 -1
- package/dist/types/components/general/microinteractions.d.ts +1 -3
- package/dist/types/components/general/microinteractions.d.ts.map +1 -1
- package/dist/types/components/general/modal.d.ts +11 -5
- package/dist/types/components/general/modal.d.ts.map +1 -1
- package/dist/types/components/general/semantic.d.ts +3 -3
- package/dist/types/components/general/sidepanel.d.ts +20 -13
- package/dist/types/components/general/sidepanel.d.ts.map +1 -1
- package/dist/types/components/general/tab.d.ts +1 -2
- package/dist/types/components/general/tab.d.ts.map +1 -1
- package/dist/types/components/menu/menu-accordion.d.ts +22 -9
- package/dist/types/components/menu/menu-accordion.d.ts.map +1 -1
- package/dist/types/components/menu/menu-expando.d.ts +14 -5
- package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
- package/dist/types/components/menu/menu-simple.d.ts +4 -5
- package/dist/types/components/menu/menu-simple.d.ts.map +1 -1
- package/dist/types/components/nerdjoke/nerdjoke.d.ts +1 -1
- package/dist/types/components/nerdjoke/nerdjoke.d.ts.map +1 -1
- package/dist/types/components/seo/googleanalytics.d.ts.map +1 -1
- package/dist/types/components/seo/metadata.components.d.ts +2 -2
- package/dist/types/components/seo/metadata.components.d.ts.map +1 -1
- package/dist/types/components/seo/schema-blogposting.d.ts +7 -4
- package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -1
- package/dist/types/components/seo/schema-recipe.d.ts +29 -30
- package/dist/types/components/seo/schema-recipe.d.ts.map +1 -1
- package/dist/types/components/seo/schema-services.d.ts +19 -9
- package/dist/types/components/seo/schema-services.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/paypal.d.ts +1 -1
- package/dist/types/components/shoppingcart/paypal.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +77 -28
- package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts +4 -23
- package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts +10 -11
- package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +41 -174
- package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/FontSelector.d.ts +12 -13
- package/dist/types/components/sitebuilder/config/FontSelector.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formbuilder.d.ts +7 -3
- package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts +1 -1
- package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formengine.d.ts +1 -2
- package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formextractor.d.ts +5 -4
- package/dist/types/components/sitebuilder/form/formextractor.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formtypes.d.ts +3 -3
- package/dist/types/components/sitebuilder/form/formtypes.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formvalidator.d.ts +8 -3
- package/dist/types/components/sitebuilder/form/formvalidator.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts +2 -3
- package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts +2 -3
- package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts +2 -3
- package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts +8 -7
- package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/PageEngine.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts +2 -3
- package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/page/lib/componentMap.d.ts +1 -1
- package/dist/types/components/structured/markdown.d.ts +1 -3
- package/dist/types/components/structured/markdown.d.ts.map +1 -1
- package/dist/types/components/structured/recipe.d.ts +5 -32
- package/dist/types/components/structured/recipe.d.ts.map +1 -1
- package/dist/types/components/structured/socialcard.d.ts +4 -0
- package/dist/types/components/structured/socialcard.d.ts.map +1 -1
- package/dist/types/components/structured/timeline.d.ts +1 -3
- package/dist/types/components/structured/timeline.d.ts.map +1 -1
- package/dist/types/components/utilities/functions.d.ts +20 -0
- package/dist/types/components/utilities/functions.d.ts.map +1 -1
- package/dist/types/components/utilities/gemini-api.client.d.ts +38 -0
- package/dist/types/components/utilities/gemini-api.client.d.ts.map +1 -0
- package/dist/types/components/utilities/gemini-api.server.d.ts +17 -0
- package/dist/types/components/utilities/gemini-api.server.d.ts.map +1 -0
- package/dist/types/index.adminclient.d.ts +27 -0
- package/dist/types/index.adminclient.d.ts.map +1 -0
- package/dist/types/index.adminserver.d.ts +19 -0
- package/dist/types/index.adminserver.d.ts.map +1 -0
- package/dist/types/index.d.ts +4 -18
- package/dist/types/index.server.d.ts +5 -28
- package/dist/types/stories/admin/site-health.stories.d.ts +4 -0
- package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
- package/dist/types/stories/general/sidepanel.stories.d.ts.map +1 -1
- package/dist/types/stories/general/smartimage.stories.d.ts +74 -2
- package/dist/types/stories/general/smartimage.stories.d.ts.map +1 -1
- package/dist/types/tests/site-health-cloudwatch.test.d.ts +2 -0
- package/dist/types/tests/site-health-cloudwatch.test.d.ts.map +1 -0
- package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts +2 -0
- package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts.map +1 -0
- package/package.json +20 -10
- package/README.COMPONENTS.md +0 -2162
- package/dist/components/cms/pixelated.linkedin.js +0 -180
- package/dist/components/cms/pixelated.linkedin1.js +0 -84
- package/dist/components/cms/pixelated.linkedin2.js +0 -92
- package/dist/types/components/cms/pixelated.linkedin.d.ts +0 -2
- package/dist/types/components/cms/pixelated.linkedin.d.ts.map +0 -1
- package/dist/types/components/cms/pixelated.linkedin1.d.ts +0 -2
- package/dist/types/components/cms/pixelated.linkedin1.d.ts.map +0 -1
- package/dist/types/components/cms/pixelated.linkedin2.d.ts +0 -2
- package/dist/types/components/cms/pixelated.linkedin2.d.ts.map +0 -1
- package/dist/types/tests/pixelated.menu-expando.test.d.ts +0 -2
- package/dist/types/tests/pixelated.menu-expando.test.d.ts.map +0 -1
|
@@ -19,7 +19,12 @@ const dataCollectors = {
|
|
|
19
19
|
collectSemanticTagsData,
|
|
20
20
|
collectTitleTagsData,
|
|
21
21
|
collectMetaKeywordsData,
|
|
22
|
-
collectMetaDescriptionsData
|
|
22
|
+
collectMetaDescriptionsData,
|
|
23
|
+
collectMobileFirstIndexingData,
|
|
24
|
+
collectInternationalSEOData,
|
|
25
|
+
collectFacetedNavigationData,
|
|
26
|
+
collectBrowserCachingData,
|
|
27
|
+
collectGzipCompressionData
|
|
23
28
|
};
|
|
24
29
|
/**
|
|
25
30
|
* Registry of scoring functions
|
|
@@ -28,7 +33,12 @@ const scorers = {
|
|
|
28
33
|
calculateSemanticTagsScore,
|
|
29
34
|
calculateTitleTagsScore,
|
|
30
35
|
calculateMetaKeywordsScore,
|
|
31
|
-
calculateMetaDescriptionsScore
|
|
36
|
+
calculateMetaDescriptionsScore,
|
|
37
|
+
calculateMobileFirstIndexingScore,
|
|
38
|
+
calculateInternationalSEOData,
|
|
39
|
+
calculateFacetedNavigationScore,
|
|
40
|
+
calculateBrowserCachingScore,
|
|
41
|
+
calculateGzipCompressionScore
|
|
32
42
|
};
|
|
33
43
|
/**
|
|
34
44
|
* Data collection functions
|
|
@@ -229,6 +239,311 @@ function calculateMetaDescriptionsScore(descriptionData) {
|
|
|
229
239
|
}
|
|
230
240
|
};
|
|
231
241
|
}
|
|
242
|
+
/**
|
|
243
|
+
* Mobile-First Indexing Data Collector
|
|
244
|
+
*/
|
|
245
|
+
function collectMobileFirstIndexingData(html) {
|
|
246
|
+
const hasViewport = /<meta[^>]*name=["']viewport["'][^>]*content=["'][^"']*["'][^>]*>/i.test(html);
|
|
247
|
+
const hasResponsiveMeta = /<meta[^>]*name=["']viewport["'][^>]*content=["'][^"']*width=device-width[^"']*["'][^>]*>/i.test(html);
|
|
248
|
+
const hasMobileStyles = /@media[^}]*max-width[^}]*mobile|phone|tablet/i.test(html) || /<link[^>]*media=["'][^"']*handheld[^"']*["'][^>]*>/i.test(html);
|
|
249
|
+
return {
|
|
250
|
+
hasViewport,
|
|
251
|
+
hasResponsiveMeta,
|
|
252
|
+
hasMobileStyles,
|
|
253
|
+
score: (hasViewport && hasResponsiveMeta) ? 1 : 0
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Mobile-First Indexing Scorer
|
|
258
|
+
*/
|
|
259
|
+
function calculateMobileFirstIndexingScore(data) {
|
|
260
|
+
const score = data.score;
|
|
261
|
+
const displayValue = score ? 'Mobile-friendly viewport detected' : 'Missing or inadequate viewport configuration';
|
|
262
|
+
return {
|
|
263
|
+
score,
|
|
264
|
+
displayValue,
|
|
265
|
+
details: {
|
|
266
|
+
items: [
|
|
267
|
+
{ type: 'viewport', present: data.hasViewport, optimal: data.hasResponsiveMeta },
|
|
268
|
+
{ type: 'responsive-styles', present: data.hasMobileStyles }
|
|
269
|
+
]
|
|
270
|
+
}
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* International SEO Data Collector
|
|
275
|
+
*/
|
|
276
|
+
function collectInternationalSEOData(html) {
|
|
277
|
+
const currencySymbols = html.match(/[£€¥$₹₽₩₦₨₪₫₡₵₺₴₸₼₲₱₭₯₰₳₶₷₹₻₽₾₿]/g) || [];
|
|
278
|
+
const langAttributes = html.match(/lang=["'][^"']*["']/gi) || [];
|
|
279
|
+
const localeIndicators = html.match(/(en-US|en-GB|fr-FR|de-DE|es-ES|it-IT|pt-BR|ja-JP|ko-KR|zh-CN|zh-TW|ru-RU|ar-SA|hi-IN)/gi) || [];
|
|
280
|
+
return {
|
|
281
|
+
currencyCount: currencySymbols.length,
|
|
282
|
+
langAttributes: langAttributes.length,
|
|
283
|
+
localeIndicators: localeIndicators.length,
|
|
284
|
+
hasInternationalElements: currencySymbols.length > 0 || langAttributes.length > 0 || localeIndicators.length > 0
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* International SEO Scorer
|
|
289
|
+
*/
|
|
290
|
+
function calculateInternationalSEOData(data) {
|
|
291
|
+
const score = data.hasInternationalElements ? 1 : 0;
|
|
292
|
+
const displayValue = score ? `${data.currencyCount} currencies, ${data.langAttributes} lang attributes, ${data.localeIndicators} locale indicators found` : 'No international SEO elements detected';
|
|
293
|
+
return {
|
|
294
|
+
score,
|
|
295
|
+
displayValue,
|
|
296
|
+
details: {
|
|
297
|
+
items: [
|
|
298
|
+
{ type: 'currencies', count: data.currencyCount },
|
|
299
|
+
{ type: 'lang-attributes', count: data.langAttributes },
|
|
300
|
+
{ type: 'locale-indicators', count: data.localeIndicators }
|
|
301
|
+
]
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Faceted Navigation Data Collector
|
|
307
|
+
*/
|
|
308
|
+
function collectFacetedNavigationData(html) {
|
|
309
|
+
// Look for URL patterns that suggest faceted navigation (query parameters, filters)
|
|
310
|
+
const filterUrls = html.match(/href=["'][^"']*[?&][^"']*filter[^"']*["']/gi) || [];
|
|
311
|
+
const sortUrls = html.match(/href=["'][^"']*[?&][^"']*sort[^"']*["']/gi) || [];
|
|
312
|
+
const categoryUrls = html.match(/href=["'][^"']*[?&][^"']*category[^"']*["']/gi) || [];
|
|
313
|
+
const cleanUrls = html.match(/href=["'][^"']*\/[^?]*\/[^?]*["']/gi) || [];
|
|
314
|
+
return {
|
|
315
|
+
filterUrls: filterUrls.length,
|
|
316
|
+
sortUrls: sortUrls.length,
|
|
317
|
+
categoryUrls: categoryUrls.length,
|
|
318
|
+
cleanUrls: cleanUrls.length,
|
|
319
|
+
hasFacetedNavigation: (filterUrls.length + sortUrls.length + categoryUrls.length) > 0
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Faceted Navigation Scorer
|
|
324
|
+
*/
|
|
325
|
+
function calculateFacetedNavigationScore(data) {
|
|
326
|
+
const totalFaceted = data.filterUrls + data.sortUrls + data.categoryUrls;
|
|
327
|
+
const totalUrls = data.cleanUrls + totalFaceted;
|
|
328
|
+
// Calculate score as percentage of clean URLs vs total URLs
|
|
329
|
+
// Higher score = more clean URLs (better for SEO)
|
|
330
|
+
let score = 1; // Default to 100% if no URLs found
|
|
331
|
+
let displayValue = 'No URLs detected';
|
|
332
|
+
if (totalUrls > 0) {
|
|
333
|
+
score = data.cleanUrls / totalUrls; // Ratio of clean URLs to total URLs
|
|
334
|
+
const percentage = Math.round(score * 100);
|
|
335
|
+
if (totalFaceted === 0) {
|
|
336
|
+
displayValue = `100% clean URLs - excellent for SEO`;
|
|
337
|
+
}
|
|
338
|
+
else if (score >= 0.8) {
|
|
339
|
+
displayValue = `${percentage}% clean URLs (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
340
|
+
}
|
|
341
|
+
else if (score >= 0.5) {
|
|
342
|
+
displayValue = `${percentage}% clean URLs - consider reducing faceted navigation (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
displayValue = `${percentage}% clean URLs - high faceted navigation may hurt SEO (${data.cleanUrls} clean, ${totalFaceted} faceted)`;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return {
|
|
349
|
+
score,
|
|
350
|
+
displayValue,
|
|
351
|
+
details: {
|
|
352
|
+
items: [
|
|
353
|
+
{ type: 'clean-urls', count: data.cleanUrls },
|
|
354
|
+
{ type: 'filter-urls', count: data.filterUrls },
|
|
355
|
+
{ type: 'sort-urls', count: data.sortUrls },
|
|
356
|
+
{ type: 'category-urls', count: data.categoryUrls }
|
|
357
|
+
]
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Browser Caching Data Collector
|
|
363
|
+
*/
|
|
364
|
+
async function collectBrowserCachingData(url) {
|
|
365
|
+
try {
|
|
366
|
+
const response = await fetch(url, {
|
|
367
|
+
method: 'HEAD', // Use HEAD to get headers without downloading the full content
|
|
368
|
+
headers: {
|
|
369
|
+
'User-Agent': 'Mozilla/5.0 (compatible; SEO Analysis Bot)'
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
return {
|
|
374
|
+
cacheControl: '',
|
|
375
|
+
expires: '',
|
|
376
|
+
lastModified: '',
|
|
377
|
+
etag: '',
|
|
378
|
+
age: '',
|
|
379
|
+
hasCachingHeaders: false,
|
|
380
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const cacheControl = response.headers.get('cache-control') || '';
|
|
384
|
+
const expires = response.headers.get('expires') || '';
|
|
385
|
+
const lastModified = response.headers.get('last-modified') || '';
|
|
386
|
+
const etag = response.headers.get('etag') || '';
|
|
387
|
+
const age = response.headers.get('age') || '';
|
|
388
|
+
return {
|
|
389
|
+
cacheControl,
|
|
390
|
+
expires,
|
|
391
|
+
lastModified,
|
|
392
|
+
etag,
|
|
393
|
+
age,
|
|
394
|
+
hasCachingHeaders: !!(cacheControl || expires || lastModified || etag)
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
catch {
|
|
398
|
+
return {
|
|
399
|
+
cacheControl: '',
|
|
400
|
+
expires: '',
|
|
401
|
+
lastModified: '',
|
|
402
|
+
etag: '',
|
|
403
|
+
age: '',
|
|
404
|
+
hasCachingHeaders: false,
|
|
405
|
+
error: 'Could not access response headers - network error or server unavailable'
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Browser Caching Scorer
|
|
411
|
+
*/
|
|
412
|
+
function calculateBrowserCachingScore(data) {
|
|
413
|
+
if (data.error) {
|
|
414
|
+
return {
|
|
415
|
+
score: 0,
|
|
416
|
+
displayValue: 'Requires server header analysis',
|
|
417
|
+
details: { error: data.error }
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
let score = 0;
|
|
421
|
+
let displayValue = 'No caching headers detected';
|
|
422
|
+
const issues = [];
|
|
423
|
+
const goodHeaders = [];
|
|
424
|
+
// Check Cache-Control header
|
|
425
|
+
if (data.cacheControl) {
|
|
426
|
+
goodHeaders.push('Cache-Control');
|
|
427
|
+
// Check for good caching directives
|
|
428
|
+
if (data.cacheControl.includes('max-age') && !data.cacheControl.includes('no-cache') && !data.cacheControl.includes('no-store')) {
|
|
429
|
+
score = 1;
|
|
430
|
+
displayValue = 'Good caching headers found';
|
|
431
|
+
}
|
|
432
|
+
else if (data.cacheControl.includes('no-cache') || data.cacheControl.includes('no-store')) {
|
|
433
|
+
issues.push('Cache-Control prevents caching');
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
// Check Expires header
|
|
437
|
+
if (data.expires) {
|
|
438
|
+
goodHeaders.push('Expires');
|
|
439
|
+
const expiresDate = new Date(data.expires);
|
|
440
|
+
const now = new Date();
|
|
441
|
+
if (expiresDate > now) {
|
|
442
|
+
if (score === 0)
|
|
443
|
+
score = 0.5; // Partial credit for expires header
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
// Check other headers
|
|
447
|
+
if (data.lastModified)
|
|
448
|
+
goodHeaders.push('Last-Modified');
|
|
449
|
+
if (data.etag)
|
|
450
|
+
goodHeaders.push('ETag');
|
|
451
|
+
if (data.age)
|
|
452
|
+
goodHeaders.push('Age');
|
|
453
|
+
if (goodHeaders.length > 0 && score === 0) {
|
|
454
|
+
score = 0.5; // Partial credit for having some caching headers
|
|
455
|
+
displayValue = `Basic caching headers found: ${goodHeaders.join(', ')}`;
|
|
456
|
+
}
|
|
457
|
+
if (issues.length > 0) {
|
|
458
|
+
displayValue += ` (${issues.join(', ')})`;
|
|
459
|
+
}
|
|
460
|
+
return {
|
|
461
|
+
score,
|
|
462
|
+
displayValue,
|
|
463
|
+
details: {
|
|
464
|
+
headers: {
|
|
465
|
+
'cache-control': data.cacheControl,
|
|
466
|
+
'expires': data.expires,
|
|
467
|
+
'last-modified': data.lastModified,
|
|
468
|
+
'etag': data.etag,
|
|
469
|
+
'age': data.age
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Gzip Compression Data Collector
|
|
476
|
+
*/
|
|
477
|
+
async function collectGzipCompressionData(url) {
|
|
478
|
+
try {
|
|
479
|
+
const response = await fetch(url, {
|
|
480
|
+
method: 'GET', // Changed from HEAD to GET to properly detect compression
|
|
481
|
+
headers: {
|
|
482
|
+
'User-Agent': 'Mozilla/5.0 (compatible; SEO Analysis Bot)',
|
|
483
|
+
'Accept-Encoding': 'gzip, deflate' // Added Accept-Encoding like browsers
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
if (!response.ok) {
|
|
487
|
+
return {
|
|
488
|
+
contentEncoding: '',
|
|
489
|
+
contentLength: '',
|
|
490
|
+
transferEncoding: '',
|
|
491
|
+
isCompressed: false,
|
|
492
|
+
error: `HTTP ${response.status}: ${response.statusText}`
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
const contentEncoding = response.headers.get('content-encoding') || '';
|
|
496
|
+
const contentLength = response.headers.get('content-length') || '';
|
|
497
|
+
const transferEncoding = response.headers.get('transfer-encoding') || '';
|
|
498
|
+
return {
|
|
499
|
+
contentEncoding,
|
|
500
|
+
contentLength,
|
|
501
|
+
transferEncoding,
|
|
502
|
+
isCompressed: contentEncoding.includes('gzip') || contentEncoding.includes('deflate')
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
catch {
|
|
506
|
+
return {
|
|
507
|
+
contentEncoding: '',
|
|
508
|
+
contentLength: '',
|
|
509
|
+
transferEncoding: '',
|
|
510
|
+
isCompressed: false,
|
|
511
|
+
error: 'Could not access response headers - network error or server unavailable'
|
|
512
|
+
};
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* Gzip Compression Scorer
|
|
517
|
+
*/
|
|
518
|
+
function calculateGzipCompressionScore(data) {
|
|
519
|
+
if (data.error) {
|
|
520
|
+
return {
|
|
521
|
+
score: 0,
|
|
522
|
+
displayValue: 'Requires server header analysis',
|
|
523
|
+
details: { error: data.error }
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
let score = 0;
|
|
527
|
+
let displayValue = 'No compression detected';
|
|
528
|
+
if (data.isCompressed) {
|
|
529
|
+
score = 1;
|
|
530
|
+
displayValue = `Compression enabled: ${data.contentEncoding || data.transferEncoding}`;
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
displayValue = 'Response not compressed - consider enabling gzip compression';
|
|
534
|
+
}
|
|
535
|
+
return {
|
|
536
|
+
score,
|
|
537
|
+
displayValue,
|
|
538
|
+
details: {
|
|
539
|
+
headers: {
|
|
540
|
+
'content-encoding': data.contentEncoding,
|
|
541
|
+
'content-length': data.contentLength,
|
|
542
|
+
'transfer-encoding': data.transferEncoding
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
232
547
|
/**
|
|
233
548
|
* Crawl the site to discover internal pages
|
|
234
549
|
*/
|
|
@@ -348,11 +663,16 @@ async function analyzeSinglePage(url) {
|
|
|
348
663
|
// Navigate to the page with faster waiting strategy
|
|
349
664
|
const response = await page.goto(url, {
|
|
350
665
|
waitUntil: 'domcontentloaded', // Wait for DOM instead of all network requests
|
|
351
|
-
timeout:
|
|
666
|
+
timeout: 10000 // Reduced timeout from 15000 to 10000
|
|
352
667
|
});
|
|
668
|
+
// Check if navigation failed
|
|
669
|
+
if (!response) {
|
|
670
|
+
console.warn(`Failed to load page: ${url} - no response received`);
|
|
671
|
+
// Continue with analysis using available data, but mark header-dependent metrics as unavailable
|
|
672
|
+
}
|
|
353
673
|
// Wait for H1 elements to be rendered (if any) with a short timeout
|
|
354
674
|
try {
|
|
355
|
-
await page.waitForSelector('h1', { timeout:
|
|
675
|
+
await page.waitForSelector('h1', { timeout: 1000 }); // Reduced from 2000 to 1000
|
|
356
676
|
}
|
|
357
677
|
catch {
|
|
358
678
|
// H1 not found within timeout, continue anyway
|
|
@@ -373,7 +693,8 @@ async function analyzeSinglePage(url) {
|
|
|
373
693
|
});
|
|
374
694
|
// Get the rendered HTML for other pattern-based checks
|
|
375
695
|
const html = await page.content();
|
|
376
|
-
|
|
696
|
+
// Don't close the page here - let it be reused or closed by caller
|
|
697
|
+
// await page.close();
|
|
377
698
|
const audits = [];
|
|
378
699
|
// Process on-page metrics from configuration
|
|
379
700
|
const config = seoMetricsConfig;
|
|
@@ -387,12 +708,23 @@ async function analyzeSinglePage(url) {
|
|
|
387
708
|
const collector = dataCollectors[metric.dataCollector];
|
|
388
709
|
const scorer = scorers[metric.scorer];
|
|
389
710
|
if (collector && scorer) {
|
|
390
|
-
|
|
711
|
+
// Pass URL for collectors that need headers (like browser caching and gzip compression)
|
|
712
|
+
let rawData;
|
|
713
|
+
if (metric.dataCollector === 'collectBrowserCachingData' || metric.dataCollector === 'collectGzipCompressionData') {
|
|
714
|
+
rawData = await collector(url);
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
rawData = collector(html, pageData.title);
|
|
718
|
+
}
|
|
391
719
|
const result = scorer(rawData);
|
|
392
720
|
score = result.score;
|
|
393
721
|
displayValue = result.displayValue;
|
|
394
722
|
details = result.details;
|
|
395
723
|
}
|
|
724
|
+
else {
|
|
725
|
+
score = 0;
|
|
726
|
+
displayValue = `Data collector or scorer not found: ${metric.dataCollector}/${metric.scorer}`;
|
|
727
|
+
}
|
|
396
728
|
}
|
|
397
729
|
else if (metric.pattern) {
|
|
398
730
|
// Use pattern-based analysis
|
|
@@ -401,6 +733,11 @@ async function analyzeSinglePage(url) {
|
|
|
401
733
|
displayValue = result.displayValue;
|
|
402
734
|
details = result.details;
|
|
403
735
|
}
|
|
736
|
+
else {
|
|
737
|
+
// Neither data collector/scorer nor pattern available
|
|
738
|
+
score = 0;
|
|
739
|
+
displayValue = 'Configuration incomplete - no pattern or data collector defined';
|
|
740
|
+
}
|
|
404
741
|
// Override H1 and H2 results with direct DOM counts for accuracy and speed
|
|
405
742
|
if (metric.id === 'h1-tags') {
|
|
406
743
|
score = pageData.h1Count === (metric.expectedCount || 1) ? 1 : 0;
|
|
@@ -422,6 +759,8 @@ async function analyzeSinglePage(url) {
|
|
|
422
759
|
details
|
|
423
760
|
});
|
|
424
761
|
}
|
|
762
|
+
// Close the page after analysis
|
|
763
|
+
await page.close();
|
|
425
764
|
return {
|
|
426
765
|
url,
|
|
427
766
|
title: pageData.title,
|
|
@@ -441,11 +780,7 @@ async function analyzeSinglePage(url) {
|
|
|
441
780
|
crawledAt: new Date().toISOString()
|
|
442
781
|
};
|
|
443
782
|
}
|
|
444
|
-
|
|
445
|
-
if (browser) {
|
|
446
|
-
await browser.close();
|
|
447
|
-
}
|
|
448
|
-
}
|
|
783
|
+
// Don't close browser here - keep it alive for reuse
|
|
449
784
|
}
|
|
450
785
|
async function performSiteWideAudits(baseUrl) {
|
|
451
786
|
const audits = [];
|
|
@@ -520,6 +855,38 @@ async function performSiteWideAudits(baseUrl) {
|
|
|
520
855
|
displayValue = 'Manifest.webmanifest not accessible';
|
|
521
856
|
}
|
|
522
857
|
break;
|
|
858
|
+
case 'gzip-compression':
|
|
859
|
+
try {
|
|
860
|
+
const gzipData = await collectGzipCompressionData(baseUrl);
|
|
861
|
+
const gzipResult = calculateGzipCompressionScore(gzipData);
|
|
862
|
+
score = gzipResult.score;
|
|
863
|
+
displayValue = gzipResult.displayValue;
|
|
864
|
+
}
|
|
865
|
+
catch {
|
|
866
|
+
score = 0;
|
|
867
|
+
displayValue = 'Error analyzing compression headers';
|
|
868
|
+
}
|
|
869
|
+
break;
|
|
870
|
+
case 'browser-caching':
|
|
871
|
+
try {
|
|
872
|
+
const cachingData = await collectBrowserCachingData(baseUrl);
|
|
873
|
+
const cachingResult = calculateBrowserCachingScore(cachingData);
|
|
874
|
+
score = cachingResult.score;
|
|
875
|
+
displayValue = cachingResult.displayValue;
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
score = 0;
|
|
879
|
+
displayValue = 'Error analyzing caching headers';
|
|
880
|
+
}
|
|
881
|
+
break;
|
|
882
|
+
case 'duplicate-content-detection':
|
|
883
|
+
score = 0; // Placeholder - would need multi-page content analysis
|
|
884
|
+
displayValue = 'Requires comprehensive site crawl';
|
|
885
|
+
break;
|
|
886
|
+
case 'safe-browsing-status':
|
|
887
|
+
score = 0; // Placeholder - would need external API
|
|
888
|
+
displayValue = 'Requires Google Safe Browsing API';
|
|
889
|
+
break;
|
|
523
890
|
default:
|
|
524
891
|
score = 0;
|
|
525
892
|
displayValue = 'Not implemented';
|
|
@@ -595,7 +962,7 @@ export async function performOnSiteSEOAnalysis(baseUrl) {
|
|
|
595
962
|
}
|
|
596
963
|
else {
|
|
597
964
|
// Fallback to crawling if sitemap not available
|
|
598
|
-
pagesToAnalyze = await crawlSite(baseUrl,
|
|
965
|
+
pagesToAnalyze = await crawlSite(baseUrl, 2); // Reduced from 5 to 2 pages
|
|
599
966
|
}
|
|
600
967
|
if (pagesToAnalyze.length === 0) {
|
|
601
968
|
return {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import PropTypes from 'prop-types';
|
|
3
4
|
import { SiteHealthTemplate } from './site-health-template';
|
|
4
5
|
import { getScoreIndicator } from './site-health-indicators';
|
|
5
6
|
/**
|
|
@@ -91,6 +92,9 @@ async function fetchOnSiteSEOData(siteName) {
|
|
|
91
92
|
};
|
|
92
93
|
}
|
|
93
94
|
}
|
|
95
|
+
SiteHealthOnSiteSEO.propTypes = {
|
|
96
|
+
siteName: PropTypes.string.isRequired,
|
|
97
|
+
};
|
|
94
98
|
export function SiteHealthOnSiteSEO({ siteName }) {
|
|
95
99
|
return (_jsx(SiteHealthTemplate, { siteName: siteName, title: "On-Site SEO", fetchData: fetchOnSiteSEOData, children: (data) => {
|
|
96
100
|
if (!data)
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { SiteHealthTemplate } from './site-health-template';
|
|
5
6
|
import { getScoreIndicator } from './site-health-indicators';
|
|
7
|
+
SiteHealthOverview.propTypes = {
|
|
8
|
+
siteName: PropTypes.string.isRequired,
|
|
9
|
+
};
|
|
6
10
|
export function SiteHealthOverview({ siteName }) {
|
|
7
11
|
const fetchCWVData = useCallback(async (site) => {
|
|
8
12
|
const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
|
|
@@ -57,9 +61,12 @@ export function SiteHealthOverview({ siteName }) {
|
|
|
57
61
|
};
|
|
58
62
|
return (_jsxs(_Fragment, { children: [_jsx("h4", { className: "health-site-name", children: siteData.site.replace('-', ' ') }), _jsxs("p", { className: "health-site-url", children: ["URL: ", siteData.url] }), _jsx("div", { className: "health-score-container", children: Object.entries(siteData.scores)
|
|
59
63
|
.filter(([, score]) => score !== null)
|
|
60
|
-
.map(([category, score]) =>
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
.map(([category, score]) => {
|
|
65
|
+
const numScore = score;
|
|
66
|
+
return (_jsxs("div", { className: "health-score-item", children: [_jsx("div", { className: "health-score-label", children: category.replace('-', ' ') }), _jsx("div", { className: "health-score-value", style: { color: getScoreColor(numScore) }, children: formatScore(numScore) }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
|
|
67
|
+
width: numScore !== null ? `${numScore * 100}%` : '0%',
|
|
68
|
+
backgroundColor: numScore !== null ? getScoreColor(numScore) : '#6b7280'
|
|
69
|
+
} }) })] }, category));
|
|
70
|
+
}) }), _jsxs("div", { style: { marginBottom: '1.5rem' }, children: [_jsx("h5", { style: { fontSize: '1rem', fontWeight: '600', marginBottom: '1rem' }, children: "Core Web Vitals" }), _jsxs("div", { className: "health-cwv-grid", children: [_jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Cumulative Layout Shift:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.cls, { good: 0.1, poor: 0.25 })) }, children: formatMetric(siteData.metrics.cls, '') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "First Input Delay:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.fid, { good: 100, poor: 300 })) }, children: formatMetric(siteData.metrics.fid, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Largest Contentful Paint:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.lcp, { good: 2500, poor: 4000 })) }, children: formatMetric(siteData.metrics.lcp, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "First Contentful Paint:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.fcp, { good: 1800, poor: 3000 })) }, children: formatMetric(siteData.metrics.fcp, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Time to First Byte:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.ttfb, { good: 800, poor: 1800 })) }, children: formatMetric(siteData.metrics.ttfb, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Speed Index:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.speedIndex, { good: 3400, poor: 5800 })) }, children: formatMetric(siteData.metrics.speedIndex, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Time to Interactive:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.interactive, { good: 3800, poor: 7300 })) }, children: formatMetric(siteData.metrics.interactive, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "Total Blocking Time:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.totalBlockingTime, { good: 200, poor: 600 })) }, children: formatMetric(siteData.metrics.totalBlockingTime, 'ms') })] }), _jsxs("div", { className: "health-cwv-item", children: [_jsx("span", { className: "health-cwv-label", children: "First Meaningful Paint:" }), _jsx("span", { className: "health-cwv-value", style: { color: getStatusColor(getMetricStatus(siteData.metrics.firstMeaningfulPaint, { good: 2000, poor: 4000 })) }, children: formatMetric(siteData.metrics.firstMeaningfulPaint, 'ms') })] })] })] }), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(siteData.timestamp).toLocaleString()] })] }));
|
|
64
71
|
} }));
|
|
65
72
|
}
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { SiteHealthTemplate } from './site-health-template';
|
|
5
6
|
import { getScoreIndicator } from './site-health-indicators';
|
|
7
|
+
SiteHealthPerformance.propTypes = {
|
|
8
|
+
siteName: PropTypes.string.isRequired,
|
|
9
|
+
};
|
|
6
10
|
export function SiteHealthPerformance({ siteName }) {
|
|
7
11
|
const fetchCWVData = useCallback(async (site) => {
|
|
8
12
|
const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { SiteHealthTemplate } from './site-health-template';
|
|
5
6
|
import { getScoreIndicator } from './site-health-indicators';
|
|
7
|
+
SiteHealthSecurity.propTypes = {
|
|
8
|
+
siteName: PropTypes.string.isRequired,
|
|
9
|
+
};
|
|
6
10
|
export function SiteHealthSecurity({ siteName }) {
|
|
7
11
|
const fetchSecurityData = useCallback(async (site) => {
|
|
8
12
|
// Fetch PSI data for best practices security audits
|
|
@@ -161,7 +165,7 @@ export function SiteHealthSecurity({ siteName }) {
|
|
|
161
165
|
width: `${(psiData.scores['best-practices'] || 0) * 100}%`,
|
|
162
166
|
backgroundColor: getScoreColor(psiData.scores['best-practices'])
|
|
163
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
|
|
164
|
-
.filter(audit => audit.scoreDisplayMode !== 'notApplicable')
|
|
168
|
+
.filter((audit) => audit.scoreDisplayMode !== 'notApplicable')
|
|
165
169
|
.sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
166
170
|
.slice(0, 20)
|
|
167
171
|
.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: ["(", Math.round((audit.score || 0) * 100), "%) ", audit.title] }), audit.displayValue && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details?.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && (audit.score || 0) < 0.9 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items.map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatAuditItem(item, audit.title) }, idx))) }) }))] })] }, audit.id))) })] })), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(psiData.timestamp).toLocaleString()] })] }));
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useCallback } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { SiteHealthTemplate } from './site-health-template';
|
|
5
6
|
import { getScoreIndicator } from './site-health-indicators';
|
|
7
|
+
SiteHealthSEO.propTypes = {
|
|
8
|
+
siteName: PropTypes.string.isRequired,
|
|
9
|
+
};
|
|
6
10
|
export function SiteHealthSEO({ siteName }) {
|
|
7
11
|
const fetchSEOData = useCallback(async (site) => {
|
|
8
12
|
const response = await fetch(`/api/site-health/core-web-vitals?siteName=${encodeURIComponent(site)}`);
|
|
@@ -116,7 +120,7 @@ export function SiteHealthSEO({ siteName }) {
|
|
|
116
120
|
width: `${(siteData.scores.seo || 0) * 100}%`,
|
|
117
121
|
backgroundColor: getScoreColor(siteData.scores.seo)
|
|
118
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
|
|
119
|
-
.filter(audit => audit.scoreDisplayMode !== 'notApplicable')
|
|
123
|
+
.filter((audit) => audit.scoreDisplayMode !== 'notApplicable')
|
|
120
124
|
.sort((a, b) => (b.score || 0) - (a.score || 0))
|
|
121
125
|
.slice(0, 20)
|
|
122
126
|
.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: ["(", Math.round((audit.score || 0) * 100), "%) ", audit.title] }), audit.displayValue && (_jsx("p", { className: "health-audit-description", children: audit.displayValue })), audit.details?.items && Array.isArray(audit.details.items) && audit.details.items.length > 0 && (audit.score || 0) < 1.0 && (_jsx("div", { className: "health-audit-details", children: _jsx("div", { style: { fontSize: '0.75rem', color: '#6b7280', marginTop: '0.25rem' }, children: audit.details.items.map((item, idx) => (_jsx("div", { style: { marginBottom: '0.125rem' }, children: formatAuditItem(item, audit.title) }, idx))) }) }))] })] }, audit.id))) })] })), _jsxs("p", { className: "health-timestamp", children: ["Last checked: ", new Date(siteData.timestamp).toLocaleString()] })] }));
|
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
3
|
import { useEffect, useState } from 'react';
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
4
5
|
import { PageGridItem } from '../../general/semantic';
|
|
5
|
-
|
|
6
|
+
SiteHealthTemplate.propTypes = {
|
|
7
|
+
siteName: PropTypes.string.isRequired,
|
|
8
|
+
title: PropTypes.string,
|
|
9
|
+
children: PropTypes.func.isRequired,
|
|
10
|
+
fetchData: PropTypes.func.isRequired,
|
|
11
|
+
enableCacheControl: PropTypes.bool,
|
|
12
|
+
columnSpan: PropTypes.number,
|
|
13
|
+
};
|
|
14
|
+
export function SiteHealthTemplate(props) {
|
|
15
|
+
const typedProps = props;
|
|
6
16
|
const [data, setData] = useState(null);
|
|
7
17
|
const [loading, setLoading] = useState(false);
|
|
8
18
|
const [error, setError] = useState(null);
|
|
9
19
|
useEffect(() => {
|
|
10
20
|
let isMounted = true;
|
|
11
21
|
const loadData = async () => {
|
|
12
|
-
if (!siteName) {
|
|
22
|
+
if (!typedProps.siteName) {
|
|
13
23
|
if (isMounted) {
|
|
14
24
|
setData(null);
|
|
15
25
|
setLoading(false);
|
|
@@ -25,8 +35,8 @@ export function SiteHealthTemplate({ siteName, title, children, fetchData, enabl
|
|
|
25
35
|
// Check for cache control from URL query parameters
|
|
26
36
|
const urlParams = new URLSearchParams(window.location.search);
|
|
27
37
|
const cacheParam = urlParams.get('cache');
|
|
28
|
-
const useCache = enableCacheControl ? (cacheParam !== 'false') : true;
|
|
29
|
-
const result = await fetchData(siteName, useCache);
|
|
38
|
+
const useCache = typedProps.enableCacheControl ? (cacheParam !== 'false') : true;
|
|
39
|
+
const result = await typedProps.fetchData(typedProps.siteName, useCache);
|
|
30
40
|
if (isMounted) {
|
|
31
41
|
setData(result);
|
|
32
42
|
setError(null);
|
|
@@ -48,15 +58,15 @@ export function SiteHealthTemplate({ siteName, title, children, fetchData, enabl
|
|
|
48
58
|
return () => {
|
|
49
59
|
isMounted = false;
|
|
50
60
|
};
|
|
51
|
-
}, [siteName, fetchData]);
|
|
61
|
+
}, [typedProps.siteName, typedProps.fetchData]);
|
|
52
62
|
// If no site selected, show nothing
|
|
53
|
-
if (!siteName) {
|
|
63
|
+
if (!typedProps.siteName) {
|
|
54
64
|
return null;
|
|
55
65
|
}
|
|
56
66
|
// If title is provided, render the complete card structure
|
|
57
|
-
if (title) {
|
|
58
|
-
return (_jsxs(PageGridItem, { className: "health-card", columnSpan: columnSpan, children: [_jsx("h2", { className: "health-card-title", children: title }), _jsx("div", { className: "health-card-content", children: loading ? (_jsxs("div", { className: "health-loading", children: [_jsx("div", { className: "health-loading-spinner" }), _jsx("p", { className: "health-loading-text", children: "Loading..." })] })) : error ? (_jsx("div", { className: "health-error", children: _jsxs("p", { className: "health-error-text", children: ["Error: ", error] }) })) : (children(data)) })] }));
|
|
67
|
+
if (typedProps.title) {
|
|
68
|
+
return (_jsxs(PageGridItem, { className: "health-card", columnSpan: typedProps.columnSpan, children: [_jsx("h2", { className: "health-card-title", children: typedProps.title }), _jsx("div", { className: "health-card-content", children: loading ? (_jsxs("div", { className: "health-loading", children: [_jsx("div", { className: "health-loading-spinner" }), _jsx("p", { className: "health-loading-text", children: "Loading..." })] })) : error ? (_jsx("div", { className: "health-error", children: _jsxs("p", { className: "health-error-text", children: ["Error: ", error] }) })) : (typedProps.children(data)) })] }));
|
|
59
69
|
}
|
|
60
70
|
// Legacy mode: render content directly without wrapper
|
|
61
|
-
return (_jsx(_Fragment, { children: loading ? (_jsxs("div", { className: "health-loading", children: [_jsx("div", { className: "health-loading-spinner" }), _jsx("p", { className: "health-loading-text", children: "Loading..." })] })) : error ? (_jsx("div", { className: "health-error", children: _jsxs("p", { className: "health-error-text", children: ["Error: ", error] }) })) : (children(data)) }));
|
|
71
|
+
return (_jsx(_Fragment, { children: loading ? (_jsxs("div", { className: "health-loading", children: [_jsx("div", { className: "health-loading-spinner" }), _jsx("p", { className: "health-loading-text", children: "Loading..." })] })) : error ? (_jsx("div", { className: "health-error", children: _jsxs("p", { className: "health-error-text", children: ["Error: ", error] }) })) : (typedProps.children(data)) }));
|
|
62
72
|
}
|