@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.
- package/README.md +7 -0
- package/dist/components/admin/site-health/site-health-utils.js +22 -0
- package/dist/components/config/config.example.js +2 -0
- package/dist/components/config/config.types.js +1 -3
- package/dist/components/config/config.utils.js +13 -5
- package/dist/components/general/cache-manager.js +124 -0
- package/dist/components/general/googlemap.js +5 -2
- package/dist/components/general/sitemap.js +3 -1
- package/dist/components/shoppingcart/ebay.components.js +9 -7
- package/dist/components/shoppingcart/ebay.functions.js +94 -9
- package/dist/components/shoppingcart/shoppingcart.components.js +4 -2
- package/dist/config/pixelated.config.json +3 -1
- package/dist/index.js +1 -0
- package/dist/scripts/release.sh +2 -2
- package/dist/test/config.mock.js +13 -0
- package/dist/test/setup.js +46 -0
- package/dist/test/test-utils.js +23 -0
- package/dist/types/components/admin/site-health/site-health-utils.d.ts +1 -1
- package/dist/types/components/admin/site-health/site-health-utils.d.ts.map +1 -1
- package/dist/types/components/config/config.example.d.ts.map +1 -1
- package/dist/types/components/config/config.types.d.ts +2 -0
- package/dist/types/components/config/config.types.d.ts.map +1 -1
- package/dist/types/components/config/config.utils.d.ts.map +1 -1
- package/dist/types/components/general/cache-manager.d.ts +45 -0
- package/dist/types/components/general/cache-manager.d.ts.map +1 -0
- package/dist/types/components/general/googlemap.d.ts +1 -1
- package/dist/types/components/general/googlemap.d.ts.map +1 -1
- package/dist/types/components/general/sitemap.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/ebay.components.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/ebay.functions.d.ts +15 -0
- package/dist/types/components/shoppingcart/ebay.functions.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/shoppingcart.components.d.ts +1 -1
- package/dist/types/components/shoppingcart/shoppingcart.components.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/stories/callout/callout.many.stories.d.ts +0 -1
- package/dist/types/stories/callout/callout.many.stories.d.ts.map +1 -1
- package/dist/types/stories/callout/callout.stories.d.ts +0 -1
- package/dist/types/stories/callout/callout.stories.d.ts.map +1 -1
- package/dist/types/stories/carousel/carousel-hero.stories.d.ts +1 -9
- package/dist/types/stories/carousel/carousel-hero.stories.d.ts.map +1 -1
- package/dist/types/stories/carousel/carousel-reviews.stories.d.ts +0 -1
- package/dist/types/stories/carousel/carousel-reviews.stories.d.ts.map +1 -1
- package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts +1 -9
- package/dist/types/stories/carousel/carousel-workportfolio.stories.d.ts.map +1 -1
- package/dist/types/stories/carousel/carousel.stories.d.ts +1 -9
- package/dist/types/stories/carousel/carousel.stories.d.ts.map +1 -1
- package/dist/types/stories/carousel/tiles.stories.d.ts +0 -1
- package/dist/types/stories/carousel/tiles.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/contentful.item.stories.d.ts +0 -9
- package/dist/types/stories/cms/contentful.item.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/contentful.items.stories.d.ts +1 -11
- package/dist/types/stories/cms/contentful.items.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/contentful.stories.d.ts +0 -1
- package/dist/types/stories/cms/contentful.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/google.reviews.stories.d.ts +0 -1
- package/dist/types/stories/cms/google.reviews.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/gravatar.stories.d.ts +0 -1
- package/dist/types/stories/cms/gravatar.stories.d.ts.map +1 -1
- package/dist/types/stories/cms/wordpress.stories.d.ts +6 -2
- package/dist/types/stories/cms/wordpress.stories.d.ts.map +1 -1
- package/dist/types/stories/general/accordion.stories.d.ts +3 -2
- package/dist/types/stories/general/accordion.stories.d.ts.map +1 -1
- package/dist/types/stories/general/headers.stories.d.ts.map +1 -1
- package/dist/types/stories/general/microinteractions.stories.d.ts +0 -1
- package/dist/types/stories/general/microinteractions.stories.d.ts.map +1 -1
- package/dist/types/stories/general/modal.stories.d.ts +0 -1
- package/dist/types/stories/general/modal.stories.d.ts.map +1 -1
- package/dist/types/stories/general/smartimage.stories.d.ts.map +1 -1
- package/dist/types/stories/general/splitscroll.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.404.stories.d.ts +0 -1
- package/dist/types/stories/seo/seo.404.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts +1 -3
- package/dist/types/stories/seo/seo.googleanalytics.stories.d.ts.map +1 -1
- package/dist/types/stories/seo/seo.schema.stories.d.ts +0 -1
- package/dist/types/stories/seo/seo.schema.stories.d.ts.map +1 -1
- package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts +1 -12
- package/dist/types/stories/shoppingcart/shoppingcart.ebay.item.stories.d.ts.map +1 -1
- package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts +1 -12
- package/dist/types/stories/shoppingcart/shoppingcart.ebay.items.stories.d.ts.map +1 -1
- package/dist/types/stories/shoppingcart/shoppingcart.stories.d.ts.map +1 -1
- package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts +12 -34
- package/dist/types/stories/sitebuilder/compoundfontselector.stories.d.ts.map +1 -1
- package/dist/types/stories/sitebuilder/form-engine.stories.d.ts +0 -1
- package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -1
- package/dist/types/stories/sitebuilder/pageengine.stories.d.ts +0 -1
- package/dist/types/stories/sitebuilder/pageengine.stories.d.ts.map +1 -1
- package/dist/types/stories/structured/markdown.stories.d.ts +0 -1
- package/dist/types/stories/structured/markdown.stories.d.ts.map +1 -1
- package/dist/types/stories/structured/recipe.stories.d.ts +0 -1
- package/dist/types/stories/structured/recipe.stories.d.ts.map +1 -1
- package/dist/types/stories/structured/resume.stories.d.ts +0 -1
- package/dist/types/stories/structured/resume.stories.d.ts.map +1 -1
- package/dist/types/stories/structured/socialcard.stories.d.ts +0 -1
- package/dist/types/stories/structured/socialcard.stories.d.ts.map +1 -1
- package/dist/types/stories/structured/timeline.stories.d.ts +0 -1
- package/dist/types/stories/structured/timeline.stories.d.ts.map +1 -1
- package/dist/types/test/config.mock.d.ts +11 -0
- package/dist/types/test/config.mock.d.ts.map +1 -0
- package/dist/types/test/setup.d.ts.map +1 -0
- package/dist/types/test/test-utils.d.ts +84 -0
- package/dist/types/test/test-utils.d.ts.map +1 -0
- package/dist/types/tests/cache-manager.test.d.ts +2 -0
- package/dist/types/tests/cache-manager.test.d.ts.map +1 -0
- package/dist/types/tests/config-core.test.d.ts +2 -0
- package/dist/types/tests/config-core.test.d.ts.map +1 -0
- package/dist/types/tests/ebay-functions.test.d.ts +2 -0
- package/dist/types/tests/ebay-functions.test.d.ts.map +1 -0
- package/dist/types/tests/site-health-utils.test.d.ts +2 -0
- package/dist/types/tests/site-health-utils.test.d.ts.map +1 -0
- package/package.json +4 -4
- package/dist/types/tests/setup.d.ts.map +0 -1
- /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',
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
35
|
+
// At the top level (serviceName is undefined), k is the service name
|
|
30
36
|
const currentService = serviceName || k;
|
|
31
|
-
if
|
|
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
|
|
12
|
+
api_key: PropTypes.string,
|
|
12
13
|
parameters: PropTypes.string,
|
|
13
14
|
};
|
|
14
15
|
export function GoogleMaps(props) {
|
|
15
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
|
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(
|
|
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"
|
|
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
|
-
|
|
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(
|
|
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:",
|
|
128
|
-
|
|
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(
|
|
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:",
|
|
162
|
-
|
|
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(
|
|
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
|
|
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:
|
|
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
|
-
|
|
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';
|
package/dist/scripts/release.sh
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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,
|
|
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"}
|