@pixelated-tech/components 3.13.16 → 3.14.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 (247) hide show
  1. package/dist/components/admin/site-health/google.api.integration.js +5 -4
  2. package/dist/components/admin/site-health/site-health-cloudwatch.integration.js +3 -2
  3. package/dist/components/config/crypto.js +17 -1
  4. package/dist/components/general/cache-manager.js +19 -2
  5. package/dist/components/general/carousel.drag.js +21 -24
  6. package/dist/components/general/intersection-observer.js +4 -0
  7. package/dist/components/general/metadata.functions.js +1 -1
  8. package/dist/components/general/sitemap.js +2 -1
  9. package/dist/components/general/utilities.js +68 -0
  10. package/dist/components/integrations/contentful.delivery.js +16 -16
  11. package/dist/components/integrations/contentful.items.components.js +2 -11
  12. package/dist/components/integrations/flickr.js +7 -4
  13. package/dist/components/integrations/googleplaces.js +144 -0
  14. package/dist/components/integrations/socialcard.js +5 -2
  15. package/dist/components/integrations/wordpress.components.js +2 -1
  16. package/dist/components/shoppingcart/ebay.components.js +5 -5
  17. package/dist/components/shoppingcart/ebay.functions.js +5 -3
  18. package/dist/components/shoppingcart/shipping.to.json +3 -4
  19. package/dist/components/shoppingcart/shoppingcart.components.js +7 -5
  20. package/dist/components/shoppingcart/shoppingcart.css +1 -1
  21. package/dist/components/shoppingcart/shoppingcart.functions.js +5 -14
  22. package/dist/components/sitebuilder/form/formcomponents.js +151 -0
  23. package/dist/components/sitebuilder/form/formutils.js +3 -0
  24. package/dist/components/sitebuilder/page/lib/pageStorageContentful.js +2 -2
  25. package/dist/config/pixelated.config.json.enc +1 -1
  26. package/dist/data/form.json +18 -0
  27. package/dist/index.adminserver.js +1 -3
  28. package/dist/index.js +1 -1
  29. package/dist/index.server.js +1 -0
  30. package/dist/scripts/create-pixelated-app.js +187 -79
  31. package/dist/scripts/create-pixelated-app.json +51 -1
  32. package/dist/scripts/pixelated-eslint-plugin.js +142 -0
  33. package/dist/scripts/release.sh +14 -1
  34. package/dist/scripts/update.sh +2 -0
  35. package/dist/types/components/admin/deploy/deployment.integration.d.ts +1 -0
  36. package/dist/types/components/admin/deploy/deployment.integration.d.ts.map +1 -1
  37. package/dist/types/components/admin/site-health/google.api.integration.d.ts.map +1 -1
  38. package/dist/types/components/admin/site-health/site-health-cloudwatch.integration.d.ts.map +1 -1
  39. package/dist/types/components/config/config.types.d.ts +23 -0
  40. package/dist/types/components/config/config.types.d.ts.map +1 -1
  41. package/dist/types/components/config/crypto.d.ts +1 -0
  42. package/dist/types/components/config/crypto.d.ts.map +1 -1
  43. package/dist/types/components/general/cache-manager.d.ts +16 -2
  44. package/dist/types/components/general/cache-manager.d.ts.map +1 -1
  45. package/dist/types/components/general/carousel.drag.d.ts.map +1 -1
  46. package/dist/types/components/general/intersection-observer.d.ts.map +1 -1
  47. package/dist/types/components/general/sitemap.d.ts.map +1 -1
  48. package/dist/types/components/general/utilities.d.ts +37 -0
  49. package/dist/types/components/general/utilities.d.ts.map +1 -1
  50. package/dist/types/components/integrations/contentful.delivery.d.ts +16 -16
  51. package/dist/types/components/integrations/contentful.items.components.d.ts.map +1 -1
  52. package/dist/types/components/integrations/flickr.d.ts.map +1 -1
  53. package/dist/types/components/integrations/googleplaces.d.ts +61 -0
  54. package/dist/types/components/integrations/googleplaces.d.ts.map +1 -0
  55. package/dist/types/components/integrations/socialcard.d.ts.map +1 -1
  56. package/dist/types/components/integrations/wordpress.components.d.ts.map +1 -1
  57. package/dist/types/components/shoppingcart/ebay.functions.d.ts.map +1 -1
  58. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
  59. package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts +2 -2
  60. package/dist/types/components/shoppingcart/shoppingcart.functions.d.ts.map +1 -1
  61. package/dist/types/components/sitebuilder/form/formcomponents.d.ts +22 -0
  62. package/dist/types/components/sitebuilder/form/formcomponents.d.ts.map +1 -1
  63. package/dist/types/components/sitebuilder/form/formutils.d.ts.map +1 -1
  64. package/dist/types/index.adminserver.d.ts +1 -3
  65. package/dist/types/index.d.ts +1 -1
  66. package/dist/types/index.server.d.ts +1 -0
  67. package/dist/types/scripts/create-pixelated-app.d.ts +3 -0
  68. package/dist/types/scripts/create-pixelated-app.d.ts.map +1 -1
  69. package/dist/types/scripts/pixelated-eslint-plugin.d.ts +20 -0
  70. package/dist/types/stories/integrations/contentful.items.stories.d.ts.map +1 -0
  71. package/dist/types/stories/integrations/contentful.stories.d.ts.map +1 -0
  72. package/dist/types/stories/integrations/google.reviews.stories.d.ts.map +1 -0
  73. package/dist/types/stories/integrations/googlesearch.stories.d.ts.map +1 -0
  74. package/dist/types/stories/integrations/gravatar.stories.d.ts.map +1 -0
  75. package/dist/types/stories/integrations/instagram.stories.d.ts.map +1 -0
  76. package/dist/types/stories/integrations/wordpress.stories.d.ts.map +1 -0
  77. package/dist/types/test/test-utils.d.ts +2 -0
  78. package/dist/types/test/test-utils.d.ts.map +1 -1
  79. package/dist/types/tests/404.test.d.ts +2 -0
  80. package/dist/types/tests/404.test.d.ts.map +1 -0
  81. package/dist/types/tests/carousel.drag.test.d.ts +2 -0
  82. package/dist/types/tests/carousel.drag.test.d.ts.map +1 -0
  83. package/dist/types/tests/carouselDrag.test.d.ts +2 -0
  84. package/dist/types/tests/carouselDrag.test.d.ts.map +1 -0
  85. package/dist/types/tests/componentAnalysis.test.d.ts +2 -0
  86. package/dist/types/tests/componentAnalysis.test.d.ts.map +1 -0
  87. package/dist/types/tests/componentDiscovery.test.d.ts +2 -0
  88. package/dist/types/tests/componentDiscovery.test.d.ts.map +1 -0
  89. package/dist/types/tests/componentMap.test.d.ts +2 -0
  90. package/dist/types/tests/componentMap.test.d.ts.map +1 -0
  91. package/dist/types/tests/contentful.items.components.test.d.ts +2 -0
  92. package/dist/types/tests/contentful.items.components.test.d.ts.map +1 -0
  93. package/dist/types/tests/contentful.management.test.d.ts +2 -0
  94. package/dist/types/tests/contentful.management.test.d.ts.map +1 -0
  95. package/dist/types/tests/contentfulManagement.test.d.ts +2 -0
  96. package/dist/types/tests/contentfulManagement.test.d.ts.map +1 -0
  97. package/dist/types/tests/countup.test.d.ts +2 -0
  98. package/dist/types/tests/countup.test.d.ts.map +1 -0
  99. package/dist/types/tests/crypto.test.d.ts +2 -0
  100. package/dist/types/tests/crypto.test.d.ts.map +1 -0
  101. package/dist/types/tests/deployment.integration.test.d.ts +2 -0
  102. package/dist/types/tests/deployment.integration.test.d.ts.map +1 -0
  103. package/dist/types/tests/ebay.components.test.d.ts +2 -0
  104. package/dist/types/tests/ebay.components.test.d.ts.map +1 -0
  105. package/dist/types/tests/ebayComponents.test.d.ts +2 -0
  106. package/dist/types/tests/ebayComponents.test.d.ts.map +1 -0
  107. package/dist/types/tests/flickr.test.d.ts +2 -0
  108. package/dist/types/tests/flickr.test.d.ts.map +1 -0
  109. package/dist/types/tests/formgoogleplacesinput.test.d.ts +2 -0
  110. package/dist/types/tests/formgoogleplacesinput.test.d.ts.map +1 -0
  111. package/dist/types/tests/formutils.test.d.ts +2 -0
  112. package/dist/types/tests/formutils.test.d.ts.map +1 -0
  113. package/dist/types/tests/formvalidator.test.d.ts +2 -0
  114. package/dist/types/tests/formvalidator.test.d.ts.map +1 -0
  115. package/dist/types/tests/gemini-api.client.test.d.ts +2 -0
  116. package/dist/types/tests/gemini-api.client.test.d.ts.map +1 -0
  117. package/dist/types/tests/gemini-api.server.test.d.ts +2 -0
  118. package/dist/types/tests/gemini-api.server.test.d.ts.map +1 -0
  119. package/dist/types/tests/geminiApi.test.d.ts +2 -0
  120. package/dist/types/tests/geminiApi.test.d.ts.map +1 -0
  121. package/dist/types/tests/google.reviews.components.test.d.ts +2 -0
  122. package/dist/types/tests/google.reviews.components.test.d.ts.map +1 -0
  123. package/dist/types/tests/googleanalytics.test.d.ts +2 -0
  124. package/dist/types/tests/googleanalytics.test.d.ts.map +1 -0
  125. package/dist/types/tests/googlemap.test.d.ts +2 -0
  126. package/dist/types/tests/googlemap.test.d.ts.map +1 -0
  127. package/dist/types/tests/gravatar.functions.test.d.ts +2 -0
  128. package/dist/types/tests/gravatar.functions.test.d.ts.map +1 -0
  129. package/dist/types/tests/hubspot.components.test.d.ts +2 -0
  130. package/dist/types/tests/hubspot.components.test.d.ts.map +1 -0
  131. package/dist/types/tests/image-utils.test.d.ts +2 -0
  132. package/dist/types/tests/image-utils.test.d.ts.map +1 -0
  133. package/dist/types/tests/instagram.components.test.d.ts +2 -0
  134. package/dist/types/tests/instagram.components.test.d.ts.map +1 -0
  135. package/dist/types/tests/instagram.functions.test.d.ts +2 -0
  136. package/dist/types/tests/instagram.functions.test.d.ts.map +1 -0
  137. package/dist/types/tests/intersection-observer.test.d.ts +2 -0
  138. package/dist/types/tests/intersection-observer.test.d.ts.map +1 -0
  139. package/dist/types/tests/metadata.functions.test.d.ts +2 -0
  140. package/dist/types/tests/metadata.functions.test.d.ts.map +1 -0
  141. package/dist/types/tests/metadataComponents.test.d.ts +2 -0
  142. package/dist/types/tests/metadataComponents.test.d.ts.map +1 -0
  143. package/dist/types/tests/page-storage.test.d.ts +2 -0
  144. package/dist/types/tests/page-storage.test.d.ts.map +1 -0
  145. package/dist/types/tests/pageStorageContentful.test.d.ts +2 -0
  146. package/dist/types/tests/pageStorageContentful.test.d.ts.map +1 -0
  147. package/dist/types/tests/pageStorageLocal.test.d.ts +2 -0
  148. package/dist/types/tests/pageStorageLocal.test.d.ts.map +1 -0
  149. package/dist/types/tests/pixelated.test.d.ts +2 -0
  150. package/dist/types/tests/pixelated.test.d.ts.map +1 -0
  151. package/dist/types/tests/propTypeIntrospection.test.d.ts +2 -0
  152. package/dist/types/tests/propTypeIntrospection.test.d.ts.map +1 -0
  153. package/dist/types/tests/save-route-example.test.d.ts +2 -0
  154. package/dist/types/tests/save-route-example.test.d.ts.map +1 -0
  155. package/dist/types/tests/saveRouteExample.test.d.ts +2 -0
  156. package/dist/types/tests/saveRouteExample.test.d.ts.map +1 -0
  157. package/dist/types/tests/seoConstants.test.d.ts +2 -0
  158. package/dist/types/tests/seoConstants.test.d.ts.map +1 -0
  159. package/dist/types/tests/site-health-accessibility.test.d.ts +2 -0
  160. package/dist/types/tests/site-health-accessibility.test.d.ts.map +1 -0
  161. package/dist/types/tests/site-health-cloudwatch.integration.test.d.ts +2 -0
  162. package/dist/types/tests/site-health-cloudwatch.integration.test.d.ts.map +1 -0
  163. package/dist/types/tests/site-health-dependency-vulnerabilities.test.d.ts +2 -0
  164. package/dist/types/tests/site-health-dependency-vulnerabilities.test.d.ts.map +1 -0
  165. package/dist/types/tests/site-health-github.test.d.ts +2 -0
  166. package/dist/types/tests/site-health-github.test.d.ts.map +1 -0
  167. package/dist/types/tests/site-health-google-analytics.integration.test.d.ts +2 -0
  168. package/dist/types/tests/site-health-google-analytics.integration.test.d.ts.map +1 -0
  169. package/dist/types/tests/site-health-google-analytics.test.d.ts +2 -0
  170. package/dist/types/tests/site-health-google-analytics.test.d.ts.map +1 -0
  171. package/dist/types/tests/site-health-google-search-console.integration.test.d.ts +2 -0
  172. package/dist/types/tests/site-health-google-search-console.integration.test.d.ts.map +1 -0
  173. package/dist/types/tests/site-health-google-search-console.test.d.ts +2 -0
  174. package/dist/types/tests/site-health-google-search-console.test.d.ts.map +1 -0
  175. package/dist/types/tests/site-health-mock-context.test.d.ts +2 -0
  176. package/dist/types/tests/site-health-mock-context.test.d.ts.map +1 -0
  177. package/dist/types/tests/site-health-on-site-seo.test.d.ts +2 -0
  178. package/dist/types/tests/site-health-on-site-seo.test.d.ts.map +1 -0
  179. package/dist/types/tests/site-health-performance.test.d.ts +2 -0
  180. package/dist/types/tests/site-health-performance.test.d.ts.map +1 -0
  181. package/dist/types/tests/site-health-security.integration.test.d.ts +2 -0
  182. package/dist/types/tests/site-health-security.integration.test.d.ts.map +1 -0
  183. package/dist/types/tests/site-health-security.test.d.ts +2 -0
  184. package/dist/types/tests/site-health-security.test.d.ts.map +1 -0
  185. package/dist/types/tests/site-health-seo.test.d.ts +2 -0
  186. package/dist/types/tests/site-health-seo.test.d.ts.map +1 -0
  187. package/dist/types/tests/site-health-uptime.integration.test.d.ts +2 -0
  188. package/dist/types/tests/site-health-uptime.integration.test.d.ts.map +1 -0
  189. package/dist/types/tests/site-health-uptime.test.d.ts +2 -0
  190. package/dist/types/tests/site-health-uptime.test.d.ts.map +1 -0
  191. package/dist/types/tests/siteHealthGaIntegration.test.d.ts +2 -0
  192. package/dist/types/tests/siteHealthGaIntegration.test.d.ts.map +1 -0
  193. package/dist/types/tests/siteHealthGscIntegration.test.d.ts +2 -0
  194. package/dist/types/tests/siteHealthGscIntegration.test.d.ts.map +1 -0
  195. package/dist/types/tests/spotify.components.test.d.ts +2 -0
  196. package/dist/types/tests/spotify.components.test.d.ts.map +1 -0
  197. package/dist/types/tests/spotify.functions.test.d.ts +2 -0
  198. package/dist/types/tests/spotify.functions.test.d.ts.map +1 -0
  199. package/dist/types/tests/test-utils.d.ts +7 -0
  200. package/dist/types/tests/test-utils.d.ts.map +1 -0
  201. package/dist/types/tests/usePageBuilder.test.d.ts +2 -0
  202. package/dist/types/tests/usePageBuilder.test.d.ts.map +1 -0
  203. package/package.json +19 -16
  204. package/dist/components/admin/site-health/site-health-google-analytics.integration.js +0 -6
  205. package/dist/components/admin/site-health/site-health-google-search-console.integration.js +0 -6
  206. package/dist/components/general/proxy-csp-listener.js +0 -20
  207. package/dist/scripts/create-pixelated-app-template-mapper.js +0 -80
  208. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts +0 -6
  209. package/dist/types/components/admin/site-health/site-health-google-analytics.integration.d.ts.map +0 -1
  210. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts +0 -6
  211. package/dist/types/components/admin/site-health/site-health-google-search-console.integration.d.ts.map +0 -1
  212. package/dist/types/components/general/proxy-csp-listener.d.ts +0 -15
  213. package/dist/types/components/general/proxy-csp-listener.d.ts.map +0 -1
  214. package/dist/types/scripts/create-pixelated-app-template-mapper.d.ts +0 -5
  215. package/dist/types/scripts/create-pixelated-app-template-mapper.d.ts.map +0 -1
  216. package/dist/types/stories/general/callout.many.stories.d.ts +0 -7
  217. package/dist/types/stories/general/callout.many.stories.d.ts.map +0 -1
  218. package/dist/types/stories/general/contentful.item.stories.d.ts +0 -12
  219. package/dist/types/stories/general/contentful.item.stories.d.ts.map +0 -1
  220. package/dist/types/stories/general/contentful.items.stories.d.ts.map +0 -1
  221. package/dist/types/stories/general/contentful.stories.d.ts.map +0 -1
  222. package/dist/types/stories/general/global-error.stories.d.ts +0 -26
  223. package/dist/types/stories/general/global-error.stories.d.ts.map +0 -1
  224. package/dist/types/stories/general/google.reviews.stories.d.ts.map +0 -1
  225. package/dist/types/stories/general/googleanalytics.stories.d.ts +0 -14
  226. package/dist/types/stories/general/googleanalytics.stories.d.ts.map +0 -1
  227. package/dist/types/stories/general/googlesearch.stories.d.ts.map +0 -1
  228. package/dist/types/stories/general/gravatar.stories.d.ts.map +0 -1
  229. package/dist/types/stories/general/instagram.stories.d.ts.map +0 -1
  230. package/dist/types/stories/general/loading.stories.d.ts +0 -11
  231. package/dist/types/stories/general/loading.stories.d.ts.map +0 -1
  232. package/dist/types/stories/general/metadata.stories.d.ts +0 -25
  233. package/dist/types/stories/general/metadata.stories.d.ts.map +0 -1
  234. package/dist/types/stories/general/schema.stories.d.ts +0 -62
  235. package/dist/types/stories/general/schema.stories.d.ts.map +0 -1
  236. package/dist/types/stories/general/sitemap.stories.d.ts +0 -8
  237. package/dist/types/stories/general/sitemap.stories.d.ts.map +0 -1
  238. package/dist/types/stories/general/wordpress.stories.d.ts.map +0 -1
  239. package/dist/types/stories/integrations/schema-podcast.stories.d.ts +0 -45
  240. package/dist/types/stories/integrations/schema-podcast.stories.d.ts.map +0 -1
  241. /package/dist/types/stories/{general → integrations}/contentful.items.stories.d.ts +0 -0
  242. /package/dist/types/stories/{general → integrations}/contentful.stories.d.ts +0 -0
  243. /package/dist/types/stories/{general → integrations}/google.reviews.stories.d.ts +0 -0
  244. /package/dist/types/stories/{general → integrations}/googlesearch.stories.d.ts +0 -0
  245. /package/dist/types/stories/{general → integrations}/gravatar.stories.d.ts +0 -0
  246. /package/dist/types/stories/{general → integrations}/instagram.stories.d.ts +0 -0
  247. /package/dist/types/stories/{general → integrations}/wordpress.stories.d.ts +0 -0
@@ -6,6 +6,7 @@
6
6
  "use server";
7
7
  import { google } from 'googleapis';
8
8
  import { CacheManager } from '../../general/cache-manager';
9
+ import { getDomain } from '../../general/utilities';
9
10
  import { calculateDateRanges, formatChartDate, getCachedData, setCachedData } from './google.api.utils';
10
11
  // Migration-time debug flag (owner requested): verbose cache traces during migration
11
12
  const debug = false; // keep as literal during migration for traceability
@@ -72,8 +73,8 @@ export async function createSearchConsoleClient(config) {
72
73
  auth: result.auth
73
74
  };
74
75
  }
75
- // Cache for analytics data (1 hour)
76
- const analyticsCache = new CacheManager({ prefix: 'sitehealth-analytics-', ttl: 60 * 60 * 1000 });
76
+ // Cache for analytics data (1 hour) — isolated per domain
77
+ const analyticsCache = new CacheManager({ domain: getDomain(), namespace: 'analytics', ttl: 60 * 60 * 1000 });
77
78
  /**
78
79
  * Get Google Analytics data for a site with current/previous period comparison
79
80
  */
@@ -168,8 +169,8 @@ export async function getGoogleAnalyticsData(config, siteName, startDate, endDat
168
169
  };
169
170
  }
170
171
  }
171
- // Cache for search console data (1 hour)
172
- const searchConsoleCache = new CacheManager({ prefix: 'sitehealth-searchconsole-', ttl: 60 * 60 * 1000 });
172
+ // Cache for search console data (1 hour) — isolated per domain
173
+ const searchConsoleCache = new CacheManager({ domain: getDomain(), namespace: 'searchconsole', ttl: 60 * 60 * 1000 });
173
174
  /**
174
175
  * Get Google Search Console data for a site with current/previous period comparison
175
176
  */
@@ -5,10 +5,11 @@
5
5
  "use server";
6
6
  import { CloudWatchClient, GetMetricDataCommand } from '@aws-sdk/client-cloudwatch';
7
7
  import { CacheManager } from '../../general/cache-manager';
8
+ import { getDomain } from '../../general/utilities';
8
9
  import { getFullPixelatedConfig } from '../../config/config';
9
10
  const debug = false; // migration-time verbose logging
10
- // Cache for health check data (15 minutes)
11
- const healthCheckCache = new CacheManager({ prefix: 'sitehealth-cloudwatch-', ttl: 15 * 60 * 1000 });
11
+ // Cache for health check data (15 minutes) — isolated per domain
12
+ const healthCheckCache = new CacheManager({ domain: getDomain(), namespace: 'cloudwatch', ttl: 15 * 60 * 1000 });
12
13
  /**
13
14
  * Get health check data for a site using CloudWatch metrics
14
15
  */
@@ -56,7 +56,23 @@ export function decrypt(payload, keyHex) {
56
56
  }
57
57
  /**
58
58
  * Checks if a string is encrypted using our format.
59
+ * Validates that the string has the proper structure: pxl:v1:iv:authTag:encryptedContent
59
60
  */
60
61
  export function isEncrypted(text) {
61
- return typeof text === 'string' && text.startsWith(ENCRYPTED_PREFIX);
62
+ if (typeof text !== 'string' || !text.startsWith(ENCRYPTED_PREFIX)) {
63
+ return false;
64
+ }
65
+ // Remove prefix and check that we have the expected structure (3 colon-separated parts)
66
+ const data = text.slice(ENCRYPTED_PREFIX.length);
67
+ const parts = data.split(':');
68
+ // Must have exactly 3 parts: iv, authTag, encryptedContent
69
+ if (parts.length !== 3) {
70
+ return false;
71
+ }
72
+ // Each part should be valid hex and have minimum length
73
+ // IV: 12 bytes = 24 hex chars, AuthTag: 16 bytes = 32 hex chars, Content: at least 1 byte = 2 hex chars
74
+ const [ivHex, authTagHex, encryptedHex] = parts;
75
+ return (ivHex.length === 24 && /^[0-9a-f]*$/i.test(ivHex) &&
76
+ authTagHex.length === 32 && /^[0-9a-f]*$/i.test(authTagHex) &&
77
+ encryptedHex.length > 0 && /^[0-9a-f]*$/i.test(encryptedHex));
62
78
  }
@@ -3,6 +3,19 @@
3
3
  *
4
4
  * A unified caching utility that supports Memory, LocalStorage, and SessionStorage.
5
5
  * Includes TTL (Time-To-Live) support and automatic SSR fallback.
6
+ *
7
+ * ARCHITECTURE NOTE: The `domain` parameter is required to enforce clear namespace separation and
8
+ * provide future-proofing. While the library is currently deployed **per-domain** (each site gets
9
+ * its own isolated copy of this library), the domain parameter serves important purposes:
10
+ * 1. **Code Clarity**: Makes the multi-tenant intent explicit in code
11
+ * 2. **Future-Proofing**: If architecture changes to shared backends (Redis), isolation is built-in
12
+ * 3. **Safety**: Prevents accidental empty prefixes that could cause collisions
13
+ *
14
+ * Each domain runs in isolation:
15
+ * - Client-side: Uses window.location to determine domain (e.g., pixelvivid.com → "pixelvivid")
16
+ * - Server-side: Defaults to 'pixelated' (safe because server caches are in-memory per process)
17
+ * - Memory caches: Naturally isolated to their process, so multi-tenancy doesn't apply
18
+ * - Local/Session storage: Browser-based, inherently isolated by domain
6
19
  */
7
20
  export class CacheManager {
8
21
  memoryCache = new Map();
@@ -10,10 +23,14 @@ export class CacheManager {
10
23
  mode;
11
24
  prefix;
12
25
  ttl;
13
- constructor(options = {}) {
26
+ constructor(options) {
14
27
  this.mode = options.mode || 'memory';
15
- this.prefix = options.prefix || 'pix_';
16
28
  this.ttl = options.ttl || this.defaultTTL;
29
+ // Build prefix from domain + optional namespace
30
+ // domain + namespace pattern: "pixelvivid_cart_" or "sitehealth_analytics_"
31
+ this.prefix = options.namespace
32
+ ? `${options.domain}_${options.namespace}_`
33
+ : `${options.domain}_`;
17
34
  // Fallback to memory if browser storage is requested but unavailable (SSR/Node environment)
18
35
  if (typeof window === 'undefined' && (this.mode === 'local' || this.mode === 'session')) {
19
36
  this.mode = 'memory';
@@ -188,28 +188,25 @@ export function DragHandler(props) {
188
188
  }
189
189
  }
190
190
  ;
191
- function handleEventListeners(activeIndex) {
192
- useEffect(() => {
193
- // const container = document.getElementById(classSelector);
194
- // { passive: true } is needed for preventDefault to work on drag events
195
- document.addEventListener('touchstart', dragStart, { capture: true, passive: false });
196
- document.addEventListener('touchmove', draggable, { capture: true, passive: false });
197
- document.addEventListener('touchend', dragEnd, { capture: true, passive: true });
198
- document.addEventListener('mousedown', dragStart, { capture: true, passive: false });
199
- document.addEventListener('mousemove', draggable, { capture: true, passive: false });
200
- document.addEventListener('mouseup', dragEnd, { capture: true, passive: true });
201
- document.addEventListener('transitionend', transitionEnd, { capture: true, passive: true });
202
- return () => {
203
- // { passive: true } is not needed to match and remove an event listener
204
- document.removeEventListener('touchstart', dragStart, { capture: true });
205
- document.removeEventListener('touchmove', draggable, { capture: true });
206
- document.removeEventListener('touchend', dragEnd, { capture: true });
207
- document.removeEventListener('mousedown', dragStart, { capture: true });
208
- document.removeEventListener('mousemove', draggable, { capture: true });
209
- document.removeEventListener('mouseup', dragEnd, { capture: true });
210
- document.removeEventListener('transitionend', transitionEnd, { capture: true });
211
- };
212
- }, [activeIndex]);
213
- }
214
- handleEventListeners(props.activeIndex);
191
+ useEffect(() => {
192
+ // const container = document.getElementById(classSelector);
193
+ // { passive: true } is needed for preventDefault to work on drag events
194
+ document.addEventListener('touchstart', dragStart, { capture: true, passive: false });
195
+ document.addEventListener('touchmove', draggable, { capture: true, passive: false });
196
+ document.addEventListener('touchend', dragEnd, { capture: true, passive: true });
197
+ document.addEventListener('mousedown', dragStart, { capture: true, passive: false });
198
+ document.addEventListener('mousemove', draggable, { capture: true, passive: false });
199
+ document.addEventListener('mouseup', dragEnd, { capture: true, passive: true });
200
+ document.addEventListener('transitionend', transitionEnd, { capture: true, passive: true });
201
+ return () => {
202
+ // { passive: true } is not needed to match and remove an event listener
203
+ document.removeEventListener('touchstart', dragStart, { capture: true });
204
+ document.removeEventListener('touchmove', draggable, { capture: true });
205
+ document.removeEventListener('touchend', dragEnd, { capture: true });
206
+ document.removeEventListener('mousedown', dragStart, { capture: true });
207
+ document.removeEventListener('mousemove', draggable, { capture: true });
208
+ document.removeEventListener('mouseup', dragEnd, { capture: true });
209
+ document.removeEventListener('transitionend', transitionEnd, { capture: true });
210
+ };
211
+ }, [props.activeIndex]);
215
212
  }
@@ -99,6 +99,8 @@ export function observeIntersection(selector, callback, options = {}) {
99
99
  * Check if an element is fully in the viewport
100
100
  */
101
101
  export function isElementInViewport(element) {
102
+ if (!element)
103
+ return false;
102
104
  const rect = element.getBoundingClientRect();
103
105
  return (rect.top >= 0 &&
104
106
  rect.left >= 0 &&
@@ -109,6 +111,8 @@ export function isElementInViewport(element) {
109
111
  * Check if an element is partially in the viewport
110
112
  */
111
113
  export function isElementPartiallyInViewport(element) {
114
+ if (!element)
115
+ return false;
112
116
  const rect = element.getBoundingClientRect();
113
117
  return (rect.top < (window.innerHeight || document.documentElement.clientHeight) &&
114
118
  rect.left < (window.innerWidth || document.documentElement.clientWidth) &&
@@ -6,7 +6,7 @@ export function descriptionToKeywords(descriptionText, numKeywords = 5, customSt
6
6
  // Define a default list of common English stop words
7
7
  const defaultStopWords = new Set([
8
8
  'a', 'an', 'and', 'are', 'as', 'at', 'be', 'but', 'by', 'for', 'if', 'in', 'into',
9
- 'is', 'it', 'no', 'not', 'of', 'on', 'or', 'such', 'that', 'the', 'their',
9
+ 'is', 'it', 'no', 'not', 'of', 'on', 'or', 'over', 'such', 'that', 'the', 'their',
10
10
  'then', 'there', 'these', 'they', 'this', 'to', 'was', 'will', 'with'
11
11
  ]);
12
12
  const allStopWords = new Set([...defaultStopWords, ...customStopWords]);
@@ -6,6 +6,7 @@ import { getContentfulFieldValues, getContentfulAssets } from "../integrations/c
6
6
  import { getEbayAppToken, getEbayItemsSearch } from "../shoppingcart/ebay.functions";
7
7
  import { getFullPixelatedConfig } from '../config/config';
8
8
  import { CacheManager } from '../general/cache-manager';
9
+ import { getDomain } from './utilities';
9
10
  /**
10
11
  * Helper to construct an origin string from a Next-like headers() object or plain values.
11
12
  * Accepts an object with `get(key)` method, or `undefined` and falls back to localhost origin.
@@ -438,7 +439,7 @@ export async function createEbayItemURLs(origin) {
438
439
  }
439
440
  const SITEMAP_TTL = 24 * 60 * 60 * 1000; // one day
440
441
  const EBAY_SITE_SITEMAP_KEY = 'ebay_sitemap_items';
441
- const ebaySitemapCache = new CacheManager({ mode: 'memory', prefix: 'ebaySitemap_', ttl: SITEMAP_TTL });
442
+ const ebaySitemapCache = new CacheManager({ mode: 'memory', domain: getDomain(), namespace: 'ebaySitemap', ttl: SITEMAP_TTL });
442
443
  function getEbayCacheTTL(configTTL) {
443
444
  if (typeof configTTL === 'number' && configTTL > 0) {
444
445
  return configTTL;
@@ -64,6 +64,74 @@ Array.prototype.contains = function(obj) {
64
64
  return this.indexOf(obj) > -1;
65
65
  };
66
66
  */
67
+ /**
68
+ * Get the domain name to use as a key component for CacheManager.
69
+ * Safe to call in browser contexts. For server-side, use getDomainFromHeaders() instead.
70
+ *
71
+ * Extracts the domain name from the current hostname to use as a cache/storage prefix.
72
+ * This ensures multi-tenant applications don't have key collisions across different domain instances.
73
+ *
74
+ * @returns Domain name suitable for multi-tenant cache isolation (lowercase, no special characters)
75
+ *
76
+ * @example
77
+ * const domain = getDomain();
78
+ * const cache = new CacheManager({
79
+ * mode: 'local',
80
+ * domain,
81
+ * namespace: 'checkout'
82
+ * });
83
+ *
84
+ * @example
85
+ * // In different environments:
86
+ * // www.pixelvivid.com → "pixelvivid"
87
+ * // manningmetalworks.com → "manningmetalworks"
88
+ * // localhost → "pixelated" (development)
89
+ */
90
+ export function getDomain() {
91
+ // Browser environment
92
+ if (typeof window !== 'undefined' && window.location?.hostname) {
93
+ return extractDomainName(window.location.hostname);
94
+ }
95
+ // SSR/Node environment - return safe fallback
96
+ // Library is deployed per-domain, so no actual multi-tenancy at library level
97
+ // Each domain runs its own isolated copy of this library
98
+ return 'pixelated';
99
+ }
100
+ /**
101
+ * Extract the domain name from a hostname string.
102
+ * Handles www prefixes and multi-level TLDs.
103
+ *
104
+ * @param hostname - The hostname (e.g., "www.example.com" or "example.com")
105
+ * @returns The domain name without www or TLD (lowercase)
106
+ *
107
+ * @example
108
+ * extractDomainName('www.pixelvivid.com') // → "pixelvivid"
109
+ * extractDomainName('manningmetalworks.com') // → "manningmetalworks"
110
+ * extractDomainName('localhost') // → "pixelated"
111
+ */
112
+ export function extractDomainName(hostname) {
113
+ if (!hostname)
114
+ return 'pixelated';
115
+ // Normalize: lowercase, trim whitespace
116
+ const normalized = hostname.toLowerCase().trim();
117
+ // Handle localhost / 127.0.0.1 - use development prefix
118
+ if (normalized === 'localhost' || normalized === '127.0.0.1' || normalized.startsWith('localhost:')) {
119
+ return 'pixelated';
120
+ }
121
+ // Split by dots
122
+ const parts = normalized.split('.');
123
+ // Single label (rare but possible in development)
124
+ if (parts.length === 1) {
125
+ return parts[0];
126
+ }
127
+ // Two labels: domain.com → domain
128
+ if (parts.length === 2) {
129
+ return parts[0];
130
+ }
131
+ // Multiple labels: www.domain.com or subdomain.domain.com → domain
132
+ // Take the second-to-last part (the domain before the TLD)
133
+ return parts[parts.length - 2];
134
+ }
67
135
  export function attributeMap(oldAttribute) {
68
136
  // https://reactjs.org/docs/dom-elements.html
69
137
  const attributes = {
@@ -114,10 +114,10 @@ getContentfulContentType.propTypes = {
114
114
  /** Contentful API configuration */
115
115
  apiProps: PropTypes.shape({
116
116
  proxyURL: PropTypes.string,
117
- base_url: PropTypes.string.isRequired,
118
- space_id: PropTypes.string.isRequired,
119
- environment: PropTypes.string.isRequired,
120
- access_token: PropTypes.string.isRequired,
117
+ base_url: PropTypes.string,
118
+ space_id: PropTypes.string,
119
+ environment: PropTypes.string,
120
+ access_token: PropTypes.string,
121
121
  }).isRequired,
122
122
  /** Content type ID to retrieve */
123
123
  contentType: PropTypes.string.isRequired,
@@ -142,10 +142,10 @@ getContentfulEntryByEntryID.propTypes = {
142
142
  /** Contentful API configuration */
143
143
  apiProps: PropTypes.shape({
144
144
  proxyURL: PropTypes.string,
145
- base_url: PropTypes.string.isRequired,
146
- space_id: PropTypes.string.isRequired,
147
- environment: PropTypes.string.isRequired,
148
- delivery_access_token: PropTypes.string.isRequired,
145
+ base_url: PropTypes.string,
146
+ space_id: PropTypes.string,
147
+ environment: PropTypes.string,
148
+ delivery_access_token: PropTypes.string,
149
149
  }).isRequired,
150
150
  /** Target entry ID */
151
151
  entry_id: PropTypes.string.isRequired,
@@ -239,10 +239,10 @@ getContentfulAssets.propTypes = {
239
239
  /** Contentful API configuration */
240
240
  apiProps: PropTypes.shape({
241
241
  proxyURL: PropTypes.string,
242
- base_url: PropTypes.string.isRequired,
243
- space_id: PropTypes.string.isRequired,
244
- environment: PropTypes.string.isRequired,
245
- access_token: PropTypes.string.isRequired,
242
+ base_url: PropTypes.string,
243
+ space_id: PropTypes.string,
244
+ environment: PropTypes.string,
245
+ access_token: PropTypes.string,
246
246
  }).isRequired,
247
247
  };
248
248
  export async function getContentfulAssets(props) {
@@ -263,10 +263,10 @@ getContentfulAssetURLs.propTypes = {
263
263
  /** Contentful API configuration */
264
264
  apiProps: PropTypes.shape({
265
265
  proxyURL: PropTypes.string,
266
- base_url: PropTypes.string.isRequired,
267
- space_id: PropTypes.string.isRequired,
268
- environment: PropTypes.string.isRequired,
269
- access_token: PropTypes.string.isRequired,
266
+ base_url: PropTypes.string,
267
+ space_id: PropTypes.string,
268
+ environment: PropTypes.string,
269
+ access_token: PropTypes.string,
270
270
  }).isRequired,
271
271
  };
272
272
  export async function getContentfulAssetURLs(props) {
@@ -13,15 +13,6 @@ import { SmartImage } from "../general/smartimage";
13
13
  import "../../css/pixelated.grid.scss";
14
14
  import "./contentful.items.css";
15
15
  const debug = false;
16
- let ContentfulApiProps = {
17
- proxyURL: 'https://proxy.pixelated.tech/prod/proxy?url=',
18
- base_url: "https://cdn.contentful.com",
19
- space_id: "soi9w77t7027",
20
- environment: "master",
21
- management_access_token: "",
22
- delivery_access_token: "muY9LfpCt4qoXosDsnRkkoH3DAVVuUFEuB0WRKRdBUM",
23
- preview_access_token: "",
24
- };
25
16
  const contentfulContentType = "item";
26
17
  /* ========== CONTENTFUL ITEMS PAGE ========== */
27
18
  /**
@@ -50,7 +41,7 @@ export function ContentfulItems(props) {
50
41
  if ((providerContentful as any).proxyURL) localContentfulApiProps.proxyURL = (providerContentful as any).proxyURL;
51
42
  } */
52
43
  const providerContentfulApiProps = usePixelatedConfig()?.contentful;
53
- const mergedApiProps = { ...ContentfulApiProps, ...providerContentfulApiProps, ...props.apiProps, };
44
+ const mergedApiProps = { ...providerContentfulApiProps, ...props.apiProps };
54
45
  const [apiProps] = useState(mergedApiProps);
55
46
  /**
56
47
  * paintItems — Convert Contentful API items and assets into rendered list nodes.
@@ -204,7 +195,7 @@ export function ContentfulItemDetail(props) {
204
195
  const [assets, setAssets] = useState({});
205
196
  const [cards, setCards] = useState([]);
206
197
  const providerContentfulApiProps = usePixelatedConfig()?.contentful;
207
- const [apiProps] = useState({ ...ContentfulApiProps, ...providerContentfulApiProps, ...props.apiProps });
198
+ const [apiProps] = useState({ ...providerContentfulApiProps, ...props.apiProps });
208
199
  useEffect(() => {
209
200
  if (debug)
210
201
  console.log("Running useEffect");
@@ -1,12 +1,15 @@
1
1
  import PropTypes from 'prop-types';
2
2
  import { mergeDeep } from '../general/utilities';
3
+ // Flickr API base URL - non-secret configuration
4
+ const FLICKR_API_BASE_URL = 'https://api.flickr.com/services/rest/?';
3
5
  const defaultFlickr = {
4
6
  flickr: {
5
- baseURL: 'https://api.flickr.com/services/rest/?',
7
+ baseURL: FLICKR_API_BASE_URL,
6
8
  urlProps: {
7
9
  method: 'flickr.photos.search',
8
- api_key: '882cab5548d53c9e6b5fb24d59cc321d',
9
- user_id: '15473210@N04',
10
+ // api_key and user_id must come from props or config provider - do not hardcode
11
+ api_key: '',
12
+ user_id: '',
10
13
  tags: 'pixelatedviewsgallery',
11
14
  extras: 'date_taken,description,owner_name',
12
15
  sort: 'date-taken-desc',
@@ -168,7 +171,7 @@ FlickrWrapper.propTypes = {
168
171
  export function FlickrWrapper(props) {
169
172
  const flickr = {
170
173
  flickr: {
171
- baseURL: 'https://api.flickr.com/services/rest/?',
174
+ baseURL: FLICKR_API_BASE_URL,
172
175
  urlProps: {
173
176
  method: props.method || 'flickr.photos.search',
174
177
  api_key: props.api_key /* || '882cab5548d53c9e6b5fb24d59cc321d' */,
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Google Places API Integration
3
+ * Handles autocomplete predictions and place details for address validation
4
+ */
5
+ /**
6
+ * GooglePlacesService — Thin service for Google Places API interactions using googleapis
7
+ */
8
+ export class GooglePlacesService {
9
+ apiKey = null;
10
+ sessionToken = null;
11
+ requestCache = new Map();
12
+ cacheTTL = 3600000; // 1 hour default
13
+ constructor(config) {
14
+ if (config) {
15
+ this.apiKey = config.apiKey || null;
16
+ this.cacheTTL = config.cacheTTL || 3600000;
17
+ }
18
+ this.sessionToken = this.generateSessionToken();
19
+ }
20
+ /**
21
+ * Generate or return cached session token for Places requests
22
+ */
23
+ generateSessionToken() {
24
+ return `session_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
25
+ }
26
+ /**
27
+ * Get autocomplete predictions for a search input
28
+ * Uses Google Places Autocomplete API (web service version via googleapis)
29
+ */
30
+ async getPlacePredictions(input, config) {
31
+ if (!input || input.length < 2)
32
+ return [];
33
+ const cacheKey = `predictions_${input}`;
34
+ const cached = this.requestCache.get(cacheKey);
35
+ if (cached)
36
+ return cached;
37
+ try {
38
+ const apiKey = config?.googlePlaces?.apiKey || this.apiKey;
39
+ if (!apiKey) {
40
+ console.error('Google Places API key not configured');
41
+ return [];
42
+ }
43
+ const restrictions = config?.googlePlaces?.countryRestrictions || ['us'];
44
+ const componentFilter = restrictions.length > 0 ? `components=country:${restrictions.join('|country:')}` : '';
45
+ const apiUrl = `https://maps.googleapis.com/maps/api/place/autocomplete/json?input=${encodeURIComponent(input)}&key=${apiKey}&sessiontoken=${this.sessionToken}&${componentFilter}`;
46
+ // Use global proxy to avoid CORS issues
47
+ const proxyURL = config?.global?.proxyUrl || '';
48
+ const url = proxyURL ? `${proxyURL}${encodeURIComponent(apiUrl)}` : apiUrl;
49
+ const response = await fetch(url);
50
+ const data = await response.json();
51
+ if (!data.predictions) {
52
+ return [];
53
+ }
54
+ const predictions = data.predictions.map((pred) => ({
55
+ placeId: pred.place_id,
56
+ mainText: pred.structured_formatting?.main_text || pred.description,
57
+ secondaryText: pred.structured_formatting?.secondary_text,
58
+ fullText: pred.description,
59
+ }));
60
+ // Cache for TTL
61
+ this.requestCache.set(cacheKey, predictions);
62
+ setTimeout(() => this.requestCache.delete(cacheKey), this.cacheTTL);
63
+ return predictions;
64
+ }
65
+ catch (error) {
66
+ console.error('Error fetching place predictions:', error);
67
+ return [];
68
+ }
69
+ }
70
+ /**
71
+ * Get detailed place information including address components
72
+ */
73
+ async getPlaceDetails(placeId, config) {
74
+ try {
75
+ const apiKey = config?.googlePlaces?.apiKey || this.apiKey;
76
+ if (!apiKey) {
77
+ console.error('Google Places API key not configured');
78
+ return null;
79
+ }
80
+ const apiUrl = `https://maps.googleapis.com/maps/api/place/details/json?place_id=${placeId}&key=${apiKey}&fields=address_component,formatted_address&sessiontoken=${this.sessionToken}`;
81
+ // Use global proxy to avoid CORS issues
82
+ const proxyURL = config?.global?.proxyUrl || '';
83
+ const url = proxyURL ? `${proxyURL}${encodeURIComponent(apiUrl)}` : apiUrl;
84
+ const response = await fetch(url);
85
+ const data = await response.json();
86
+ if (!data.result)
87
+ return null;
88
+ const result = data.result;
89
+ const addressComponents = result.address_components || [];
90
+ // Parse address components
91
+ const parsed = {
92
+ formattedAddress: result.formatted_address || '',
93
+ addressComponents: addressComponents,
94
+ };
95
+ // Extract standard address fields
96
+ for (const component of addressComponents) {
97
+ const types = component.types || [];
98
+ if (types.includes('street_number') || types.includes('route')) {
99
+ parsed.street1 = (parsed.street1 || '') + (component.long_name || '') + ' ';
100
+ }
101
+ else if (types.includes('locality')) {
102
+ parsed.city = component.long_name;
103
+ }
104
+ else if (types.includes('administrative_area_level_1')) {
105
+ parsed.state = component.short_name;
106
+ }
107
+ else if (types.includes('postal_code')) {
108
+ parsed.zip = component.long_name;
109
+ }
110
+ else if (types.includes('country')) {
111
+ parsed.country = component.short_name;
112
+ }
113
+ }
114
+ if (parsed.street1) {
115
+ parsed.street1 = parsed.street1.trim();
116
+ }
117
+ return parsed;
118
+ }
119
+ catch (error) {
120
+ console.error('Error fetching place details:', error);
121
+ return null;
122
+ }
123
+ }
124
+ /**
125
+ * Validate that address is in allowed country
126
+ */
127
+ isValidCountry(placeDetails, allowedCountries = ['US']) {
128
+ if (!placeDetails.country)
129
+ return false;
130
+ return allowedCountries.includes(placeDetails.country.toUpperCase());
131
+ }
132
+ /**
133
+ * Clear cached predictions
134
+ */
135
+ clearCache() {
136
+ this.requestCache.clear();
137
+ }
138
+ }
139
+ /**
140
+ * Factory function to get configured GooglePlacesService instance
141
+ */
142
+ export function getGooglePlacesService(config) {
143
+ return new GooglePlacesService(config);
144
+ }
@@ -42,19 +42,22 @@ SocialCards.propTypes = {
42
42
  };
43
43
  export function SocialCards(props) {
44
44
  const debug = false;
45
+ // Get config values from provider
46
+ const config = usePixelatedConfig();
47
+ const proxyURL = config?.global?.proxyUrl || 'https://proxy.pixelated.tech/prod/proxy';
45
48
  const [state, setState] = useState({
46
49
  loading: true,
47
50
  targetID: '#social',
48
51
  myCardData: [],
49
52
  mySocialCards: [],
50
53
  proxy: {
51
- proxyURL: 'https://proxy.pixelated.tech/prod/proxy',
54
+ proxyURL,
52
55
  proxyURLParam: 'url'
53
56
  },
54
57
  rss2json: {
55
58
  apiURL: 'https://api.rss2json.com/v1/api.json',
56
59
  apiURLParam: 'rss_url',
57
- apiKey: 'c3wsmqh4h1iydxxip3sgkr1jtk3brllbp61jc6yd'
60
+ apiKey: ''
58
61
  },
59
62
  toptal: {
60
63
  apiURL: 'https://www.toptal.com/developers/feed2json/convert',
@@ -8,6 +8,7 @@ import { PageGridItem } from '../general/semantic';
8
8
  import { getWordPressItems, getWordPressLastModified } from './wordpress.functions';
9
9
  import { Loading, ToggleLoading } from '../general/loading';
10
10
  import { CacheManager } from "../general/cache-manager";
11
+ import { getDomain } from '../general/utilities';
11
12
  import "./wordpress.css";
12
13
  import { SchemaBlogPosting } from '../general/schema';
13
14
  import { mapWordPressToBlogPosting } from '../general/schema.functions';
@@ -18,7 +19,7 @@ function decodeString(str) {
18
19
  return textarea.value;
19
20
  }
20
21
  const wpCacheTTL = 1000 * 60 * 60 * 24 * 7; // 1 week
21
- const wpCache = new CacheManager({ mode: 'local', ttl: wpCacheTTL, prefix: 'wp_' });
22
+ const wpCache = new CacheManager({ mode: 'local', ttl: wpCacheTTL, domain: getDomain(), namespace: 'wp' });
22
23
  const wpApiURL = "https://public-api.wordpress.com/rest/v1/sites/";
23
24
  /**
24
25
  * getCachedWordPressItems — Fetch posts from the WordPress REST API with caching. Checks local cache first and returns cached posts if available and not expired; otherwise fetches from the API, stores in cache, and returns the fresh data.
@@ -178,8 +178,8 @@ export function EbayListItem(props) {
178
178
  const itemURL = "./store/" + thisItem.legacyItemId;
179
179
  const itemURLTarget = "_self"; /* "_blank" */
180
180
  const itemImage = (props.cloudinaryProductEnv)
181
- ? getImg({ url: thisItem.thumbnailImages[0].imageUrl, product_env: props.cloudinaryProductEnv })
182
- : thisItem.thumbnailImages[0].imageUrl;
181
+ ? getImg({ url: thisItem.thumbnailImages?.[0]?.imageUrl || thisItem.image?.imageUrl || '', product_env: props.cloudinaryProductEnv })
182
+ : thisItem.thumbnailImages?.[0]?.imageUrl || thisItem.image?.imageUrl || '';
183
183
  const shoppingCartItem = getShoppingCartItem({ thisItem: thisItem, cloudinaryProductEnv: props.cloudinaryProductEnv, apiProps: apiProps });
184
184
  // CHANGE EBAY URL TO LOCAL EBAY ITEM DETAIL URL
185
185
  shoppingCartItem.itemURL = itemURL;
@@ -189,7 +189,7 @@ export function EbayListItem(props) {
189
189
  ? _jsx("a", { href: itemURL, target: itemURLTarget, rel: "noopener noreferrer", children: itemImageComponent })
190
190
  : (itemImageComponent) }), _jsxs("div", { className: "ebay-item-body grid-s5-e13", children: [_jsx("div", { className: "ebay-item-header", children: itemURL
191
191
  ? _jsx(EbayItemHeader, { url: itemURL, target: itemURLTarget, title: thisItem.title })
192
- : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsxs("div", { className: "ebay-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categories[0].categoryId == apiProps.itemCategory ? 1 : 10] }), _jsxs("div", { children: [_jsx("b", { children: "Condition: " }), thisItem.condition] }), _jsxs("div", { children: [_jsx("b", { children: "Seller: " }), thisItem.seller.username, " (", thisItem.seller.feedbackScore, ")", _jsx("br", {}), thisItem.seller.feedbackPercentage, "% positive"] }), _jsxs("div", { children: [_jsx("b", { children: "Buying Options: " }), thisItem.buyingOptions[0]] }), _jsxs("div", { children: [_jsx("b", { children: "Location: " }), thisItem.itemLocation.postalCode + ", " + thisItem.itemLocation.country] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.itemCreationDate] })] }), _jsx("div", { className: "ebay-item-price", children: itemURL
192
+ : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsxs("div", { className: "ebay-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categories?.[0]?.categoryId == apiProps.itemCategory ? 1 : 10] }), _jsxs("div", { children: [_jsx("b", { children: "Condition: " }), thisItem.condition] }), _jsxs("div", { children: [_jsx("b", { children: "Seller: " }), thisItem.seller?.username, " (", thisItem.seller?.feedbackScore, ")", _jsx("br", {}), thisItem.seller?.feedbackPercentage, "% positive"] }), _jsxs("div", { children: [_jsx("b", { children: "Buying Options: " }), thisItem.buyingOptions?.[0]] }), _jsxs("div", { children: [_jsx("b", { children: "Location: " }), thisItem.itemLocation?.postalCode + ", " + thisItem.itemLocation?.country] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.itemCreationDate] })] }), _jsx("div", { className: "ebay-item-price", children: itemURL
193
193
  ? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.price.value + " " + thisItem.price.currency] })
194
194
  : "$" + thisItem.price.value + " " + thisItem.price.currency }), _jsx("br", {}), _jsxs("div", { className: "ebay-item-add-to-cart", children: [_jsx(ViewItemDetails, { href: "/store", itemID: thisItem.legacyItemId }), _jsx(AddToCartButton, { handler: addToShoppingCart, item: shoppingCartItem, itemID: thisItem.legacyItemId })] })] })] }));
195
195
  }
@@ -253,7 +253,7 @@ export function EbayItemDetail(props) {
253
253
  const thisItem = { ...item };
254
254
  if (debug)
255
255
  console.log(thisItem);
256
- const images = thisItem.additionalImages.map((thisImage) => ({ image: (props.cloudinaryProductEnv)
256
+ const images = (thisItem.additionalImages || []).map((thisImage) => ({ image: (props.cloudinaryProductEnv)
257
257
  ? getImg({ url: thisImage.imageUrl, product_env: props.cloudinaryProductEnv })
258
258
  : thisImage.imageUrl }));
259
259
  const itemURL = undefined;
@@ -262,7 +262,7 @@ export function EbayItemDetail(props) {
262
262
  shoppingCartItem.itemURL = itemURL;
263
263
  return (_jsx(_Fragment, { children: _jsxs("div", { className: "ebay-item row-12col", children: [_jsx("div", { className: "ebay-item-header grid-s1-e13", children: itemURL
264
264
  ? _jsx(EbayItemHeader, { url: itemURL, title: thisItem.title })
265
- : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsx("br", {}), _jsx("div", { className: "ebay-item-photo-carousel grid-s1-e7", children: _jsx(Carousel, { cards: images, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "ebay-item-details grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.description.replace(/(<br\s*\/?>\s*){2,}/gi, '') } }) }), _jsx("br", {}), _jsxs("div", { className: "ebay-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categoryId == apiProps.itemCategory ? 1 : 10] }), _jsxs("div", { children: [_jsx("b", { children: "Category: " }), thisItem.categoryPath] }), _jsxs("div", { children: [_jsx("b", { children: "Condition: " }), thisItem.condition] }), _jsxs("div", { children: [_jsx("b", { children: "Seller: " }), thisItem.seller.username, " (", thisItem.seller.feedbackScore, ")", _jsx("br", {}), thisItem.seller.feedbackPercentage, "% positive"] }), _jsxs("div", { children: [_jsx("b", { children: "Buying Options: " }), thisItem.buyingOptions[0]] }), _jsxs("div", { children: [_jsx("b", { children: "Location: " }), thisItem.itemLocation.city + ", " + thisItem.itemLocation.stateOrProvince] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.itemCreationDate] }), _jsx("br", {})] }), _jsx("div", { className: "ebay-item-price", children: itemURL
265
+ : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsx("br", {}), _jsx("div", { className: "ebay-item-photo-carousel grid-s1-e7", children: _jsx(Carousel, { cards: images, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "ebay-item-details grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.description?.replace(/(<br\s*\/?>\s*){2,}/gi, '') || '' } }) }), _jsx("br", {}), _jsxs("div", { className: "ebay-item-details grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categoryId == apiProps.itemCategory ? 1 : 10] }), _jsxs("div", { children: [_jsx("b", { children: "Category: " }), thisItem.categoryPath] }), _jsxs("div", { children: [_jsx("b", { children: "Condition: " }), thisItem.condition] }), _jsxs("div", { children: [_jsx("b", { children: "Seller: " }), thisItem.seller?.username, " (", thisItem.seller?.feedbackScore, ")", _jsx("br", {}), thisItem.seller?.feedbackPercentage, "% positive"] }), _jsxs("div", { children: [_jsx("b", { children: "Buying Options: " }), thisItem.buyingOptions?.[0]] }), _jsxs("div", { children: [_jsx("b", { children: "Location: " }), thisItem.itemLocation?.city + ", " + thisItem.itemLocation?.stateOrProvince] }), _jsxs("div", { children: [_jsx("b", { children: "Listing Date: " }), thisItem.itemCreationDate] }), _jsx("br", {})] }), _jsx("div", { className: "ebay-item-price", children: itemURL
266
266
  ? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.price.value + " " + thisItem.price.currency] })
267
267
  : "$" + thisItem.price.value + " " + thisItem.price.currency }), _jsx("br", {}), _jsx("div", { className: "ebay-item-add-to-cart", children: _jsx(AddToCartButton, { handler: addToShoppingCart, item: shoppingCartItem, itemID: thisItem.legacyItemId }) })] })] }) }));
268
268
  }