@pixelated-tech/components 3.8.0 → 3.8.1

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 (112) hide show
  1. package/README.md +7 -0
  2. package/dist/components/admin/site-health/site-health-utils.js +22 -0
  3. package/dist/components/config/config.example.js +2 -0
  4. package/dist/components/config/config.types.js +1 -3
  5. package/dist/components/config/config.utils.js +13 -5
  6. package/dist/components/general/cache-manager.js +124 -0
  7. package/dist/components/general/googlemap.js +5 -2
  8. package/dist/components/general/sitemap.js +3 -1
  9. package/dist/components/shoppingcart/ebay.components.js +9 -7
  10. package/dist/components/shoppingcart/ebay.functions.js +94 -9
  11. package/dist/components/shoppingcart/shoppingcart.components.js +4 -2
  12. package/dist/config/pixelated.config.json +3 -1
  13. package/dist/index.js +1 -0
  14. package/dist/scripts/release.sh +2 -2
  15. package/dist/test/config.mock.js +13 -0
  16. package/dist/test/setup.js +46 -0
  17. package/dist/test/test-utils.js +23 -0
  18. package/dist/types/components/admin/site-health/site-health-utils.d.ts +1 -1
  19. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -1
  20. package/dist/types/components/config/config.example.d.ts.map +1 -1
  21. package/dist/types/components/config/config.types.d.ts +2 -0
  22. package/dist/types/components/config/config.types.d.ts.map +1 -1
  23. package/dist/types/components/config/config.utils.d.ts.map +1 -1
  24. package/dist/types/components/general/cache-manager.d.ts +45 -0
  25. package/dist/types/components/general/cache-manager.d.ts.map +1 -0
  26. package/dist/types/components/general/googlemap.d.ts +1 -1
  27. package/dist/types/components/general/googlemap.d.ts.map +1 -1
  28. package/dist/types/components/general/sitemap.d.ts.map +1 -1
  29. package/dist/types/components/shoppingcart/ebay.components.d.ts.map +1 -1
  30. package/dist/types/components/shoppingcart/ebay.functions.d.ts +15 -0
  31. package/dist/types/components/shoppingcart/ebay.functions.d.ts.map +1 -1
  32. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
  33. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
  34. package/dist/types/index.d.ts +1 -0
  35. package/dist/types/stories/callout/callout.many.stories.d.ts +0 -1
  36. package/dist/types/stories/callout/callout.many.stories.d.ts.map +1 -1
  37. package/dist/types/stories/callout/callout.stories.d.ts +0 -1
  38. package/dist/types/stories/callout/callout.stories.d.ts.map +1 -1
  39. package/dist/types/stories/carousel/carousel-hero.stories.d.ts +1 -9
  40. package/dist/types/stories/carousel/carousel-hero.stories.d.ts.map +1 -1
  41. package/dist/types/stories/carousel/carousel-reviews.stories.d.ts +0 -1
  42. package/dist/types/stories/carousel/carousel-reviews.stories.d.ts.map +1 -1
  43. package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts +1 -9
  44. package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts.map +1 -1
  45. package/dist/types/stories/carousel/carousel.stories.d.ts +1 -9
  46. package/dist/types/stories/carousel/carousel.stories.d.ts.map +1 -1
  47. package/dist/types/stories/carousel/tiles.stories.d.ts +0 -1
  48. package/dist/types/stories/carousel/tiles.stories.d.ts.map +1 -1
  49. package/dist/types/stories/cms/contentful.item.stories.d.ts +0 -9
  50. package/dist/types/stories/cms/contentful.item.stories.d.ts.map +1 -1
  51. package/dist/types/stories/cms/contentful.items.stories.d.ts +1 -11
  52. package/dist/types/stories/cms/contentful.items.stories.d.ts.map +1 -1
  53. package/dist/types/stories/cms/contentful.stories.d.ts +0 -1
  54. package/dist/types/stories/cms/contentful.stories.d.ts.map +1 -1
  55. package/dist/types/stories/cms/google.reviews.stories.d.ts +0 -1
  56. package/dist/types/stories/cms/google.reviews.stories.d.ts.map +1 -1
  57. package/dist/types/stories/cms/gravatar.stories.d.ts +0 -1
  58. package/dist/types/stories/cms/gravatar.stories.d.ts.map +1 -1
  59. package/dist/types/stories/cms/wordpress.stories.d.ts +6 -2
  60. package/dist/types/stories/cms/wordpress.stories.d.ts.map +1 -1
  61. package/dist/types/stories/general/accordion.stories.d.ts +3 -2
  62. package/dist/types/stories/general/accordion.stories.d.ts.map +1 -1
  63. package/dist/types/stories/general/headers.stories.d.ts.map +1 -1
  64. package/dist/types/stories/general/microinteractions.stories.d.ts +0 -1
  65. package/dist/types/stories/general/microinteractions.stories.d.ts.map +1 -1
  66. package/dist/types/stories/general/modal.stories.d.ts +0 -1
  67. package/dist/types/stories/general/modal.stories.d.ts.map +1 -1
  68. package/dist/types/stories/general/smartimage.stories.d.ts.map +1 -1
  69. package/dist/types/stories/general/splitscroll.stories.d.ts.map +1 -1
  70. package/dist/types/stories/seo/seo.404.stories.d.ts +0 -1
  71. package/dist/types/stories/seo/seo.404.stories.d.ts.map +1 -1
  72. package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts +1 -3
  73. package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +1 -1
  74. package/dist/types/stories/seo/seo.schema.stories.d.ts +0 -1
  75. package/dist/types/stories/seo/seo.schema.stories.d.ts.map +1 -1
  76. package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts +1 -12
  77. package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts.map +1 -1
  78. package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts +1 -12
  79. package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts.map +1 -1
  80. package/dist/types/stories/shoppingcart/shoppingcart.stories.d.ts.map +1 -1
  81. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts +12 -34
  82. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts.map +1 -1
  83. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts +0 -1
  84. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -1
  85. package/dist/types/stories/sitebuilder/pageengine.stories.d.ts +0 -1
  86. package/dist/types/stories/sitebuilder/pageengine.stories.d.ts.map +1 -1
  87. package/dist/types/stories/structured/markdown.stories.d.ts +0 -1
  88. package/dist/types/stories/structured/markdown.stories.d.ts.map +1 -1
  89. package/dist/types/stories/structured/recipe.stories.d.ts +0 -1
  90. package/dist/types/stories/structured/recipe.stories.d.ts.map +1 -1
  91. package/dist/types/stories/structured/resume.stories.d.ts +0 -1
  92. package/dist/types/stories/structured/resume.stories.d.ts.map +1 -1
  93. package/dist/types/stories/structured/socialcard.stories.d.ts +0 -1
  94. package/dist/types/stories/structured/socialcard.stories.d.ts.map +1 -1
  95. package/dist/types/stories/structured/timeline.stories.d.ts +0 -1
  96. package/dist/types/stories/structured/timeline.stories.d.ts.map +1 -1
  97. package/dist/types/test/config.mock.d.ts +11 -0
  98. package/dist/types/test/config.mock.d.ts.map +1 -0
  99. package/dist/types/test/setup.d.ts.map +1 -0
  100. package/dist/types/test/test-utils.d.ts +84 -0
  101. package/dist/types/test/test-utils.d.ts.map +1 -0
  102. package/dist/types/tests/cache-manager.test.d.ts +2 -0
  103. package/dist/types/tests/cache-manager.test.d.ts.map +1 -0
  104. package/dist/types/tests/config-core.test.d.ts +2 -0
  105. package/dist/types/tests/config-core.test.d.ts.map +1 -0
  106. package/dist/types/tests/ebay-functions.test.d.ts +2 -0
  107. package/dist/types/tests/ebay-functions.test.d.ts.map +1 -0
  108. package/dist/types/tests/site-health-utils.test.d.ts +2 -0
  109. package/dist/types/tests/site-health-utils.test.d.ts.map +1 -0
  110. package/package.json +4 -4
  111. package/dist/types/tests/setup.d.ts.map +0 -1
  112. /package/dist/types/{tests → test}/setup.d.ts +0 -0
package/README.md CHANGED
@@ -242,6 +242,13 @@ External service integrations:
242
242
  - **Yelp** - Business reviews and ratings
243
243
 
244
244
 
245
+ ### Utilities
246
+ Shared technical utilities and helpers:
247
+ - **CacheManager** - Unified caching layer with Memory, Session, and LocalStorage support with TTL and SSR fallbacks.
248
+ - **Cloudinary** - Image processing and URL generation helpers.
249
+ - **Date/Time** - Formatting and manipulation utilities.
250
+
251
+
245
252
  ### Site Health & Monitoring
246
253
  Comprehensive site health monitoring and analytics:
247
254
  - **SiteHealthOverview** - Dashboard overview of site health metrics
@@ -25,6 +25,28 @@ export function formatScore(score) {
25
25
  * Formats audit item details for display
26
26
  */
27
27
  export function formatAuditItem(item, auditTitle) {
28
+ // Handle raw timing data that might be passed directly
29
+ if (typeof item === 'number') {
30
+ let context = '';
31
+ if (auditTitle) {
32
+ if (auditTitle.toLowerCase().includes('server') || auditTitle.toLowerCase().includes('backend')) {
33
+ context = ' server response';
34
+ }
35
+ else if (auditTitle.toLowerCase().includes('network') || auditTitle.toLowerCase().includes('request')) {
36
+ context = ' network request';
37
+ }
38
+ else if (auditTitle.toLowerCase().includes('render') || auditTitle.toLowerCase().includes('blocking')) {
39
+ context = ' render blocking';
40
+ }
41
+ else if (auditTitle.toLowerCase().includes('javascript') || auditTitle.toLowerCase().includes('js')) {
42
+ context = ' JavaScript';
43
+ }
44
+ else if (auditTitle.toLowerCase().includes('image') || auditTitle.toLowerCase().includes('media')) {
45
+ context = ' media resource';
46
+ }
47
+ }
48
+ return `${item.toFixed(2)}ms${context}`;
49
+ }
28
50
  // Handle URLs
29
51
  if (item.url && typeof item.url === 'string') {
30
52
  return item.url;
@@ -21,8 +21,10 @@ const pixelatedConfig = {
21
21
  ebay: {
22
22
  proxyURL: 'https://proxy.provier.com/proxy?url=',
23
23
  appId: 'your-ebay-client-id',
24
+ appDevId: 'your-ebay-client-dev-id',
24
25
  appCertId: 'your-ebay-client-secret',
25
26
  sbxAppId: 'your-ebay-sandbox-client-id',
27
+ sbxAppDevId: 'your-ebay-sandbox-client-dev-id',
26
28
  sbxAppCertId: 'your-ebay-sandbox-client-secret',
27
29
  globalId: 'EBAY_US',
28
30
  environment: 'production',
@@ -19,9 +19,7 @@ export const SECRET_CONFIG_KEYS = {
19
19
  'preview_access_token'
20
20
  ],
21
21
  ebay: [
22
- 'appCertId',
23
- 'sbxAppId',
24
- 'sbxAppCertId'
22
+ 'sbxAppId'
25
23
  ],
26
24
  paypal: [
27
25
  'sandboxPayPalApiKey',
@@ -11,25 +11,33 @@ export function getClientOnlyPixelatedConfig(src) {
11
11
  // 2. Check Service-Specific Secret List
12
12
  if (serviceName && SECRET_CONFIG_KEYS.services[serviceName]) {
13
13
  const serviceSecrets = SECRET_CONFIG_KEYS.services[serviceName];
14
- if (serviceSecrets.includes(key))
14
+ if (serviceSecrets.includes(key)) {
15
+ // console.log(`Config Stripper: Removing secret key "${key}" from service "${serviceName}"`);
15
16
  return true;
17
+ }
16
18
  }
17
19
  return false;
18
20
  }
19
21
  function strip(obj, serviceName) {
20
- if (!obj || typeof obj !== 'object' || obj === null)
22
+ // Base case for non-objects
23
+ if (obj === null || typeof obj !== 'object')
21
24
  return obj;
25
+ // Avoid circular references
22
26
  if (visited.has(obj))
23
27
  return '[Circular]';
24
28
  visited.add(obj);
25
- if (Array.isArray(obj))
29
+ // Handle Arrays
30
+ if (Array.isArray(obj)) {
26
31
  return obj.map((item) => strip(item, serviceName));
32
+ }
27
33
  const out = {};
28
34
  for (const k of Object.keys(obj)) {
29
- // If we are at the top level, the key 'k' IS the service name (ebay, cloudinary, etc.)
35
+ // At the top level (serviceName is undefined), k is the service name
30
36
  const currentService = serviceName || k;
31
- if (isSecretKey(k, serviceName))
37
+ // Check if this key should be stripped
38
+ if (isSecretKey(k, serviceName)) {
32
39
  continue;
40
+ }
33
41
  out[k] = strip(obj[k], currentService);
34
42
  }
35
43
  return out;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * CacheManager
3
+ *
4
+ * A unified caching utility that supports Memory, LocalStorage, and SessionStorage.
5
+ * Includes TTL (Time-To-Live) support and automatic SSR fallback.
6
+ */
7
+ export class CacheManager {
8
+ memoryCache = new Map();
9
+ defaultTTL = 60 * 60 * 1000; // 1 hour
10
+ mode;
11
+ prefix;
12
+ ttl;
13
+ constructor(options = {}) {
14
+ this.mode = options.mode || 'memory';
15
+ this.prefix = options.prefix || 'pix_';
16
+ this.ttl = options.ttl || this.defaultTTL;
17
+ // Fallback to memory if browser storage is requested but unavailable (SSR/Node environment)
18
+ if (typeof window === 'undefined' && (this.mode === 'local' || this.mode === 'session')) {
19
+ this.mode = 'memory';
20
+ }
21
+ }
22
+ /**
23
+ * Returns the storage engine based on the mode
24
+ */
25
+ getStorage() {
26
+ if (typeof window === 'undefined')
27
+ return null;
28
+ if (this.mode === 'local')
29
+ return window.localStorage;
30
+ if (this.mode === 'session')
31
+ return window.sessionStorage;
32
+ return null;
33
+ }
34
+ /**
35
+ * Generates a prefixed key to avoid collisions
36
+ */
37
+ getFullKey(key) {
38
+ return `${this.prefix}${key}`;
39
+ }
40
+ /**
41
+ * Retrieves data from the cache
42
+ */
43
+ get(key) {
44
+ if (this.mode === 'none')
45
+ return null;
46
+ const fullKey = this.getFullKey(key);
47
+ let wrapper = null;
48
+ // 1. Check Memory cache first (fastest)
49
+ const memMatch = this.memoryCache.get(fullKey);
50
+ if (memMatch) {
51
+ wrapper = memMatch;
52
+ }
53
+ // 2. Check Browser storage if memory failed or was bypassed
54
+ if (!wrapper) {
55
+ const storage = this.getStorage();
56
+ if (storage) {
57
+ const raw = storage.getItem(fullKey);
58
+ if (raw) {
59
+ try {
60
+ wrapper = JSON.parse(raw);
61
+ }
62
+ catch (e) {
63
+ return null;
64
+ }
65
+ }
66
+ }
67
+ }
68
+ // 3. Validate Expiry
69
+ if (wrapper) {
70
+ if (Date.now() < wrapper.expiry) {
71
+ // Resync memory cache if we pulled from storage
72
+ if (!memMatch) {
73
+ this.memoryCache.set(fullKey, wrapper);
74
+ }
75
+ return wrapper.data;
76
+ }
77
+ // Clean up expired items
78
+ this.remove(key);
79
+ }
80
+ return null;
81
+ }
82
+ /**
83
+ * Stores data in the cache with a specified TTL
84
+ */
85
+ set(key, data, customTTL) {
86
+ if (this.mode === 'none')
87
+ return;
88
+ const fullKey = this.getFullKey(key);
89
+ const expiry = Date.now() + (customTTL || this.ttl);
90
+ const wrapper = { data, expiry };
91
+ // Always update memory
92
+ this.memoryCache.set(fullKey, wrapper);
93
+ // Update browser storage if applicable
94
+ const storage = this.getStorage();
95
+ if (storage) {
96
+ storage.setItem(fullKey, JSON.stringify(wrapper));
97
+ }
98
+ }
99
+ /**
100
+ * Removes a specific item from all storage engines
101
+ */
102
+ remove(key) {
103
+ const fullKey = this.getFullKey(key);
104
+ this.memoryCache.delete(fullKey);
105
+ const storage = this.getStorage();
106
+ if (storage) {
107
+ storage.removeItem(fullKey);
108
+ }
109
+ }
110
+ /**
111
+ * Clears only the items belonging to this manager (by prefix)
112
+ */
113
+ clear() {
114
+ this.memoryCache.clear();
115
+ const storage = this.getStorage();
116
+ if (storage) {
117
+ Object.keys(storage).forEach(k => {
118
+ if (k.startsWith(this.prefix)) {
119
+ storage.removeItem(k);
120
+ }
121
+ });
122
+ }
123
+ }
124
+ }
@@ -1,5 +1,6 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import PropTypes from "prop-types";
3
+ import { usePixelatedConfig } from "../config/config.client";
3
4
  // https://developers.google.com/maps/documentation/embed/embedding-map
4
5
  GoogleMaps.propTypes = {
5
6
  title: PropTypes.string,
@@ -8,9 +9,11 @@ GoogleMaps.propTypes = {
8
9
  frameBorder: PropTypes.string,
9
10
  style: PropTypes.object,
10
11
  map_mode: PropTypes.string.isRequired,
11
- api_key: PropTypes.string.isRequired,
12
+ api_key: PropTypes.string,
12
13
  parameters: PropTypes.string,
13
14
  };
14
15
  export function GoogleMaps(props) {
15
- return (_jsx("div", { className: "gmap", suppressHydrationWarning: true, children: _jsx("iframe", { title: props.title || "Google Map", width: props.width || "600", height: props.height || "400", frameBorder: props.frameBorder || "0", style: props.style || { border: 0 }, referrerPolicy: "no-referrer-when-downgrade", src: `https://www.google.com/maps/embed/v1/${props.map_mode}?key=${props.api_key}&${props.parameters}`, allowFullScreen: true }) }));
16
+ const config = usePixelatedConfig();
17
+ const apiKey = props.api_key || config?.googleMaps?.apiKey;
18
+ return (_jsx("div", { className: "gmap", suppressHydrationWarning: true, children: _jsx("iframe", { title: props.title || "Google Map", width: props.width || "600", height: props.height || "400", frameBorder: props.frameBorder || "0", style: props.style || { border: 0 }, referrerPolicy: "no-referrer-when-downgrade", src: `https://www.google.com/maps/embed/v1/${props.map_mode}?key=${apiKey}&${props.parameters}`, allowFullScreen: true }) }));
16
19
  }
@@ -305,9 +305,11 @@ const defaultEbayProps = {
305
305
  baseItemURL: 'https://api.ebay.com/buy/browse/v1/item',
306
306
  qsItemURL: '/v1|295959752403|0?fieldgroups=PRODUCT,ADDITIONAL_SELLER_DETAILS',
307
307
  appId: 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe', // clientId
308
+ appDevId: 'fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd',
308
309
  appCertId: 'PRD-fb4458deef01-0d54-496a-b572-a04b', // clientSecret
309
310
  sbxAppId: 'BrianWha-Pixelate-SBX-ad482b6ae-8cb8fead', // Sandbox
310
- sbxAppCertId: '',
311
+ sbxAppDevId: 'fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd',
312
+ sbxAppCertId: 'SBX-d482b6ae0d62-f57c-4d3c-88b7-22ec',
311
313
  globalId: 'EBAY-US',
312
314
  };
313
315
  export async function createEbayItemURLs(origin) {
@@ -8,7 +8,7 @@ import { defaultEbayProps, ebaySunglassCategory, getEbayItems, getEbayItem, getS
8
8
  import { addToShoppingCart } from "./shoppingcart.functions";
9
9
  import { AddToCartButton, /* GoToCartButton */ ViewItemDetails } from "./shoppingcart.components";
10
10
  import { getCloudinaryRemoteFetchURL as getImg } from "../general/cloudinary";
11
- import { Loading, ToggleLoading } from "../general/loading";
11
+ import { ToggleLoading } from "../general/loading";
12
12
  import { usePixelatedConfig } from "../config/config.client";
13
13
  import "../../css/pixelated.grid.scss";
14
14
  import "./ebay.css";
@@ -44,6 +44,8 @@ export function EbayItems(props) {
44
44
  };
45
45
  async function fetchItems(props) {
46
46
  try {
47
+ if (debug)
48
+ console.log("Fetching ebay API Items Data");
47
49
  const myApiProps = { ...apiProps };
48
50
  if (props) {
49
51
  const params = new URLSearchParams(myApiProps.qsSearchURL);
@@ -55,9 +57,9 @@ export function EbayItems(props) {
55
57
  }
56
58
  const response = await getEbayItems({ apiProps: myApiProps });
57
59
  if (debug)
58
- console.log("eBay API Get Items Data", response);
59
- setItems(response?.itemSummaries || []);
60
- setAspects(response?.refinement?.aspectDistributions || []);
60
+ console.log("eBay API Search Items Data:", response);
61
+ setItems(response?.itemSummaries);
62
+ setAspects(response?.refinement?.aspectDistributions);
61
63
  }
62
64
  catch (error) {
63
65
  console.error("Error fetching eBay items:", error);
@@ -70,11 +72,11 @@ export function EbayItems(props) {
70
72
  fetchItems();
71
73
  ToggleLoading(false);
72
74
  }, []);
73
- if (items.length > 0) {
74
- return (_jsxs(_Fragment, { children: [_jsx(Loading, {}), _jsx("div", { className: "ebayItemsHeader", children: _jsx(EbayItemHeader, { title: `${items.length} Store Items` }) }), _jsx("div", { className: "ebayItemsHeader", children: _jsx(EbayListFilter, { aspects: aspects, callback: fetchItems }) }), _jsx("div", { id: "ebayItems", className: "ebayItems", children: paintItems({ items: items, cloudinaryProductEnv: props.cloudinaryProductEnv }) })] }));
75
+ if (items && items.length > 0) {
76
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "ebayItemsHeader", children: _jsx(EbayItemHeader, { title: `${items.length} Store Items` }) }), _jsx("div", { className: "ebayItemsHeader", children: _jsx(EbayListFilter, { aspects: aspects, callback: fetchItems }) }), _jsx("div", { id: "ebayItems", className: "ebayItems", children: paintItems({ items: items, cloudinaryProductEnv: props.cloudinaryProductEnv }) })] }));
75
77
  }
76
78
  else {
77
- return (_jsx("div", { className: "section-container", children: _jsx("div", { id: "ebayItems", className: "ebayItems", children: _jsx(Loading, {}) }) }));
79
+ return (_jsx("div", { className: "section-container", children: _jsx("div", { id: "ebayItems", className: "ebayItems" }) }));
78
80
  }
79
81
  }
80
82
  EbayListFilter.propTypes = {
@@ -1,6 +1,13 @@
1
1
  import PropTypes from "prop-types";
2
2
  import { getCloudinaryRemoteFetchURL as getImg } from "../general/cloudinary";
3
+ import { CacheManager } from "../general/cache-manager";
3
4
  const debug = false;
5
+ // Initialize eBay Cache (Session storage, 1 hour TTL)
6
+ const ebayCache = new CacheManager({
7
+ mode: 'session',
8
+ prefix: 'ebay_',
9
+ ttl: 60 * 60 * 1000
10
+ });
4
11
  /* ===== EBAY BROWSE API DOCUMENTATION =====
5
12
  https://developer.ebay.com/api-docs/buy/browse/resources/item_summary/methods/search
6
13
  https://developer.ebay.com/api-docs/buy/static/ref-buy-browse-filters.html
@@ -57,10 +64,13 @@ export const defaultEbayProps = {
57
64
  qsSearchURL: '?q=sunglasses&fieldgroups=full&category_ids=79720&aspect_filter=categoryId:79720&filter=sellers:{pixelatedtech}&sort=newlyListed&limit=200',
58
65
  baseItemURL: 'https://api.ebay.com/buy/browse/v1/item',
59
66
  qsItemURL: '/v1|295959752403|0?fieldgroups=PRODUCT,ADDITIONAL_SELLER_DETAILS',
67
+ baseAnalyticsURL: 'https://api.ebay.com/developer/analytics/v1_beta',
60
68
  appId: 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe', // clientId
69
+ appDevId: 'fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd',
61
70
  appCertId: 'PRD-fb4458deef01-0d54-496a-b572-a04b', // clientSecret
62
71
  sbxAppId: 'BrianWha-Pixelate-SBX-ad482b6ae-8cb8fead', // Sandbox
63
- sbxAppCertId: '',
72
+ sbxAppDevId: 'fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd',
73
+ sbxAppCertId: 'SBX-d482b6ae0d62-f57c-4d3c-88b7-22ec',
64
74
  globalId: 'EBAY-US',
65
75
  };
66
76
  /* ========== GET TOKEN ========== */
@@ -107,10 +117,19 @@ getEbayBrowseSearch.propTypes = {
107
117
  export function getEbayBrowseSearch(props) {
108
118
  const apiProps = { ...defaultEbayProps, ...props.apiProps };
109
119
  const fetchData = async (token) => {
120
+ const fullURL = apiProps.baseSearchURL + apiProps.qsSearchURL;
121
+ const cacheKey = `search_${fullURL}`;
122
+ // Check Cache
123
+ const cached = ebayCache.get(cacheKey);
124
+ if (cached) {
125
+ if (debug)
126
+ console.log("Returning cached eBay Search Data", cacheKey);
127
+ return cached;
128
+ }
110
129
  if (debug)
111
130
  console.log("Fetching ebay API Browse Search Data");
112
131
  try {
113
- const response = await fetch(apiProps.proxyURL + encodeURIComponent(apiProps.baseSearchURL + apiProps.qsSearchURL), {
132
+ const response = await fetch(apiProps.proxyURL + encodeURIComponent(fullURL), {
114
133
  method: 'GET',
115
134
  headers: {
116
135
  'Authorization': 'Bearer ' + token,
@@ -124,8 +143,10 @@ export function getEbayBrowseSearch(props) {
124
143
  }
125
144
  const data = await response.json();
126
145
  if (debug)
127
- console.log("Fetched eBay API Browse Search Data:", await data);
128
- return (await data);
146
+ console.log("Fetched eBay API Browse Search Data:", data);
147
+ // Store in Cache
148
+ ebayCache.set(cacheKey, data);
149
+ return data;
129
150
  }
130
151
  catch (error) {
131
152
  console.error('Error fetching data:', error);
@@ -141,10 +162,19 @@ getEbayBrowseItem.propTypes = {
141
162
  export function getEbayBrowseItem(props) {
142
163
  const apiProps = { ...defaultEbayProps, ...props.apiProps };
143
164
  const fetchData = async (token) => {
165
+ const fullURL = (apiProps.baseItemURL ?? '') + (apiProps.qsItemURL ?? '');
166
+ const cacheKey = `item_${fullURL}`;
167
+ // Check Cache
168
+ const cached = ebayCache.get(cacheKey);
169
+ if (cached) {
170
+ if (debug)
171
+ console.log("Returning cached eBay Item Data", cacheKey);
172
+ return cached;
173
+ }
144
174
  if (debug)
145
175
  console.log("Fetching ebay API Browse Item Data");
146
176
  try {
147
- const response = await fetch(apiProps.proxyURL + encodeURIComponent((apiProps.baseItemURL ?? '') + (apiProps.qsItemURL ?? '')), {
177
+ const response = await fetch(apiProps.proxyURL + encodeURIComponent(fullURL), {
148
178
  method: 'GET',
149
179
  headers: {
150
180
  'Authorization': 'Bearer ' + token,
@@ -158,8 +188,10 @@ export function getEbayBrowseItem(props) {
158
188
  }
159
189
  const data = await response.json();
160
190
  if (debug)
161
- console.log("Fetched eBay Item Data:", await data);
162
- return (await data);
191
+ console.log("Fetched eBay Item Data:", data);
192
+ // Store in Cache
193
+ ebayCache.set(cacheKey, data);
194
+ return data;
163
195
  }
164
196
  catch (error) {
165
197
  console.error('Error fetching data:', error);
@@ -167,6 +199,48 @@ export function getEbayBrowseItem(props) {
167
199
  };
168
200
  return fetchData(props.token);
169
201
  }
202
+ /* ========== RATE LIMITS ========== */
203
+ getEbayAllRateLimits.propTypes = {
204
+ apiProps: PropTypes.object.isRequired,
205
+ token: PropTypes.string.isRequired,
206
+ };
207
+ export function getEbayAllRateLimits(props) {
208
+ const apiProps = { ...defaultEbayProps, ...props.apiProps };
209
+ const fetchAllLimits = async (token) => {
210
+ if (debug)
211
+ console.log("Fetching all eBay API Rate Limits");
212
+ try {
213
+ const [rateLimitRes, userRateLimitRes] = await Promise.all([
214
+ fetch(apiProps.proxyURL + encodeURIComponent(apiProps.baseAnalyticsURL + '/rate_limit'), {
215
+ method: 'GET',
216
+ headers: { 'Authorization': 'Bearer ' + token }
217
+ }),
218
+ fetch(apiProps.proxyURL + encodeURIComponent(apiProps.baseAnalyticsURL + '/user_rate_limit'), {
219
+ method: 'GET',
220
+ headers: { 'Authorization': 'Bearer ' + token }
221
+ })
222
+ ]);
223
+ if (!rateLimitRes.ok || !userRateLimitRes.ok) {
224
+ throw new Error(`HTTP error! rate_limit: ${rateLimitRes.status}, user_rate_limit: ${userRateLimitRes.status}`);
225
+ }
226
+ const [rateLimit, userRateLimit] = await Promise.all([
227
+ rateLimitRes.json(),
228
+ userRateLimitRes.json()
229
+ ]);
230
+ const combinedData = {
231
+ rate_limit: rateLimit,
232
+ user_rate_limit: userRateLimit
233
+ };
234
+ if (debug)
235
+ console.log("Fetched Combined eBay Rate Limit Data:", combinedData);
236
+ return combinedData;
237
+ }
238
+ catch (error) {
239
+ console.error('Error fetching rate limits:', error);
240
+ }
241
+ };
242
+ return fetchAllLimits(props.token);
243
+ }
170
244
  /* ========== EXPORTED FUNCTIONS ========== */
171
245
  /* ========== GET EBAY ITEMS ========== */
172
246
  getEbayItems.propTypes = {
@@ -214,10 +288,19 @@ export async function getEbayItem(props) {
214
288
  export function getEbayItemsSearch(props) {
215
289
  const apiProps = { ...defaultEbayProps, ...props.apiProps };
216
290
  const fetchData = async (token) => {
291
+ const fullURL = apiProps.baseSearchURL + apiProps.qsSearchURL;
292
+ const cacheKey = `search_${fullURL}`;
293
+ // Check Cache
294
+ const cached = ebayCache.get(cacheKey);
295
+ if (cached) {
296
+ if (debug)
297
+ console.log("Returning cached eBay Search Data", cacheKey);
298
+ return cached;
299
+ }
217
300
  if (debug)
218
- console.log("Fetching ebay API Data");
301
+ console.log("Fetching ebay API Items Search Data");
219
302
  try {
220
- const response = await fetch(apiProps.proxyURL + encodeURIComponent(apiProps.baseSearchURL + apiProps.qsSearchURL), {
303
+ const response = await fetch(apiProps.proxyURL + encodeURIComponent(fullURL), {
221
304
  method: 'GET',
222
305
  headers: {
223
306
  'Authorization': 'Bearer ' + token,
@@ -230,6 +313,8 @@ export function getEbayItemsSearch(props) {
230
313
  throw new Error(`HTTP error! status: ${response.status}`);
231
314
  }
232
315
  const data = await response.json();
316
+ // Store in Cache
317
+ ebayCache.set(cacheKey, data);
233
318
  return data;
234
319
  }
235
320
  catch (error) {
@@ -21,9 +21,11 @@ const debug = false;
21
21
  /* ========== SHOPPING CART UI COMPONENT ========== */
22
22
  /* ================================================ */
23
23
  ShoppingCart.propTypes = {
24
- payPalClientID: PropTypes.string.isRequired,
24
+ payPalClientID: PropTypes.string,
25
25
  };
26
26
  export function ShoppingCart(props) {
27
+ const config = usePixelatedConfig();
28
+ const payPalClientID = props.payPalClientID || config?.paypal?.payPalApiKey || config?.paypal?.sandboxPayPalApiKey;
27
29
  const [shoppingCart, setShoppingCart] = useState();
28
30
  const [shippingData, setShippingData] = useState();
29
31
  const [checkoutData, setcheckoutData] = useState();
@@ -146,7 +148,7 @@ export function ShoppingCart(props) {
146
148
  }
147
149
  else if (progressStep === "Checkout") {
148
150
  // ========== CHECKOUT ==========
149
- return (_jsxs("div", { className: "pixCart", children: [_jsx(CalloutHeader, { title: "Checkout Summary : " }), checkoutData && _jsx(CheckoutItems, { ...checkoutData }), _jsx("br", {}), _jsx(FormButton, { className: "pixCartButton", type: "button", id: "backToCart", text: "<= Back To Cart", onClick: () => SetProgressStep("ShippingInfo") }), _jsx("br", {}), _jsx(PayPal, { payPalClientID: props.payPalClientID, checkoutData: getCheckoutData(), onApprove: handleOnApprove })] }));
151
+ return (_jsxs("div", { className: "pixCart", children: [_jsx(CalloutHeader, { title: "Checkout Summary : " }), checkoutData && _jsx(CheckoutItems, { ...checkoutData }), _jsx("br", {}), _jsx(FormButton, { className: "pixCartButton", type: "button", id: "backToCart", text: "<= Back To Cart", onClick: () => SetProgressStep("ShippingInfo") }), _jsx("br", {}), payPalClientID && (_jsx(PayPal, { payPalClientID: payPalClientID, checkoutData: getCheckoutData(), onApprove: handleOnApprove }))] }));
150
152
  }
151
153
  else if (progressStep === "ShippingInfo") {
152
154
  // ========== SHOPPING CART ==========
@@ -20,9 +20,11 @@
20
20
  "ebay": {
21
21
  "proxyURL": "https://proxy.pixelated.tech/prod/proxy?url=",
22
22
  "appId": "BrianWha-Pixelate-PRD-1fb4458de-1a8431fe",
23
+ "appDevId": "fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd",
23
24
  "appCertId": "PRD-fb4458deef01-0d54-496a-b572-a04b",
24
25
  "sbxAppId": "BrianWha-Pixelate-SBX-ad482b6ae-8cb8fead",
25
- "sbxAppCertId": "",
26
+ "sbxAppDevId": "fa7b86b5-3a10-43ae-bf6f-497aa4e55ddd",
27
+ "sbxAppCertId": "SBX-d482b6ae0d62-f57c-4d3c-88b7-22ec",
26
28
  "globalId": "EBAY_US",
27
29
  "environment": "production",
28
30
  "tokenScope": "https://api.ebay.com/oauth/api_scope",
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ export * from './components/config/config.utils';
5
5
  export * from './components/general/404';
6
6
  export * from './components/general/accordion';
7
7
  export * from './components/general/buzzwordbingo';
8
+ export * from './components/general/cache-manager';
8
9
  export * from './components/general/calendly';
9
10
  export * from './components/general/callout';
10
11
  export * from './components/general/carousel';
@@ -143,7 +143,7 @@ fi
143
143
  echo ""
144
144
  echo "💾 Step 5: Committing changes..."
145
145
  echo "================================================="
146
- if grep -q "\"config:encrypt\":" package.json && [ -f "./src/app/config/pixelated.config.json" ]; then
146
+ if grep -q "\"config:encrypt\":" package.json; then
147
147
  echo "🔒 Encrypting configuration..."
148
148
  npm run config:encrypt
149
149
  fi
@@ -200,7 +200,7 @@ else
200
200
  echo "â„šī¸ Tag v$new_version already exists"
201
201
  fi
202
202
 
203
- if grep -q "\"config:decrypt\":" package.json && [ -f "./src/app/config/pixelated.config.json" ]; then
203
+ if grep -q "\"config:decrypt\":" package.json; then
204
204
  echo "🔓 Decrypting configuration for local development..."
205
205
  npm run config:decrypt
206
206
  fi
@@ -0,0 +1,13 @@
1
+ import configJson from '@/config/pixelated.config.json';
2
+ /**
3
+ * Standard mock configuration derived from the main pixelated.config.json.
4
+ * Used as the default configuration for renderWithProviders in tests.
5
+ */
6
+ export const mockConfig = configJson;
7
+ /**
8
+ * Helper to create a partial configuration override for specific test cases.
9
+ */
10
+ export const createMockConfig = (overrides) => ({
11
+ ...mockConfig,
12
+ ...overrides,
13
+ });
@@ -0,0 +1,46 @@
1
+ import { afterEach, vi, beforeAll, afterAll } from 'vitest';
2
+ import { cleanup } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ // Cleanup after each test
5
+ afterEach(() => {
6
+ cleanup();
7
+ });
8
+ // Mock window.matchMedia
9
+ Object.defineProperty(window, 'matchMedia', {
10
+ writable: true,
11
+ value: vi.fn().mockImplementation(query => ({
12
+ matches: false,
13
+ media: query,
14
+ onchange: null,
15
+ addListener: vi.fn(),
16
+ removeListener: vi.fn(),
17
+ addEventListener: vi.fn(),
18
+ removeEventListener: vi.fn(),
19
+ dispatchEvent: vi.fn(),
20
+ })),
21
+ });
22
+ // Mock IntersectionObserver
23
+ global.IntersectionObserver = class IntersectionObserver {
24
+ constructor() { }
25
+ disconnect() { }
26
+ observe() { }
27
+ takeRecords() {
28
+ return [];
29
+ }
30
+ unobserve() { }
31
+ };
32
+ // Suppress console errors in tests (optional)
33
+ const originalError = console.error;
34
+ beforeAll(() => {
35
+ console.error = (...args) => {
36
+ if (typeof args[0] === 'string' &&
37
+ (args[0].includes('Warning: ReactDOM.render') ||
38
+ args[0].includes('Not implemented: HTMLFormElement.prototype.submit'))) {
39
+ return;
40
+ }
41
+ originalError.call(console, ...args);
42
+ };
43
+ });
44
+ afterAll(() => {
45
+ console.error = originalError;
46
+ });
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { render } from '@testing-library/react';
3
+ import { PixelatedClientConfigProvider } from '@/components/config/config.client';
4
+ import { mockConfig } from './config.mock';
5
+ /**
6
+ * Custom render function that wraps components in the necessary providers.
7
+ * Defaults to using the mock version of the global pixelated.config.json.
8
+ */
9
+ function renderWithProviders(ui, { config = {}, ...renderOptions } = {}) {
10
+ const mergedConfig = { ...mockConfig, ...config };
11
+ function Wrapper({ children }) {
12
+ return (_jsx(PixelatedClientConfigProvider, { config: mergedConfig, children: children }));
13
+ }
14
+ return {
15
+ ...render(ui, { wrapper: Wrapper, ...renderOptions }),
16
+ config: mergedConfig,
17
+ };
18
+ }
19
+ // Re-export everything from RTL
20
+ export * from '@testing-library/react';
21
+ // Override the default render with our custom one
22
+ export { renderWithProviders as render };
23
+ export { renderWithProviders };
@@ -13,5 +13,5 @@ export declare function formatScore(score: number | null): string;
13
13
  /**
14
14
  * Formats audit item details for display
15
15
  */
16
- export declare function formatAuditItem(item: Record<string, unknown>, auditTitle?: string): string;
16
+ export declare function formatAuditItem(item: Record<string, unknown> | number, auditTitle?: string): string;
17
17
  //# sourceMappingURL=site-health-utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"site-health-utils.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGxD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA2J1F"}
1
+ {"version":3,"file":"site-health-utils.d.ts","sourceRoot":"","sources":["../../../../../src/components/admin/site-health/site-health-utils.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE9D;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAE1D;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAGxD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA8KnG"}