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