@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.
Files changed (235) hide show
  1. package/README.md +32 -191
  2. package/dist/components/admin/componentusage/componentAnalysis.js +12 -4
  3. package/dist/components/admin/componentusage/componentDiscovery.js +20 -6
  4. package/dist/components/admin/site-health/seo-metrics.config.json +111 -0
  5. package/dist/components/admin/site-health/site-health-accessibility.js +5 -1
  6. package/dist/components/admin/site-health/site-health-axe-core.js +4 -0
  7. package/dist/components/admin/site-health/site-health-cloudwatch.integration.js +0 -5
  8. package/dist/components/admin/site-health/site-health-cloudwatch.js +7 -1
  9. package/dist/components/admin/site-health/site-health-dependency-vulnerabilities.js +4 -0
  10. package/dist/components/admin/site-health/site-health-github.js +6 -0
  11. package/dist/components/admin/site-health/site-health-google-analytics.js +6 -0
  12. package/dist/components/admin/site-health/site-health-google-search-console.js +6 -0
  13. package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +379 -12
  14. package/dist/components/admin/site-health/site-health-on-site-seo.js +4 -0
  15. package/dist/components/admin/site-health/site-health-overview.js +11 -4
  16. package/dist/components/admin/site-health/site-health-performance.js +4 -0
  17. package/dist/components/admin/site-health/site-health-security.js +5 -1
  18. package/dist/components/admin/site-health/site-health-seo.js +5 -1
  19. package/dist/components/admin/site-health/site-health-template.js +19 -9
  20. package/dist/components/admin/site-health/site-health-uptime.js +4 -0
  21. package/dist/components/callout/callout.js +0 -10
  22. package/dist/components/carousel/carousel.js +15 -4
  23. package/dist/components/carousel/tiles.js +1 -1
  24. package/dist/components/cms/contentful.items.components.js +3 -4
  25. package/dist/components/cms/flickr.js +1 -1
  26. package/dist/components/cms/google.reviews.components.js +3 -3
  27. package/dist/components/cms/instagram.components.js +15 -5
  28. package/dist/components/cms/smartimage.js +2 -2
  29. package/dist/components/cms/wordpress.components.js +32 -6
  30. package/dist/components/cms/yelp.js +5 -0
  31. package/dist/components/config/config.server.js +7 -1
  32. package/dist/components/general/css.js +0 -1
  33. package/dist/components/general/image.js +0 -1
  34. package/dist/components/general/loading.js +2 -1
  35. package/dist/components/general/microinteractions.js +0 -1
  36. package/dist/components/general/modal.css +2 -4
  37. package/dist/components/general/modal.js +72 -30
  38. package/dist/components/general/sidepanel.js +16 -0
  39. package/dist/components/general/tab.js +1 -0
  40. package/dist/components/menu/menu-accordion.css +1 -1
  41. package/dist/components/menu/menu-accordion.js +15 -4
  42. package/dist/components/menu/menu-expando.js +21 -19
  43. package/dist/components/menu/menu-simple.js +14 -14
  44. package/dist/components/nerdjoke/nerdjoke.js +1 -1
  45. package/dist/components/seo/googlesearch.js +0 -1
  46. package/dist/components/seo/schema-blogposting.js +6 -1
  47. package/dist/components/seo/schema-recipe.js +34 -1
  48. package/dist/components/seo/schema-services.js +20 -2
  49. package/dist/components/shoppingcart/ebay.components.js +3 -3
  50. package/dist/components/shoppingcart/shoppingcart.components.js +76 -28
  51. package/dist/components/shoppingcart/shoppingcart.functions.js +4 -4
  52. package/dist/components/sitebuilder/config/CompoundFontSelector.js +13 -4
  53. package/dist/components/sitebuilder/config/ConfigBuilder.css +194 -5
  54. package/dist/components/sitebuilder/config/ConfigBuilder.js +183 -17
  55. package/dist/components/sitebuilder/config/FontSelector.js +13 -2
  56. package/dist/components/sitebuilder/config/routes-form.json +67 -0
  57. package/dist/components/sitebuilder/config/siteinfo-form.json +28 -14
  58. package/dist/components/sitebuilder/config/visualdesignform.json +4 -4
  59. package/dist/components/sitebuilder/form/formbuilder.js +1 -0
  60. package/dist/components/sitebuilder/form/formcomponents.js +2 -3
  61. package/dist/components/sitebuilder/form/formengine.js +6 -5
  62. package/dist/components/sitebuilder/form/formvalidator.js +5 -0
  63. package/dist/components/sitebuilder/page/components/PageBuilderUI.js +5 -1
  64. package/dist/components/structured/buzzwordbingo.css +0 -1
  65. package/dist/components/structured/recipe.js +1 -1
  66. package/dist/components/structured/socialcard.js +2 -2
  67. package/dist/components/utilities/functions.js +82 -1
  68. package/dist/components/utilities/gemini-api.client.js +76 -0
  69. package/dist/components/utilities/gemini-api.server.js +185 -0
  70. package/dist/data/routes.json +5 -5
  71. package/dist/index.adminclient.js +30 -0
  72. package/dist/index.adminserver.js +21 -0
  73. package/dist/index.js +4 -18
  74. package/dist/index.server.js +15 -28
  75. package/dist/types/components/admin/componentusage/componentAnalysis.d.ts.map +1 -1
  76. package/dist/types/components/admin/componentusage/componentDiscovery.d.ts +1 -1
  77. package/dist/types/components/admin/componentusage/componentDiscovery.d.ts.map +1 -1
  78. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts +7 -4
  79. package/dist/types/components/admin/site-health/site-health-accessibility.d.ts.map +1 -1
  80. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts +7 -4
  81. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
  82. package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts +9 -6
  83. package/dist/types/components/admin/site-health/site-health-cloudwatch.d.ts.map +1 -1
  84. package/dist/types/components/admin/site-health/site-health-cloudwatch.integration.d.ts.map +1 -1
  85. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts +7 -4
  86. package/dist/types/components/admin/site-health/site-health-dependency-vulnerabilities.d.ts.map +1 -1
  87. package/dist/types/components/admin/site-health/site-health-github.d.ts +9 -6
  88. package/dist/types/components/admin/site-health/site-health-github.d.ts.map +1 -1
  89. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts +9 -6
  90. package/dist/types/components/admin/site-health/site-health-google-analytics.d.ts.map +1 -1
  91. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts +9 -6
  92. package/dist/types/components/admin/site-health/site-health-google-search-console.d.ts.map +1 -1
  93. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts +8 -3
  94. package/dist/types/components/admin/site-health/site-health-on-site-seo.d.ts.map +1 -1
  95. package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
  96. package/dist/types/components/admin/site-health/site-health-overview.d.ts +7 -4
  97. package/dist/types/components/admin/site-health/site-health-overview.d.ts.map +1 -1
  98. package/dist/types/components/admin/site-health/site-health-performance.d.ts +7 -4
  99. package/dist/types/components/admin/site-health/site-health-performance.d.ts.map +1 -1
  100. package/dist/types/components/admin/site-health/site-health-security.d.ts +7 -4
  101. package/dist/types/components/admin/site-health/site-health-security.d.ts.map +1 -1
  102. package/dist/types/components/admin/site-health/site-health-seo.d.ts +7 -4
  103. package/dist/types/components/admin/site-health/site-health-seo.d.ts.map +1 -1
  104. package/dist/types/components/admin/site-health/site-health-template.d.ts +12 -10
  105. package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
  106. package/dist/types/components/admin/site-health/site-health-uptime.d.ts +7 -4
  107. package/dist/types/components/admin/site-health/site-health-uptime.d.ts.map +1 -1
  108. package/dist/types/components/callout/callout.d.ts +3 -3
  109. package/dist/types/components/callout/callout.d.ts.map +1 -1
  110. package/dist/types/components/carousel/carousel.d.ts +16 -7
  111. package/dist/types/components/carousel/carousel.d.ts.map +1 -1
  112. package/dist/types/components/carousel/tiles.d.ts +3 -6
  113. package/dist/types/components/carousel/tiles.d.ts.map +1 -1
  114. package/dist/types/components/cms/flickr.d.ts +3 -6
  115. package/dist/types/components/cms/flickr.d.ts.map +1 -1
  116. package/dist/types/components/cms/google.reviews.components.d.ts +1 -7
  117. package/dist/types/components/cms/google.reviews.components.d.ts.map +1 -1
  118. package/dist/types/components/cms/hubspot.components.d.ts +1 -2
  119. package/dist/types/components/cms/hubspot.components.d.ts.map +1 -1
  120. package/dist/types/components/cms/instagram.components.d.ts +14 -9
  121. package/dist/types/components/cms/instagram.components.d.ts.map +1 -1
  122. package/dist/types/components/cms/smartimage.d.ts +2 -28
  123. package/dist/types/components/cms/smartimage.d.ts.map +1 -1
  124. package/dist/types/components/cms/wordpress.components.d.ts +33 -14
  125. package/dist/types/components/cms/wordpress.components.d.ts.map +1 -1
  126. package/dist/types/components/cms/yelp.d.ts +9 -4
  127. package/dist/types/components/cms/yelp.d.ts.map +1 -1
  128. package/dist/types/components/config/config.server.d.ts +9 -6
  129. package/dist/types/components/config/config.server.d.ts.map +1 -1
  130. package/dist/types/components/general/loading.d.ts +5 -1
  131. package/dist/types/components/general/loading.d.ts.map +1 -1
  132. package/dist/types/components/general/microinteractions.d.ts +1 -3
  133. package/dist/types/components/general/microinteractions.d.ts.map +1 -1
  134. package/dist/types/components/general/modal.d.ts +11 -5
  135. package/dist/types/components/general/modal.d.ts.map +1 -1
  136. package/dist/types/components/general/semantic.d.ts +3 -3
  137. package/dist/types/components/general/sidepanel.d.ts +20 -13
  138. package/dist/types/components/general/sidepanel.d.ts.map +1 -1
  139. package/dist/types/components/general/tab.d.ts +1 -2
  140. package/dist/types/components/general/tab.d.ts.map +1 -1
  141. package/dist/types/components/menu/menu-accordion.d.ts +22 -9
  142. package/dist/types/components/menu/menu-accordion.d.ts.map +1 -1
  143. package/dist/types/components/menu/menu-expando.d.ts +14 -5
  144. package/dist/types/components/menu/menu-expando.d.ts.map +1 -1
  145. package/dist/types/components/menu/menu-simple.d.ts +4 -5
  146. package/dist/types/components/menu/menu-simple.d.ts.map +1 -1
  147. package/dist/types/components/nerdjoke/nerdjoke.d.ts +1 -1
  148. package/dist/types/components/nerdjoke/nerdjoke.d.ts.map +1 -1
  149. package/dist/types/components/seo/googleanalytics.d.ts.map +1 -1
  150. package/dist/types/components/seo/metadata.components.d.ts +2 -2
  151. package/dist/types/components/seo/metadata.components.d.ts.map +1 -1
  152. package/dist/types/components/seo/schema-blogposting.d.ts +7 -4
  153. package/dist/types/components/seo/schema-blogposting.d.ts.map +1 -1
  154. package/dist/types/components/seo/schema-recipe.d.ts +29 -30
  155. package/dist/types/components/seo/schema-recipe.d.ts.map +1 -1
  156. package/dist/types/components/seo/schema-services.d.ts +19 -9
  157. package/dist/types/components/seo/schema-services.d.ts.map +1 -1
  158. package/dist/types/components/shoppingcart/paypal.d.ts +1 -1
  159. package/dist/types/components/shoppingcart/paypal.d.ts.map +1 -1
  160. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +77 -28
  161. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
  162. package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts +4 -23
  163. package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts.map +1 -1
  164. package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts +10 -11
  165. package/dist/types/components/sitebuilder/config/CompoundFontSelector.d.ts.map +1 -1
  166. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +41 -174
  167. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  168. package/dist/types/components/sitebuilder/config/FontSelector.d.ts +12 -13
  169. package/dist/types/components/sitebuilder/config/FontSelector.d.ts.map +1 -1
  170. package/dist/types/components/sitebuilder/form/formbuilder.d.ts +7 -3
  171. package/dist/types/components/sitebuilder/form/formbuilder.d.ts.map +1 -1
  172. package/dist/types/components/sitebuilder/form/formcomponents.d.ts +1 -1
  173. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
  174. package/dist/types/components/sitebuilder/form/formengine.d.ts +1 -2
  175. package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -1
  176. package/dist/types/components/sitebuilder/form/formextractor.d.ts +5 -4
  177. package/dist/types/components/sitebuilder/form/formextractor.d.ts.map +1 -1
  178. package/dist/types/components/sitebuilder/form/formtypes.d.ts +3 -3
  179. package/dist/types/components/sitebuilder/form/formtypes.d.ts.map +1 -1
  180. package/dist/types/components/sitebuilder/form/formvalidator.d.ts +8 -3
  181. package/dist/types/components/sitebuilder/form/formvalidator.d.ts.map +1 -1
  182. package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts +2 -3
  183. package/dist/types/components/sitebuilder/page/components/ComponentPropertiesForm.d.ts.map +1 -1
  184. package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts +2 -3
  185. package/dist/types/components/sitebuilder/page/components/ComponentSelector.d.ts.map +1 -1
  186. package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts +2 -3
  187. package/dist/types/components/sitebuilder/page/components/ComponentTree.d.ts.map +1 -1
  188. package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts +8 -7
  189. package/dist/types/components/sitebuilder/page/components/PageBuilderUI.d.ts.map +1 -1
  190. package/dist/types/components/sitebuilder/page/components/PageEngine.d.ts.map +1 -1
  191. package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts +2 -3
  192. package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -1
  193. package/dist/types/components/sitebuilder/page/lib/componentMap.d.ts +1 -1
  194. package/dist/types/components/structured/markdown.d.ts +1 -3
  195. package/dist/types/components/structured/markdown.d.ts.map +1 -1
  196. package/dist/types/components/structured/recipe.d.ts +5 -32
  197. package/dist/types/components/structured/recipe.d.ts.map +1 -1
  198. package/dist/types/components/structured/socialcard.d.ts +4 -0
  199. package/dist/types/components/structured/socialcard.d.ts.map +1 -1
  200. package/dist/types/components/structured/timeline.d.ts +1 -3
  201. package/dist/types/components/structured/timeline.d.ts.map +1 -1
  202. package/dist/types/components/utilities/functions.d.ts +20 -0
  203. package/dist/types/components/utilities/functions.d.ts.map +1 -1
  204. package/dist/types/components/utilities/gemini-api.client.d.ts +38 -0
  205. package/dist/types/components/utilities/gemini-api.client.d.ts.map +1 -0
  206. package/dist/types/components/utilities/gemini-api.server.d.ts +17 -0
  207. package/dist/types/components/utilities/gemini-api.server.d.ts.map +1 -0
  208. package/dist/types/index.adminclient.d.ts +27 -0
  209. package/dist/types/index.adminclient.d.ts.map +1 -0
  210. package/dist/types/index.adminserver.d.ts +19 -0
  211. package/dist/types/index.adminserver.d.ts.map +1 -0
  212. package/dist/types/index.d.ts +4 -18
  213. package/dist/types/index.server.d.ts +5 -28
  214. package/dist/types/stories/admin/site-health.stories.d.ts +4 -0
  215. package/dist/types/stories/admin/site-health.stories.d.ts.map +1 -1
  216. package/dist/types/stories/general/sidepanel.stories.d.ts.map +1 -1
  217. package/dist/types/stories/general/smartimage.stories.d.ts +74 -2
  218. package/dist/types/stories/general/smartimage.stories.d.ts.map +1 -1
  219. package/dist/types/tests/site-health-cloudwatch.test.d.ts +2 -0
  220. package/dist/types/tests/site-health-cloudwatch.test.d.ts.map +1 -0
  221. package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts +2 -0
  222. package/dist/types/tests/site-health-on-site-seo.integration.test.d.ts.map +1 -0
  223. package/package.json +20 -10
  224. package/README.COMPONENTS.md +0 -2162
  225. package/dist/components/cms/pixelated.linkedin.js +0 -180
  226. package/dist/components/cms/pixelated.linkedin1.js +0 -84
  227. package/dist/components/cms/pixelated.linkedin2.js +0 -92
  228. package/dist/types/components/cms/pixelated.linkedin.d.ts +0 -2
  229. package/dist/types/components/cms/pixelated.linkedin.d.ts.map +0 -1
  230. package/dist/types/components/cms/pixelated.linkedin1.d.ts +0 -2
  231. package/dist/types/components/cms/pixelated.linkedin1.d.ts.map +0 -1
  232. package/dist/types/components/cms/pixelated.linkedin2.d.ts +0 -2
  233. package/dist/types/components/cms/pixelated.linkedin2.d.ts.map +0 -1
  234. package/dist/types/tests/pixelated.menu-expando.test.d.ts +0 -2
  235. 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: 15000 // Reduced 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: 2000 });
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
- await page.close();
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
- const rawData = collector(html, pageData.title);
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
- finally {
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, 5);
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]) => (_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(score) }, children: formatScore(score) }), _jsx("div", { className: "health-score-bar", children: _jsx("div", { className: "health-score-fill", style: {
61
- width: score !== null ? `${score * 100}%` : '0%',
62
- backgroundColor: score !== null ? getScoreColor(score) : '#6b7280'
63
- } }) })] }, category))) }), _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
+ .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
- export function SiteHealthTemplate({ siteName, title, children, fetchData, enableCacheControl = false, columnSpan = 1 }) {
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
  }