@pixelated-tech/components 3.8.0 → 3.9.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 (217) hide show
  1. package/README.md +7 -0
  2. package/dist/components/admin/site-health/site-health-axe-core.integration.js +135 -20
  3. package/dist/components/admin/site-health/site-health-axe-core.integration.test.js +79 -0
  4. package/dist/components/admin/site-health/site-health-axe-core.js +35 -0
  5. package/dist/components/admin/site-health/site-health-core-web-vitals.integration.js +5 -3
  6. package/dist/components/admin/site-health/site-health-core-web-vitals.integration.test.js +33 -0
  7. package/dist/components/admin/site-health/site-health-template.js +3 -1
  8. package/dist/components/admin/site-health/site-health-utils.js +22 -0
  9. package/dist/components/config/config.example.js +2 -0
  10. package/dist/components/config/config.types.js +1 -3
  11. package/dist/components/config/config.utils.js +13 -5
  12. package/dist/components/config/config.validators.js +67 -0
  13. package/dist/components/general/cache-manager.js +124 -0
  14. package/dist/components/general/google.reviews.components.js +1 -2
  15. package/dist/components/general/googlemap.js +5 -2
  16. package/dist/components/general/metadata.functions.js +15 -1
  17. package/dist/components/general/proxy-csp-listener.js +20 -0
  18. package/dist/components/general/proxy-handler.js +4 -2
  19. package/dist/components/general/sitemap.js +9 -16
  20. package/dist/components/shoppingcart/ebay.components.js +123 -15
  21. package/dist/components/shoppingcart/ebay.functions.js +136 -34
  22. package/dist/components/shoppingcart/shoppingcart.components.js +4 -2
  23. package/dist/components/sitebuilder/config/ConfigEngine.js +5 -2
  24. package/dist/components/sitebuilder/config/google-fonts.js +5 -3
  25. package/dist/components/sitebuilder/page/lib/pageStorageLocal.js +2 -1
  26. package/dist/config/pixelated.config.json +83 -69
  27. package/dist/index.adminclient.js +1 -0
  28. package/dist/index.js +3 -0
  29. package/dist/index.server.js +1 -0
  30. package/dist/scripts/release.sh +2 -2
  31. package/dist/test/config.mock.js +13 -0
  32. package/dist/test/setup.js +46 -0
  33. package/dist/test/test-utils.js +23 -0
  34. package/dist/types/components/admin/site-health/site-health-axe-core.d.ts.map +1 -1
  35. package/dist/types/components/admin/site-health/site-health-axe-core.integration.d.ts +2 -0
  36. package/dist/types/components/admin/site-health/site-health-axe-core.integration.d.ts.map +1 -1
  37. package/dist/types/components/admin/site-health/site-health-axe-core.integration.test.d.ts +2 -0
  38. package/dist/types/components/admin/site-health/site-health-axe-core.integration.test.d.ts.map +1 -0
  39. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.d.ts +1 -0
  40. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.d.ts.map +1 -1
  41. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.test.d.ts +2 -0
  42. package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.test.d.ts.map +1 -0
  43. package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
  44. package/dist/types/components/admin/site-health/site-health-utils.d.ts +1 -1
  45. package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -1
  46. package/dist/types/components/config/config.example.d.ts.map +1 -1
  47. package/dist/types/components/config/config.types.d.ts +30 -11
  48. package/dist/types/components/config/config.types.d.ts.map +1 -1
  49. package/dist/types/components/config/config.utils.d.ts.map +1 -1
  50. package/dist/types/components/config/config.validators.d.ts +17 -0
  51. package/dist/types/components/config/config.validators.d.ts.map +1 -0
  52. package/dist/types/components/general/cache-manager.d.ts +45 -0
  53. package/dist/types/components/general/cache-manager.d.ts.map +1 -0
  54. package/dist/types/components/general/google.reviews.components.d.ts.map +1 -1
  55. package/dist/types/components/general/googlemap.d.ts +1 -1
  56. package/dist/types/components/general/googlemap.d.ts.map +1 -1
  57. package/dist/types/components/general/metadata.functions.d.ts +2 -8
  58. package/dist/types/components/general/metadata.functions.d.ts.map +1 -1
  59. package/dist/types/components/general/proxy-csp-listener.d.ts +15 -0
  60. package/dist/types/components/general/proxy-csp-listener.d.ts.map +1 -0
  61. package/dist/types/components/general/proxy-handler.d.ts.map +1 -1
  62. package/dist/types/components/general/schema-localbusiness.d.ts.map +1 -1
  63. package/dist/types/components/general/schema-website.d.ts.map +1 -1
  64. package/dist/types/components/general/sitemap.d.ts.map +1 -1
  65. package/dist/types/components/shoppingcart/ebay.components.d.ts +9 -0
  66. package/dist/types/components/shoppingcart/ebay.components.d.ts.map +1 -1
  67. package/dist/types/components/shoppingcart/ebay.functions.d.ts +20 -21
  68. package/dist/types/components/shoppingcart/ebay.functions.d.ts.map +1 -1
  69. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
  70. package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
  71. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts +135 -0
  72. package/dist/types/components/sitebuilder/config/ConfigBuilder.d.ts.map +1 -1
  73. package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts +3 -2
  74. package/dist/types/components/sitebuilder/config/ConfigEngine.d.ts.map +1 -1
  75. package/dist/types/components/sitebuilder/config/google-fonts.d.ts +1 -1
  76. package/dist/types/components/sitebuilder/config/google-fonts.d.ts.map +1 -1
  77. package/dist/types/components/sitebuilder/page/lib/pageStorageLocal.d.ts.map +1 -1
  78. package/dist/types/index.adminclient.d.ts +1 -0
  79. package/dist/types/index.d.ts +3 -0
  80. package/dist/types/index.server.d.ts +1 -0
  81. package/dist/types/stories/{seo/seo.404.stories.d.ts → general/404.stories.d.ts} +1 -2
  82. package/dist/types/stories/general/404.stories.d.ts.map +1 -0
  83. package/dist/types/stories/general/accordion.stories.d.ts +3 -3
  84. package/dist/types/stories/general/accordion.stories.d.ts.map +1 -1
  85. package/dist/types/stories/general/buzzword-bingo.stories.d.ts.map +1 -0
  86. package/dist/types/stories/{callout → general}/callout.many.stories.d.ts +0 -1
  87. package/dist/types/stories/general/callout.many.stories.d.ts.map +1 -0
  88. package/dist/types/stories/{callout → general}/callout.stories.d.ts +0 -1
  89. package/dist/types/stories/general/callout.stories.d.ts.map +1 -0
  90. package/dist/types/stories/general/carousel-hero.stories.d.ts +14 -0
  91. package/dist/types/stories/general/carousel-hero.stories.d.ts.map +1 -0
  92. package/dist/types/stories/{carousel → general}/carousel-reviews.stories.d.ts +0 -1
  93. package/dist/types/stories/general/carousel-reviews.stories.d.ts.map +1 -0
  94. package/dist/types/stories/general/carousel-workportfolio.stories.d.ts +14 -0
  95. package/dist/types/stories/general/carousel-workportfolio.stories.d.ts.map +1 -0
  96. package/dist/types/stories/{carousel → general}/carousel.stories.d.ts +1 -9
  97. package/dist/types/stories/general/carousel.stories.d.ts.map +1 -0
  98. package/dist/types/stories/general/contentful.item.stories.d.ts +12 -0
  99. package/dist/types/stories/general/contentful.item.stories.d.ts.map +1 -0
  100. package/dist/types/stories/general/contentful.items.stories.d.ts +10 -0
  101. package/dist/types/stories/general/contentful.items.stories.d.ts.map +1 -0
  102. package/dist/types/stories/{cms → general}/contentful.stories.d.ts +0 -1
  103. package/dist/types/stories/general/contentful.stories.d.ts.map +1 -0
  104. package/dist/types/stories/{seo/seo.faq-accordion.stories.d.ts → general/faq-accordion.stories.d.ts} +1 -1
  105. package/dist/types/stories/general/faq-accordion.stories.d.ts.map +1 -0
  106. package/dist/types/stories/{cms → general}/google.reviews.stories.d.ts +1 -2
  107. package/dist/types/stories/general/google.reviews.stories.d.ts.map +1 -0
  108. package/dist/types/stories/{seo/seo.googleanalytics.stories.d.ts → general/googleanalytics.stories.d.ts} +2 -4
  109. package/dist/types/stories/general/googleanalytics.stories.d.ts.map +1 -0
  110. package/dist/types/stories/{seo/seo.googlesearch.stories.d.ts → general/googlesearch.stories.d.ts} +1 -1
  111. package/dist/types/stories/general/googlesearch.stories.d.ts.map +1 -0
  112. package/dist/types/stories/{cms → general}/gravatar.stories.d.ts +0 -1
  113. package/dist/types/stories/general/gravatar.stories.d.ts.map +1 -0
  114. package/dist/types/stories/general/headers.stories.d.ts.map +1 -1
  115. package/dist/types/stories/{cms → general}/instagram.stories.d.ts +2 -2
  116. package/dist/types/stories/general/instagram.stories.d.ts.map +1 -0
  117. package/dist/types/stories/general/layout.stories.d.ts +9 -9
  118. package/dist/types/stories/{structured → general}/markdown.stories.d.ts +1 -2
  119. package/dist/types/stories/general/markdown.stories.d.ts.map +1 -0
  120. package/dist/types/stories/{menu → general}/menu-accordion.stories.d.ts +2 -2
  121. package/dist/types/stories/general/menu-accordion.stories.d.ts.map +1 -0
  122. package/dist/types/stories/{menu → general}/menu-expando.stories.d.ts +1 -1
  123. package/dist/types/stories/general/menu-expando.stories.d.ts.map +1 -0
  124. package/dist/types/stories/{menu → general}/menu-simple.stories.d.ts +1 -1
  125. package/dist/types/stories/general/menu-simple.stories.d.ts.map +1 -0
  126. package/dist/types/stories/{seo/seo.metadata.stories.d.ts → general/metadata.stories.d.ts} +1 -1
  127. package/dist/types/stories/general/metadata.stories.d.ts.map +1 -0
  128. package/dist/types/stories/general/microinteractions.stories.d.ts +0 -1
  129. package/dist/types/stories/general/microinteractions.stories.d.ts.map +1 -1
  130. package/dist/types/stories/general/modal.stories.d.ts +0 -1
  131. package/dist/types/stories/general/modal.stories.d.ts.map +1 -1
  132. package/dist/types/stories/general/nerdjoke.stories.d.ts.map +1 -0
  133. package/dist/types/stories/{structured → general}/recipe.stories.d.ts +1 -2
  134. package/dist/types/stories/general/recipe.stories.d.ts.map +1 -0
  135. package/dist/types/stories/{structured → general}/resume.stories.d.ts +1 -2
  136. package/dist/types/stories/{structured → general}/resume.stories.d.ts.map +1 -1
  137. package/dist/types/stories/{seo/seo.schema.stories.d.ts → general/schema.stories.d.ts} +1 -2
  138. package/dist/types/stories/general/schema.stories.d.ts.map +1 -0
  139. package/dist/types/stories/{seo/seo.sitemap.stories.d.ts → general/sitemap.stories.d.ts} +1 -1
  140. package/dist/types/stories/general/sitemap.stories.d.ts.map +1 -0
  141. package/dist/types/stories/general/smartimage.stories.d.ts.map +1 -1
  142. package/dist/types/stories/{structured → general}/socialcard.stories.d.ts +0 -1
  143. package/dist/types/stories/general/socialcard.stories.d.ts.map +1 -0
  144. package/dist/types/stories/general/splitscroll.stories.d.ts.map +1 -1
  145. package/dist/types/stories/{carousel → general}/tiles.stories.d.ts +0 -1
  146. package/dist/types/stories/general/tiles.stories.d.ts.map +1 -0
  147. package/dist/types/stories/{structured → general}/timeline.stories.d.ts +0 -1
  148. package/dist/types/stories/general/timeline.stories.d.ts.map +1 -0
  149. package/dist/types/stories/{cms → general}/wordpress.stories.d.ts +6 -2
  150. package/dist/types/stories/general/wordpress.stories.d.ts.map +1 -0
  151. package/dist/types/stories/shoppingcart/ebay.stories.d.ts +16 -0
  152. package/dist/types/stories/shoppingcart/ebay.stories.d.ts.map +1 -0
  153. package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts +1 -12
  154. package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts.map +1 -1
  155. package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts +1 -12
  156. package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts.map +1 -1
  157. package/dist/types/stories/shoppingcart/shoppingcart.stories.d.ts.map +1 -1
  158. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts +13 -35
  159. package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts.map +1 -1
  160. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts +0 -1
  161. package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -1
  162. package/dist/types/stories/sitebuilder/pageengine.stories.d.ts +0 -1
  163. package/dist/types/stories/sitebuilder/pageengine.stories.d.ts.map +1 -1
  164. package/dist/types/test/config.mock.d.ts +11 -0
  165. package/dist/types/test/config.mock.d.ts.map +1 -0
  166. package/dist/types/test/setup.d.ts.map +1 -0
  167. package/dist/types/test/test-utils.d.ts +87 -0
  168. package/dist/types/test/test-utils.d.ts.map +1 -0
  169. package/dist/types/tests/cache-manager.test.d.ts +2 -0
  170. package/dist/types/tests/cache-manager.test.d.ts.map +1 -0
  171. package/dist/types/tests/config-core.test.d.ts +2 -0
  172. package/dist/types/tests/config-core.test.d.ts.map +1 -0
  173. package/dist/types/tests/config.validators.test.d.ts +2 -0
  174. package/dist/types/tests/config.validators.test.d.ts.map +1 -0
  175. package/dist/types/tests/ebay-functions.test.d.ts +2 -0
  176. package/dist/types/tests/ebay-functions.test.d.ts.map +1 -0
  177. package/dist/types/tests/site-health-utils.test.d.ts +2 -0
  178. package/dist/types/tests/site-health-utils.test.d.ts.map +1 -0
  179. package/package.json +7 -7
  180. package/dist/types/stories/callout/callout.many.stories.d.ts.map +0 -1
  181. package/dist/types/stories/callout/callout.stories.d.ts.map +0 -1
  182. package/dist/types/stories/carousel/carousel-hero.stories.d.ts +0 -22
  183. package/dist/types/stories/carousel/carousel-hero.stories.d.ts.map +0 -1
  184. package/dist/types/stories/carousel/carousel-reviews.stories.d.ts.map +0 -1
  185. package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts +0 -22
  186. package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts.map +0 -1
  187. package/dist/types/stories/carousel/carousel.stories.d.ts.map +0 -1
  188. package/dist/types/stories/carousel/tiles.stories.d.ts.map +0 -1
  189. package/dist/types/stories/cms/contentful.item.stories.d.ts +0 -21
  190. package/dist/types/stories/cms/contentful.item.stories.d.ts.map +0 -1
  191. package/dist/types/stories/cms/contentful.items.stories.d.ts +0 -20
  192. package/dist/types/stories/cms/contentful.items.stories.d.ts.map +0 -1
  193. package/dist/types/stories/cms/contentful.stories.d.ts.map +0 -1
  194. package/dist/types/stories/cms/google.reviews.stories.d.ts.map +0 -1
  195. package/dist/types/stories/cms/gravatar.stories.d.ts.map +0 -1
  196. package/dist/types/stories/cms/instagram.stories.d.ts.map +0 -1
  197. package/dist/types/stories/cms/wordpress.stories.d.ts.map +0 -1
  198. package/dist/types/stories/menu/menu-accordion.stories.d.ts.map +0 -1
  199. package/dist/types/stories/menu/menu-expando.stories.d.ts.map +0 -1
  200. package/dist/types/stories/menu/menu-simple.stories.d.ts.map +0 -1
  201. package/dist/types/stories/nerdjoke.stories.d.ts.map +0 -1
  202. package/dist/types/stories/seo/seo.404.stories.d.ts.map +0 -1
  203. package/dist/types/stories/seo/seo.faq-accordion.stories.d.ts.map +0 -1
  204. package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +0 -1
  205. package/dist/types/stories/seo/seo.googlesearch.stories.d.ts.map +0 -1
  206. package/dist/types/stories/seo/seo.metadata.stories.d.ts.map +0 -1
  207. package/dist/types/stories/seo/seo.schema.stories.d.ts.map +0 -1
  208. package/dist/types/stories/seo/seo.sitemap.stories.d.ts.map +0 -1
  209. package/dist/types/stories/structured/buzzword-bingo.stories.d.ts.map +0 -1
  210. package/dist/types/stories/structured/markdown.stories.d.ts.map +0 -1
  211. package/dist/types/stories/structured/recipe.stories.d.ts.map +0 -1
  212. package/dist/types/stories/structured/socialcard.stories.d.ts.map +0 -1
  213. package/dist/types/stories/structured/timeline.stories.d.ts.map +0 -1
  214. package/dist/types/tests/setup.d.ts.map +0 -1
  215. /package/dist/types/stories/{structured → general}/buzzword-bingo.stories.d.ts +0 -0
  216. /package/dist/types/stories/{nerdjoke.stories.d.ts → general/nerdjoke.stories.d.ts} +0 -0
  217. /package/dist/types/{tests → test}/setup.d.ts +0 -0
@@ -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
+ }
@@ -6,7 +6,6 @@ import { SmartImage } from './smartimage';
6
6
  import { getGoogleReviewsByPlaceId } from './google.reviews.functions';
7
7
  import { usePixelatedConfig } from '../config/config.client';
8
8
  import './google.reviews.css';
9
- const GOOGLE_MAPS_API_KEY = 'AIzaSyBJVi0O9Ir9imRgINLZbojTifatX-Z4aUs';
10
9
  GoogleReviewsCard.propTypes = {
11
10
  placeId: PropTypes.string.isRequired,
12
11
  language: PropTypes.string,
@@ -20,7 +19,7 @@ export function GoogleReviewsCard(props) {
20
19
  const [reviews, setReviews] = useState([]);
21
20
  const [loading, setLoading] = useState(true);
22
21
  const [error, setError] = useState(null);
23
- const apiKey = props.apiKey || config?.googleMaps?.apiKey || GOOGLE_MAPS_API_KEY;
22
+ const apiKey = props.apiKey || config?.googleMaps?.apiKey || '';
24
23
  const proxyBase = props.proxyBase || config?.global?.proxyUrl || undefined;
25
24
  useEffect(() => {
26
25
  (async () => {
@@ -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
  }
@@ -65,6 +65,8 @@ export function getAllRoutes(routes, key) {
65
65
  return result;
66
66
  }
67
67
  export const getMetadata = (routes, key = "name", value = "Home") => {
68
+ // Validate the routes blob early to fail fast if invalid
69
+ assertRoutes(routes);
68
70
  const foundObject = getRouteByKey(routes, key, value);
69
71
  if (foundObject) {
70
72
  const metadata = {
@@ -105,8 +107,20 @@ export function getAccordionMenuData(myRoutes) {
105
107
  });
106
108
  return menuItems;
107
109
  }
110
+ import { assertSiteInfo, assertRoutes } from '../config/config.validators';
108
111
  export function generateMetaTags(props) {
109
112
  const { title, description, keywords, origin, url, site_name: prop_site_name, email: prop_email, image: prop_image, image_height: prop_image_height, image_width: prop_image_width, favicon: prop_favicon, siteInfo } = props;
113
+ const safeOrigin = typeof origin === 'string' && origin.length > 0 ? origin : '';
114
+ const safeUrl = typeof url === 'string' && url.length > 0 ? url : '';
115
+ let newOrigin;
116
+ try {
117
+ newOrigin = safeOrigin ? new URL(safeOrigin).hostname : undefined;
118
+ }
119
+ catch {
120
+ newOrigin = undefined;
121
+ }
122
+ // Validate siteInfo strictly so downstream sites must provide the contract
123
+ assertSiteInfo(siteInfo);
110
124
  // Use props if provided, otherwise fall back to siteInfo
111
125
  const site_name = prop_site_name || siteInfo?.name;
112
126
  const email = prop_email || siteInfo?.email;
@@ -114,5 +128,5 @@ export function generateMetaTags(props) {
114
128
  const image_height = prop_image_height || siteInfo?.image_height;
115
129
  const image_width = prop_image_width || siteInfo?.image_width;
116
130
  const favicon = prop_favicon || siteInfo?.favicon;
117
- return (_jsxs(_Fragment, { children: [_jsx("title", { children: title }), _jsx("meta", { charSet: "UTF-8" }), _jsx("meta", { httpEquiv: "content-type", content: "text/html; charset=UTF-8" }), _jsx("meta", { httpEquiv: 'Expires', content: '0' }), _jsx("meta", { httpEquiv: 'Pragma', content: 'no-cache' }), _jsx("meta", { httpEquiv: 'Cache-Control', content: 'no-cache' }), _jsx("meta", { name: "application-name", content: site_name }), _jsx("meta", { name: "author", content: site_name + ", " + email }), _jsx("meta", { name: 'copyright', content: site_name }), _jsx("meta", { name: "creator", content: site_name }), _jsx("meta", { name: "description", content: description }), _jsx("meta", { name: "keywords", content: keywords }), _jsx("meta", { name: 'language', content: 'EN' }), _jsx("meta", { name: 'owner', content: site_name }), _jsx("meta", { name: "publisher", content: site_name }), _jsx("meta", { name: 'rating', content: 'General' }), _jsx("meta", { name: 'reply-to', content: email }), _jsx("meta", { name: "robots", content: "index, follow" }), _jsx("meta", { name: 'url', content: url }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0, shrink-to-fit=no" }), _jsx("meta", { property: "og:description", content: description }), _jsx("meta", { property: 'og:email', content: email }), _jsx("meta", { property: "og:image", content: image }), _jsx("meta", { property: "og:image:height", content: image_height }), _jsx("meta", { property: "og:image:width", content: image_width }), _jsx("meta", { property: "og:locale", content: "en_US" }), _jsx("meta", { property: "og:site_name", content: site_name }), _jsx("meta", { property: "og:title", content: title }), _jsx("meta", { property: "og:type", content: "website" }), _jsx("meta", { property: "og:url", content: url }), _jsx("meta", { itemProp: "name", content: site_name }), _jsx("meta", { itemProp: "url", content: url }), _jsx("meta", { itemProp: "description", content: description }), _jsx("meta", { itemProp: "thumbnailUrl", content: image }), _jsx("meta", { property: "twitter:domain", content: new URL(origin).hostname }), _jsx("meta", { property: "twitter:url", content: url }), _jsx("meta", { name: "twitter:card", content: "summary_large_image" }), _jsx("meta", { name: "twitter:creator", content: site_name }), _jsx("meta", { name: "twitter:description", content: description }), _jsx("meta", { name: "twitter:image", content: image }), _jsx("meta", { name: "twitter:image:height", content: image_height }), _jsx("meta", { name: "twitter:image:width", content: image_width }), _jsx("meta", { name: "twitter:title", content: title }), _jsx("link", { rel: "author", href: origin }), _jsx("link", { rel: "canonical", href: url }), _jsx("link", { rel: "icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "shortcut icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "manifest", href: "/manifest.webmanifest" }), _jsx("link", { rel: "preconnect", href: "https://images.ctfassets.net/" }), _jsx("link", { rel: "preconnect", href: "https://res.cloudinary.com/" }), _jsx("link", { rel: "preconnect", href: "https://farm2.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm6.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm8.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm66.static.flickr.com" })] }));
131
+ return (_jsxs(_Fragment, { children: [_jsx("title", { children: title }), _jsx("meta", { charSet: "UTF-8" }), _jsx("meta", { httpEquiv: "content-type", content: "text/html; charset=UTF-8" }), _jsx("meta", { httpEquiv: 'Expires', content: '0' }), _jsx("meta", { httpEquiv: 'Pragma', content: 'no-cache' }), _jsx("meta", { httpEquiv: 'Cache-Control', content: 'no-cache' }), _jsx("meta", { name: "application-name", content: site_name }), _jsx("meta", { name: "author", content: site_name + ", " + email }), _jsx("meta", { name: 'copyright', content: site_name }), _jsx("meta", { name: "creator", content: site_name }), _jsx("meta", { name: "description", content: description }), _jsx("meta", { name: "keywords", content: keywords }), _jsx("meta", { name: 'language', content: 'EN' }), _jsx("meta", { name: 'owner', content: site_name }), _jsx("meta", { name: "publisher", content: site_name }), _jsx("meta", { name: 'rating', content: 'General' }), _jsx("meta", { name: 'reply-to', content: email }), _jsx("meta", { name: "robots", content: "index, follow" }), _jsx("meta", { name: 'url', content: url }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1.0, shrink-to-fit=no" }), _jsx("meta", { property: "og:description", content: description }), _jsx("meta", { property: 'og:email', content: email }), _jsx("meta", { property: "og:image", content: image }), _jsx("meta", { property: "og:image:height", content: image_height != null ? String(image_height) : undefined }), _jsx("meta", { property: "og:image:width", content: image_width != null ? String(image_width) : undefined }), _jsx("meta", { property: "og:locale", content: "en_US" }), _jsx("meta", { property: "og:site_name", content: site_name }), _jsx("meta", { property: "og:title", content: title }), _jsx("meta", { property: "og:type", content: "website" }), _jsx("meta", { property: "og:url", content: url }), _jsx("meta", { itemProp: "name", content: site_name }), _jsx("meta", { itemProp: "url", content: url }), _jsx("meta", { itemProp: "description", content: description }), _jsx("meta", { itemProp: "thumbnailUrl", content: image }), _jsx("meta", { property: "twitter:domain", content: newOrigin }), _jsx("meta", { property: "twitter:url", content: url }), _jsx("meta", { name: "twitter:card", content: "summary_large_image" }), _jsx("meta", { name: "twitter:creator", content: site_name }), _jsx("meta", { name: "twitter:description", content: description }), _jsx("meta", { name: "twitter:image", content: image }), _jsx("meta", { name: "twitter:image:height", content: image_height != null ? String(image_height) : undefined }), _jsx("meta", { name: "twitter:image:width", content: image_width != null ? String(image_width) : undefined }), _jsx("meta", { name: "twitter:title", content: title }), _jsx("link", { rel: "author", href: newOrigin }), _jsx("link", { rel: "canonical", href: url }), _jsx("link", { rel: "icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "shortcut icon", type: "image/x-icon", href: favicon }), _jsx("link", { rel: "manifest", href: "/manifest.webmanifest" }), _jsx("link", { rel: "preconnect", href: "https://images.ctfassets.net/" }), _jsx("link", { rel: "preconnect", href: "https://res.cloudinary.com/" }), _jsx("link", { rel: "preconnect", href: "https://farm2.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm6.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm8.static.flickr.com" }), _jsx("link", { rel: "preconnect", href: "https://farm66.static.flickr.com" })] }));
118
132
  }
@@ -0,0 +1,20 @@
1
+ "use client";
2
+ const debug = false;
3
+ export function attachProxyCspListener(onCsp) {
4
+ const handler = (e) => {
5
+ const info = {
6
+ blockedURI: e.blockedURI,
7
+ violatedDirective: e.violatedDirective,
8
+ originalPolicy: e.originalPolicy,
9
+ sourceFile: e?.sourceFile,
10
+ disposition: e.disposition,
11
+ referrer: e.referrer,
12
+ };
13
+ const defaultHandler = (i) => { if (debug)
14
+ console.warn('CSP violation', i); };
15
+ (onCsp ?? defaultHandler)(info);
16
+ };
17
+ window.addEventListener('securitypolicyviolation', handler);
18
+ // Return a cleanup function so callers can detach the listener
19
+ return () => window.removeEventListener('securitypolicyviolation', handler);
20
+ }
@@ -41,10 +41,12 @@ export function handlePixelatedProxy(req) {
41
41
  response.headers.set("Permissions-Policy", "camera=(), microphone=(), geolocation=(), interest-cohort=()");
42
42
  // Content Security Policy (CSP)
43
43
  // Includes all discovered domains in the workspace: HubSpot, Gravatar, Flickr, Contentful, Cloudinary, eBay, and Google Analytics + Search.
44
+ const scriptSrc = "'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com https://*.googletagmanager.com https://*.hs-scripts.com https://*.hs-analytics.net https://*.hsforms.net https://*.hscollectedforms.net https://*.hs-banner.com https://*.google.com https://*.doubleclick.net https://*.googleadservices.com https://*.adtrafficquality.google https://*.hsappstatic.net https://assets.calendly.com https://cdn.jsdelivr.net";
44
45
  const csp = [
45
46
  "default-src 'self'",
46
- "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://va.vercel-scripts.com https://*.googletagmanager.com https://*.hs-scripts.com https://*.hs-analytics.net https://*.hsforms.net https://*.hscollectedforms.net https://*.hs-banner.com https://*.google.com https://*.doubleclick.net https://*.googleadservices.com https://*.adtrafficquality.google https://*.hsappstatic.net https://assets.calendly.com",
47
- "connect-src 'self' https: https://*.hubspot.com https://proxy.pixelated.tech https://sendmail.pixelated.tech https://*.google-analytics.com https://*.analytics.google.com",
47
+ `script-src ${scriptSrc}`,
48
+ `script-src-elem ${scriptSrc}`,
49
+ "connect-src 'self' https: https://*.hubspot.com https://proxy.pixelated.tech https://sendmail.pixelated.tech https://*.google-analytics.com https://*.analytics.google.com https://cdn.jsdelivr.net",
48
50
  "img-src 'self' data: https: https://*.gravatar.com https://*.staticflickr.com https://*.ctfassets.net https://res.cloudinary.com https://*.ebayimg.com",
49
51
  "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://*.google.com",
50
52
  "font-src 'self' data: https://fonts.gstatic.com",
@@ -296,25 +296,18 @@ export async function createContentfulImageURLs(props) {
296
296
  }
297
297
  return sitemap;
298
298
  }
299
- const defaultEbayProps = {
300
- proxyURL: "https://proxy.pixelated.tech/prod/proxy?url=",
301
- baseTokenURL: 'https://api.ebay.com/identity/v1/oauth2/token',
302
- tokenScope: 'https://api.ebay.com/oauth/api_scope',
303
- baseSearchURL: 'https://api.ebay.com/buy/browse/v1/item_summary/search',
304
- qsSearchURL: '?q=sunglasses&fieldgroups=full&category_ids=79720&aspect_filter=categoryId:79720&filter=sellers:{pixelatedtech}&sort=newlyListed&limit=200',
305
- baseItemURL: 'https://api.ebay.com/buy/browse/v1/item',
306
- qsItemURL: '/v1|295959752403|0?fieldgroups=PRODUCT,ADDITIONAL_SELLER_DETAILS',
307
- appId: 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe', // clientId
308
- appCertId: 'PRD-fb4458deef01-0d54-496a-b572-a04b', // clientSecret
309
- sbxAppId: 'BrianWha-Pixelate-SBX-ad482b6ae-8cb8fead', // Sandbox
310
- sbxAppCertId: '',
311
- globalId: 'EBAY-US',
312
- };
313
299
  export async function createEbayItemURLs(origin) {
314
300
  const sitemap = [];
315
- await getEbayAppToken({ apiProps: defaultEbayProps })
301
+ // Load configuration
302
+ const config = getFullPixelatedConfig();
303
+ const globalProxy = config.global?.proxyUrl;
304
+ const ebayProps = {
305
+ ...(globalProxy ? { proxyURL: globalProxy } : {}),
306
+ ...config.ebay
307
+ };
308
+ await getEbayAppToken({ apiProps: ebayProps })
316
309
  .then(async (response) => {
317
- await getEbayItemsSearch({ apiProps: defaultEbayProps, token: response })
310
+ await getEbayItemsSearch({ apiProps: ebayProps, token: response })
318
311
  .then((items) => {
319
312
  for (const item of items.itemSummaries) {
320
313
  sitemap.push({
@@ -4,11 +4,11 @@ import { useState, useEffect } from "react";
4
4
  import PropTypes from "prop-types";
5
5
  import { Carousel } from '../general/carousel';
6
6
  import { SmartImage } from "../general/smartimage";
7
- import { defaultEbayProps, ebaySunglassCategory, getEbayItems, getEbayItem, getShoppingCartItem } from "./ebay.functions";
7
+ import { getEbayItems, getEbayItem, getShoppingCartItem, getEbayRateLimits, getEbayAppToken } from "./ebay.functions";
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";
@@ -20,9 +20,10 @@ EbayItems.propTypes = {
20
20
  };
21
21
  export function EbayItems(props) {
22
22
  // https://developer.ebay.com/devzone/finding/HowTo/GettingStarted_JS_NV_JSON/GettingStarted_JS_NV_JSON.html
23
+ const config = usePixelatedConfig();
23
24
  const [items, setItems] = useState([]);
24
25
  const [aspects, setAspects] = useState([]);
25
- const [apiProps] = useState({ ...defaultEbayProps, ...props.apiProps });
26
+ const apiProps = { ...(config?.ebay || {}), ...props.apiProps };
26
27
  paintItems.propTypes = {
27
28
  items: PropTypes.array.isRequired,
28
29
  cloudinaryProductEnv: PropTypes.string,
@@ -33,7 +34,7 @@ export function EbayItems(props) {
33
34
  let newItems = [];
34
35
  for (let key in props.items) {
35
36
  const item = props.items[key];
36
- const newItem = _jsx(EbayListItem, { item: item, cloudinaryProductEnv: props.cloudinaryProductEnv }, item.legacyItemId);
37
+ const newItem = _jsx(EbayListItem, { item: item, apiProps: apiProps, cloudinaryProductEnv: props.cloudinaryProductEnv }, item.legacyItemId);
37
38
  newItems.push(newItem);
38
39
  }
39
40
  return newItems;
@@ -44,6 +45,8 @@ export function EbayItems(props) {
44
45
  };
45
46
  async function fetchItems(props) {
46
47
  try {
48
+ if (debug)
49
+ console.log("Fetching ebay API Items Data");
47
50
  const myApiProps = { ...apiProps };
48
51
  if (props) {
49
52
  const params = new URLSearchParams(myApiProps.qsSearchURL);
@@ -55,9 +58,9 @@ export function EbayItems(props) {
55
58
  }
56
59
  const response = await getEbayItems({ apiProps: myApiProps });
57
60
  if (debug)
58
- console.log("eBay API Get Items Data", response);
59
- setItems(response?.itemSummaries || []);
60
- setAspects(response?.refinement?.aspectDistributions || []);
61
+ console.log("eBay API Search Items Data:", response);
62
+ setItems(response?.itemSummaries);
63
+ setAspects(response?.refinement?.aspectDistributions);
61
64
  }
62
65
  catch (error) {
63
66
  console.error("Error fetching eBay items:", error);
@@ -70,11 +73,11 @@ export function EbayItems(props) {
70
73
  fetchItems();
71
74
  ToggleLoading(false);
72
75
  }, []);
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 }) })] }));
76
+ if (items && items.length > 0) {
77
+ 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
78
  }
76
79
  else {
77
- return (_jsx("div", { className: "section-container", children: _jsx("div", { id: "ebayItems", className: "ebayItems", children: _jsx(Loading, {}) }) }));
80
+ return (_jsx("div", { className: "section-container", children: _jsx("div", { id: "ebayItems", className: "ebayItems" }) }));
78
81
  }
79
82
  }
80
83
  EbayListFilter.propTypes = {
@@ -124,16 +127,18 @@ export function EbayListFilter(props) {
124
127
  EbayListItem.propTypes = {
125
128
  item: PropTypes.any.isRequired,
126
129
  cloudinaryProductEnv: PropTypes.string,
130
+ apiProps: PropTypes.any,
127
131
  };
128
132
  export function EbayListItem(props) {
129
133
  const thisItem = props.item;
134
+ const apiProps = props.apiProps;
130
135
  // const itemURL = thisItem.itemWebUrl;
131
136
  const itemURL = "./store/" + thisItem.legacyItemId;
132
137
  const itemURLTarget = "_self"; /* "_blank" */
133
138
  const itemImage = (props.cloudinaryProductEnv)
134
139
  ? getImg({ url: thisItem.thumbnailImages[0].imageUrl, product_env: props.cloudinaryProductEnv })
135
140
  : thisItem.thumbnailImages[0].imageUrl;
136
- const shoppingCartItem = getShoppingCartItem({ thisItem: thisItem, cloudinaryProductEnv: props.cloudinaryProductEnv });
141
+ const shoppingCartItem = getShoppingCartItem({ thisItem: thisItem, cloudinaryProductEnv: props.cloudinaryProductEnv, apiProps: apiProps });
137
142
  // CHANGE EBAY URL TO LOCAL EBAY ITEM DETAIL URL
138
143
  shoppingCartItem.itemURL = itemURL;
139
144
  const config = usePixelatedConfig();
@@ -143,7 +148,7 @@ export function EbayListItem(props) {
143
148
  ? _jsx("a", { href: itemURL, target: itemURLTarget, rel: "noopener noreferrer", children: itemImageComponent })
144
149
  : (itemImageComponent) }), _jsxs("div", { className: "ebayItemBody grid-s5-e13", children: [_jsx("div", { className: "ebayItemHeader", children: itemURL
145
150
  ? _jsx(EbayItemHeader, { url: itemURL, target: itemURLTarget, title: thisItem.title })
146
- : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsxs("div", { className: "ebayItemDetails grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categories[0].categoryId == ebaySunglassCategory ? 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: "ebayItemPrice", children: itemURL
151
+ : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsxs("div", { className: "ebayItemDetails 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: "ebayItemPrice", children: itemURL
147
152
  ? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.price.value + " " + thisItem.price.currency] })
148
153
  : "$" + thisItem.price.value + " " + thisItem.price.currency }), _jsx("br", {}), _jsxs("div", { className: "ebayItemAddToCart", children: [_jsx(ViewItemDetails, { href: "/store", itemID: thisItem.legacyItemId }), _jsx(AddToCartButton, { handler: addToShoppingCart, item: shoppingCartItem, itemID: thisItem.legacyItemId })] })] })] }));
149
154
  }
@@ -164,8 +169,9 @@ EbayItemDetail.propTypes = {
164
169
  cloudinaryProductEnv: PropTypes.string,
165
170
  };
166
171
  export function EbayItemDetail(props) {
172
+ const config = usePixelatedConfig();
167
173
  const [item, setItem] = useState({});
168
- const [apiProps] = useState({ ...defaultEbayProps, ...props.apiProps });
174
+ const apiProps = { ...(config?.ebay || {}), ...props.apiProps };
169
175
  useEffect(() => {
170
176
  if (debug)
171
177
  console.log("Running useEffect");
@@ -191,11 +197,11 @@ export function EbayItemDetail(props) {
191
197
  : thisImage.imageUrl }));
192
198
  const itemURL = undefined;
193
199
  const itemURLTarget = "_self"; /* "_blank" */
194
- const shoppingCartItem = getShoppingCartItem({ thisItem: thisItem, cloudinaryProductEnv: props.cloudinaryProductEnv });
200
+ const shoppingCartItem = getShoppingCartItem({ thisItem: thisItem, cloudinaryProductEnv: props.cloudinaryProductEnv, apiProps: apiProps });
195
201
  shoppingCartItem.itemURL = itemURL;
196
202
  return (_jsx(_Fragment, { children: _jsxs("div", { className: "ebayItem row-12col", children: [_jsx("div", { className: "ebayItemHeader grid-s1-e13", children: itemURL
197
203
  ? _jsx(EbayItemHeader, { url: itemURL, title: thisItem.title })
198
- : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsx("br", {}), _jsx("div", { className: "ebayItemPhotoCarousel grid-s1-e7", children: _jsx(Carousel, { cards: images, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "ebayItemDetails grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.description.replace(/(<br\s*\/?>\s*){2,}/gi, '') } }) }), _jsx("br", {}), _jsxs("div", { className: "ebayItemDetails grid12", children: [_jsxs("div", { children: [_jsx("b", { children: "Item ID: " }), thisItem.legacyItemId] }), _jsxs("div", { children: [_jsx("b", { children: "Quantity: " }), thisItem.categoryId == ebaySunglassCategory ? 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: "ebayItemPrice", children: itemURL
204
+ : _jsx(EbayItemHeader, { title: thisItem.title }) }), _jsx("br", {}), _jsx("div", { className: "ebayItemPhotoCarousel grid-s1-e7", children: _jsx(Carousel, { cards: images, draggable: true, imgFit: "contain" }) }), _jsxs("div", { className: "grid-s7-e13", children: [_jsx("div", { className: "ebayItemDetails grid12", children: _jsx("div", { dangerouslySetInnerHTML: { __html: thisItem.description.replace(/(<br\s*\/?>\s*){2,}/gi, '') } }) }), _jsx("br", {}), _jsxs("div", { className: "ebayItemDetails 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: "ebayItemPrice", children: itemURL
199
205
  ? _jsxs("a", { href: itemURL, target: itemURLTarget, rel: "noreferrer", children: ["$", thisItem.price.value + " " + thisItem.price.currency] })
200
206
  : "$" + thisItem.price.value + " " + thisItem.price.currency }), _jsx("br", {}), _jsx("div", { className: "ebayItemAddToCart", children: _jsx(AddToCartButton, { handler: addToShoppingCart, item: shoppingCartItem, itemID: thisItem.legacyItemId }) })] })] }) }));
201
207
  }
@@ -203,3 +209,105 @@ export function EbayItemDetail(props) {
203
209
  return (_jsx(_Fragment, { children: _jsx("div", { id: "ebayItems", className: "ebayItems", children: _jsx("div", { className: "centered", children: "Loading..." }) }) }));
204
210
  }
205
211
  }
212
+ /* ========== EBAY RATE LIMITS VISUALIZER ========== */
213
+ EbayRateLimitsVisualizer.propTypes = {
214
+ token: PropTypes.string,
215
+ apiProps: PropTypes.object,
216
+ };
217
+ export function EbayRateLimitsVisualizer(props) {
218
+ const config = usePixelatedConfig();
219
+ const apiProps = {
220
+ ...(config?.ebay || {}),
221
+ ...props.apiProps
222
+ };
223
+ const [token, setToken] = useState(props.token || '');
224
+ const [data, setData] = useState(null);
225
+ const [loading, setLoading] = useState(false);
226
+ const [fetchingToken, setFetchingToken] = useState(false);
227
+ const [error, setError] = useState(null);
228
+ const fetchToken = async () => {
229
+ setFetchingToken(true);
230
+ setError(null);
231
+ try {
232
+ const newToken = await getEbayAppToken({ apiProps });
233
+ if (newToken) {
234
+ setToken(newToken);
235
+ return newToken;
236
+ }
237
+ else {
238
+ setError('Failed to fetch eBay token. Check your appId and appCertId.');
239
+ }
240
+ }
241
+ catch (e) {
242
+ setError('Token fetch error: ' + e.message);
243
+ }
244
+ finally {
245
+ setFetchingToken(false);
246
+ }
247
+ };
248
+ // Auto-fetch token on mount if credentials are available
249
+ useEffect(() => {
250
+ if (!token && apiProps.appId && apiProps.appCertId) {
251
+ fetchToken();
252
+ }
253
+ }, []);
254
+ const fetchData = async () => {
255
+ setLoading(true);
256
+ setError(null);
257
+ try {
258
+ const result = await getEbayRateLimits({
259
+ token: token,
260
+ apiProps: apiProps
261
+ });
262
+ setData(result);
263
+ }
264
+ catch (e) {
265
+ setError(e.message || 'Failed to fetch data');
266
+ }
267
+ finally {
268
+ setLoading(false);
269
+ }
270
+ };
271
+ const showMockData = () => {
272
+ setData({
273
+ rate_limit: {
274
+ apiContext: "Buy",
275
+ apiName: "Browse",
276
+ apiVersion: "v1",
277
+ resources: [
278
+ {
279
+ resourceName: "item_summary",
280
+ methods: [
281
+ {
282
+ methodName: "search",
283
+ quotaTotal: 5000,
284
+ quotaRemaining: 4950,
285
+ quotaResets: "2026-01-10T00:00:00.000Z"
286
+ }
287
+ ]
288
+ }
289
+ ]
290
+ },
291
+ user_rate_limit: {
292
+ userContext: "Individual",
293
+ resources: [
294
+ {
295
+ resourceName: "item",
296
+ methods: [
297
+ {
298
+ methodName: "get",
299
+ quotaTotal: 1000,
300
+ quotaRemaining: 990,
301
+ quotaResets: "2026-01-10T00:00:00.000Z"
302
+ }
303
+ ]
304
+ }
305
+ ]
306
+ }
307
+ });
308
+ };
309
+ // Check for API-level errors in the returned data
310
+ const hasTokenError = data?.rate_limit?.errors?.[0]?.message === "Invalid access token" ||
311
+ data?.user_rate_limit?.errors?.[0]?.message === "Invalid access token";
312
+ return (_jsxs("div", { style: { padding: '20px', fontFamily: 'sans-serif', border: '1px solid #ddd', borderRadius: '8px' }, children: [_jsx("h3", { children: "Ebay Rate Limits Data Visualizer" }), _jsxs("div", { style: { marginBottom: '20px', display: 'flex', flexDirection: 'column', gap: '10px' }, children: [_jsxs("div", { style: { display: 'flex', gap: '10px', alignItems: 'center' }, children: [_jsx("label", { htmlFor: "ebay-token", children: _jsx("b", { children: "Token:" }) }), _jsx("input", { id: "ebay-token", type: "text", value: token, onChange: (e) => setToken(e.target.value), placeholder: "Paste eBay token here", style: { flexGrow: 1, padding: '5px', fontFamily: 'monospace' } }), _jsx("button", { onClick: fetchToken, disabled: fetchingToken || !apiProps.appId, children: fetchingToken ? 'Fetching Token...' : 'Auto-Fetch Token' })] }), _jsxs("div", { style: { display: 'flex', gap: '10px' }, children: [_jsx("button", { onClick: fetchData, disabled: loading || !token, style: { padding: '8px 16px', cursor: 'pointer', background: '#0070f3', color: 'white', border: 'none', borderRadius: '4px' }, children: loading ? 'Fetching Limits...' : 'Fetch Rate Limits' }), _jsx("button", { onClick: showMockData, style: { padding: '8px 16px', cursor: 'pointer', border: '1px solid #ccc', borderRadius: '4px', background: 'white' }, children: "Load Sample Structure" })] })] }), error && (_jsxs("div", { style: { color: '#d00', background: '#fff5f5', padding: '10px', borderRadius: '4px', marginBottom: '10px', border: '1px solid #feb2b2' }, children: [_jsx("b", { children: "Error:" }), " ", error] })), hasTokenError && (_jsxs("div", { style: { color: '#c53030', background: '#fff5f5', padding: '10px', borderRadius: '4px', marginBottom: '10px', border: '1px solid #feb2b2' }, children: ["\uD83D\uDEA8 ", _jsx("b", { children: "Authentication Error:" }), " Your eBay access token is invalid or expired. Use \"Auto-Fetch Token\" if credentials are set, or paste a new one."] })), data ? (_jsxs("div", { style: { marginTop: '20px' }, children: [_jsxs("div", { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' }, children: [_jsx("h4", { children: "Response Data:" }), _jsx("button", { onClick: () => setData(null), style: { padding: '4px 8px', fontSize: '12px' }, children: "Clear" })] }), _jsx("pre", { style: { background: '#f8f9fa', padding: '15px', borderRadius: '5px', overflow: 'auto', border: '1px solid #e9ecef', fontSize: '13px' }, children: JSON.stringify(data, null, 2) })] })) : (_jsx("div", { style: { color: '#666', fontStyle: 'italic', marginTop: '20px' }, children: "No rate limit data loaded yet." }))] }));
313
+ }