@pixelated-tech/components 3.14.4 → 3.15.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/site-health-core-web-vitals.integration.js +21 -8
- package/dist/components/admin/site-health/site-health-github.integration.js +6 -6
- package/dist/components/admin/site-health/site-health-on-site-seo.integration.js +36 -16
- package/dist/components/admin/site-health/site-health-template.js +10 -6
- package/dist/components/config/config.types.js +12 -0
- package/dist/components/general/markdown.js +35 -0
- package/dist/components/general/nerdjoke.js +2 -4
- package/dist/components/general/proxy-handler.js +2 -2
- package/dist/components/general/sitemap.js +2 -4
- package/dist/components/general/smartfetch.js +211 -0
- package/dist/components/general/tiles.js +1 -1
- package/dist/components/general/urlbuilder.js +74 -0
- package/dist/components/integrations/contentful.delivery.js +24 -20
- package/dist/components/integrations/contentful.management.js +188 -151
- package/dist/components/integrations/flickr.js +15 -22
- package/dist/components/integrations/gemini-api.client.js +22 -21
- package/dist/components/integrations/gemini-api.server.js +50 -46
- package/dist/components/integrations/google.reviews.functions.js +19 -5
- package/dist/components/integrations/googleplaces.js +33 -9
- package/dist/components/integrations/gravatar.functions.js +15 -7
- package/dist/components/integrations/hubspot.components.js +8 -10
- package/dist/components/integrations/instagram.functions.js +9 -4
- package/dist/components/integrations/lipsum.js +6 -10
- package/dist/components/integrations/loremipsum.js +21 -21
- package/dist/components/integrations/socialcard.js +14 -8
- package/dist/components/integrations/spotify.functions.js +7 -4
- package/dist/components/integrations/wordpress.functions.js +17 -19
- package/dist/components/integrations/yelp.js +6 -7
- package/dist/components/shoppingcart/ebay.functions.js +69 -53
- package/dist/components/shoppingcart/shoppingcart.components.js +1 -1
- package/dist/components/sitebuilder/config/google-fonts.js +13 -6
- package/dist/components/sitebuilder/form/formbuilder.js +1 -1
- package/dist/components/sitebuilder/form/formengine.js +37 -10
- package/dist/components/sitebuilder/form/formsubmit.js +205 -0
- package/dist/components/sitebuilder/page/components/SaveLoadSection.js +24 -12
- package/dist/config/pixelated.config.json.enc +1 -1
- package/dist/data/form.json +7 -0
- package/dist/index.js +4 -2
- package/dist/index.server.js +3 -1
- package/dist/scripts/pixelated-eslint-plugin.js +51 -0
- package/dist/types/components/admin/site-health/site-health-core-web-vitals.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-github.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-on-site-seo.integration.d.ts.map +1 -1
- package/dist/types/components/admin/site-health/site-health-template.d.ts.map +1 -1
- package/dist/types/components/config/config.types.d.ts +11 -0
- package/dist/types/components/config/config.types.d.ts.map +1 -1
- package/dist/types/components/general/markdown.d.ts +12 -0
- package/dist/types/components/general/markdown.d.ts.map +1 -1
- package/dist/types/components/general/nerdjoke.d.ts.map +1 -1
- package/dist/types/components/general/proxy-handler.d.ts.map +1 -1
- package/dist/types/components/general/sitemap.d.ts.map +1 -1
- package/dist/types/components/general/smartfetch.d.ts +85 -0
- package/dist/types/components/general/smartfetch.d.ts.map +1 -0
- package/dist/types/components/general/tiles.d.ts.map +1 -1
- package/dist/types/components/general/urlbuilder.d.ts +64 -0
- package/dist/types/components/general/urlbuilder.d.ts.map +1 -0
- package/dist/types/components/integrations/contentful.delivery.d.ts.map +1 -1
- package/dist/types/components/integrations/contentful.management.d.ts.map +1 -1
- package/dist/types/components/integrations/flickr.d.ts.map +1 -1
- package/dist/types/components/integrations/gemini-api.client.d.ts.map +1 -1
- package/dist/types/components/integrations/gemini-api.server.d.ts +1 -1
- package/dist/types/components/integrations/gemini-api.server.d.ts.map +1 -1
- package/dist/types/components/integrations/google.reviews.functions.d.ts.map +1 -1
- package/dist/types/components/integrations/googleplaces.d.ts.map +1 -1
- package/dist/types/components/integrations/gravatar.functions.d.ts.map +1 -1
- package/dist/types/components/integrations/hubspot.components.d.ts.map +1 -1
- package/dist/types/components/integrations/instagram.functions.d.ts.map +1 -1
- package/dist/types/components/integrations/lipsum.d.ts.map +1 -1
- package/dist/types/components/integrations/loremipsum.d.ts.map +1 -1
- package/dist/types/components/integrations/socialcard.d.ts.map +1 -1
- package/dist/types/components/integrations/spotify.functions.d.ts.map +1 -1
- package/dist/types/components/integrations/wordpress.functions.d.ts.map +1 -1
- package/dist/types/components/integrations/yelp.d.ts.map +1 -1
- package/dist/types/components/shoppingcart/ebay.functions.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/config/google-fonts.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/formengine.d.ts +4 -4
- package/dist/types/components/sitebuilder/form/formengine.d.ts.map +1 -1
- package/dist/types/components/sitebuilder/form/{formutils.d.ts → formengineutilities.d.ts} +1 -1
- package/dist/types/components/sitebuilder/form/formengineutilities.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/form/formsubmit.d.ts +70 -0
- package/dist/types/components/sitebuilder/form/formsubmit.d.ts.map +1 -0
- package/dist/types/components/sitebuilder/page/components/SaveLoadSection.d.ts.map +1 -1
- package/dist/types/index.d.ts +4 -2
- package/dist/types/index.server.d.ts +3 -1
- package/dist/types/scripts/pixelated-eslint-plugin.d.ts +21 -0
- package/dist/types/stories/admin/contentful-migration.stories.d.ts +43 -0
- package/dist/types/stories/admin/contentful-migration.stories.d.ts.map +1 -1
- package/dist/types/stories/general/text-generation.stories.d.ts +116 -0
- package/dist/types/stories/general/text-generation.stories.d.ts.map +1 -0
- package/dist/types/stories/integrations/google.reviews.stories.d.ts +52 -0
- package/dist/types/stories/integrations/google.reviews.stories.d.ts.map +1 -1
- package/dist/types/stories/integrations/gravatar.stories.d.ts.map +1 -1
- package/dist/types/stories/integrations/instagram.stories.d.ts +38 -0
- package/dist/types/stories/integrations/instagram.stories.d.ts.map +1 -1
- package/dist/types/stories/sitebuilder/form-engine.stories.d.ts +13 -7
- package/dist/types/stories/sitebuilder/form-engine.stories.d.ts.map +1 -1
- package/dist/types/stories/sitebuilder/form.honeypot.stories.d.ts +0 -19
- package/dist/types/stories/sitebuilder/form.honeypot.stories.d.ts.map +1 -1
- 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/formengineutilities.test.d.ts +2 -0
- package/dist/types/tests/formengineutilities.test.d.ts.map +1 -0
- package/dist/types/tests/google-apis.test.d.ts +2 -0
- package/dist/types/tests/google-apis.test.d.ts.map +1 -0
- package/dist/types/tests/google-fonts.test.d.ts +2 -0
- package/dist/types/tests/google-fonts.test.d.ts.map +1 -0
- package/dist/types/tests/site-health-core-web-vitals.test.d.ts +2 -0
- package/dist/types/tests/site-health-core-web-vitals.test.d.ts.map +1 -0
- package/dist/types/tests/smartfetch.test.d.ts +2 -0
- package/dist/types/tests/smartfetch.test.d.ts.map +1 -0
- package/dist/types/tests/social-media-apis.test.d.ts +7 -0
- package/dist/types/tests/social-media-apis.test.d.ts.map +1 -0
- package/dist/types/tests/specialized-apis.test.d.ts +7 -0
- package/dist/types/tests/specialized-apis.test.d.ts.map +1 -0
- package/dist/types/tests/urlbuilder.test.d.ts +2 -0
- package/dist/types/tests/urlbuilder.test.d.ts.map +1 -0
- package/dist/types/tests/useFormSubmit.test.d.ts +2 -0
- package/dist/types/tests/useFormSubmit.test.d.ts.map +1 -0
- package/package.json +6 -6
- package/dist/components/sitebuilder/form/formemailer.js +0 -119
- package/dist/types/components/sitebuilder/form/formemailer.d.ts +0 -3
- package/dist/types/components/sitebuilder/form/formemailer.d.ts.map +0 -1
- package/dist/types/components/sitebuilder/form/formutils.d.ts.map +0 -1
- package/dist/types/stories/integrations/lipsum.stories.d.ts +0 -38
- package/dist/types/stories/integrations/lipsum.stories.d.ts.map +0 -1
- package/dist/types/stories/integrations/loremipsum.stories.d.ts +0 -46
- package/dist/types/stories/integrations/loremipsum.stories.d.ts.map +0 -1
- package/dist/types/tests/formemailer.honeypot.test.d.ts +0 -2
- package/dist/types/tests/formemailer.honeypot.test.d.ts.map +0 -1
- /package/dist/components/sitebuilder/form/{formutils.js → formengineutilities.js} +0 -0
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import PropTypes from "prop-types";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
// const wpSite = "blog.pixelated.tech";
|
|
2
|
+
import { smartFetch } from '../general/smartfetch';
|
|
3
|
+
import { buildUrl } from '../general/urlbuilder';
|
|
5
4
|
const wpApiURL = "https://public-api.wordpress.com/rest/v1/sites/";
|
|
6
5
|
const wpCategoriesPath = "/categories";
|
|
7
6
|
/**
|
|
@@ -28,17 +27,16 @@ export async function getWordPressItems(props) {
|
|
|
28
27
|
while (true) {
|
|
29
28
|
const remaining = requested ? Math.max(requested - posts.length, 0) : 100;
|
|
30
29
|
const number = Math.min(remaining || 100, 100);
|
|
31
|
-
const wpPostsURL =
|
|
30
|
+
const wpPostsURL = buildUrl({
|
|
31
|
+
baseUrl: baseURL,
|
|
32
|
+
pathSegments: [props.site, 'posts'],
|
|
33
|
+
params: { number, page },
|
|
34
|
+
});
|
|
32
35
|
try {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// invalidated independently of the page cache.
|
|
37
|
-
cache: 'force-cache',
|
|
38
|
-
next: { revalidate: 60 * 60 * 24 * 7, tags: [tag] }, // revalidate once per week
|
|
36
|
+
const data = await smartFetch(wpPostsURL, {
|
|
37
|
+
nextCache: { revalidate: 60 * 60 * 24 * 7 }, // revalidate once per week
|
|
38
|
+
timeout: 30000,
|
|
39
39
|
});
|
|
40
|
-
// -> "HIT" or "MISS" (or "REVALIDATED" etc.)
|
|
41
|
-
const data = await response.json();
|
|
42
40
|
const batch = Array.isArray(data.posts) ? data.posts : [];
|
|
43
41
|
if (batch.length === 0) {
|
|
44
42
|
break; // no more posts
|
|
@@ -88,12 +86,13 @@ export async function getWordPressItems(props) {
|
|
|
88
86
|
*/
|
|
89
87
|
export async function getWordPressLastModified(props) {
|
|
90
88
|
const { baseURL = wpApiURL } = props;
|
|
91
|
-
const url =
|
|
89
|
+
const url = buildUrl({
|
|
90
|
+
baseUrl: baseURL,
|
|
91
|
+
pathSegments: [props.site, 'posts'],
|
|
92
|
+
params: { number: 1, fields: 'modified' },
|
|
93
|
+
});
|
|
92
94
|
try {
|
|
93
|
-
const
|
|
94
|
-
if (!res.ok)
|
|
95
|
-
return null;
|
|
96
|
-
const data = await res.json();
|
|
95
|
+
const data = await smartFetch(url, {});
|
|
97
96
|
const modified = Array.isArray(data.posts) && data.posts[0]?.modified;
|
|
98
97
|
return modified || null;
|
|
99
98
|
}
|
|
@@ -169,8 +168,7 @@ export async function getWordPressCategories(props) {
|
|
|
169
168
|
const wpCategoriesURL = baseURL + props.site + wpCategoriesPath;
|
|
170
169
|
const categories = [];
|
|
171
170
|
try {
|
|
172
|
-
const
|
|
173
|
-
const data = await response.json();
|
|
171
|
+
const data = await smartFetch(wpCategoriesURL, {});
|
|
174
172
|
// Check for total pages on the first page
|
|
175
173
|
const myCategories = data.categories.map((category) => (category.name));
|
|
176
174
|
categories.push(...myCategories);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useState, useEffect } from "react";
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
+
import { smartFetch } from '../general/smartfetch';
|
|
5
6
|
/*
|
|
6
7
|
NOTE : development has stopped for this component
|
|
7
8
|
as Yelp Base API Access costs $229 per month.
|
|
@@ -34,15 +35,13 @@ export function YelpReviews(props) {
|
|
|
34
35
|
const apiKey = 'YOUR_YELP_API_KEY';
|
|
35
36
|
const url = `https://cors-anywhere.herokuapp.com/https://api.yelp.com/v3/businesses/${props.businessID}/reviews`;
|
|
36
37
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
const data = await smartFetch(url, {
|
|
39
|
+
requestInit: {
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${apiKey}`,
|
|
42
|
+
},
|
|
40
43
|
},
|
|
41
44
|
});
|
|
42
|
-
if (!response.ok) {
|
|
43
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
44
|
-
}
|
|
45
|
-
const data = await response.json();
|
|
46
45
|
setReviews(data.reviews);
|
|
47
46
|
setLoading(false);
|
|
48
47
|
}
|
|
@@ -2,6 +2,8 @@ import PropTypes from "prop-types";
|
|
|
2
2
|
import { getCloudinaryRemoteFetchURL as getImg } from "../integrations/cloudinary";
|
|
3
3
|
import { CacheManager } from "../general/cache-manager";
|
|
4
4
|
import { getDomain } from "../general/utilities";
|
|
5
|
+
import { smartFetch } from "../general/smartfetch";
|
|
6
|
+
import { buildUrl } from "../general/urlbuilder";
|
|
5
7
|
const debug = false;
|
|
6
8
|
// Initialize eBay Cache (Session storage, 1 hour TTL) — isolated per domain
|
|
7
9
|
const ebayCache = new CacheManager({
|
|
@@ -118,21 +120,19 @@ export function getEbayAppToken(props) {
|
|
|
118
120
|
if (debug)
|
|
119
121
|
console.log("Fetching Token");
|
|
120
122
|
try {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
123
|
+
const data = await smartFetch(apiProps.proxyURL + apiProps.baseTokenURL, {
|
|
124
|
+
requestInit: {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
headers: {
|
|
127
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
128
|
+
'Authorization': 'Basic ' + btoa(`${apiProps.appId}:${apiProps.appCertId}`) // Base64 encoded
|
|
129
|
+
},
|
|
130
|
+
body: new URLSearchParams({
|
|
131
|
+
grant_type: 'client_credentials',
|
|
132
|
+
scope: apiProps.tokenScope
|
|
133
|
+
})
|
|
134
|
+
}
|
|
131
135
|
});
|
|
132
|
-
if (!response.ok) {
|
|
133
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
134
|
-
}
|
|
135
|
-
const data = await response.json();
|
|
136
136
|
const accessToken = data.access_token;
|
|
137
137
|
if (debug)
|
|
138
138
|
console.log("Fetched eBay Access Token:", accessToken);
|
|
@@ -172,19 +172,20 @@ export function getEbayBrowseSearch(props) {
|
|
|
172
172
|
if (debug)
|
|
173
173
|
console.log("Fetching ebay API Browse Search Data");
|
|
174
174
|
try {
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
175
|
+
const data = await smartFetch(buildUrl({
|
|
176
|
+
baseUrl: fullURL,
|
|
177
|
+
proxyUrl: apiProps.proxyURL,
|
|
178
|
+
}), {
|
|
179
|
+
requestInit: {
|
|
180
|
+
method: 'GET',
|
|
181
|
+
headers: {
|
|
182
|
+
'Authorization': 'Bearer ' + token,
|
|
183
|
+
'X-EBAY-C-MARKETPLACE-ID': 'EBAY_US',
|
|
184
|
+
'X-EBAY-C-ENDUSERCTX': 'affiliateCampaignId=<ePNCampaignId>,affiliateReferenceId=<referenceId>',
|
|
185
|
+
'X-EBAY-SOA-SECURITY-APPNAME': 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe',
|
|
186
|
+
}
|
|
182
187
|
}
|
|
183
188
|
});
|
|
184
|
-
if (!response.ok) {
|
|
185
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
186
|
-
}
|
|
187
|
-
const data = await response.json();
|
|
188
189
|
if (debug)
|
|
189
190
|
console.log("Fetched eBay API Browse Search Data:", data);
|
|
190
191
|
// Store in Cache
|
|
@@ -225,19 +226,20 @@ export function getEbayBrowseItem(props) {
|
|
|
225
226
|
if (debug)
|
|
226
227
|
console.log("Fetching ebay API Browse Item Data");
|
|
227
228
|
try {
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
229
|
+
const data = await smartFetch(buildUrl({
|
|
230
|
+
baseUrl: fullURL,
|
|
231
|
+
proxyUrl: apiProps.proxyURL,
|
|
232
|
+
}), {
|
|
233
|
+
requestInit: {
|
|
234
|
+
method: 'GET',
|
|
235
|
+
headers: {
|
|
236
|
+
'Authorization': 'Bearer ' + token,
|
|
237
|
+
'X-EBAY-C-MARKETPLACE-ID': 'EBAY_US',
|
|
238
|
+
'X-EBAY-C-ENDUSERCTX': 'affiliateCampaignId=<ePNCampaignId>,affiliateReferenceId=<referenceId>',
|
|
239
|
+
'X-EBAY-SOA-SECURITY-APPNAME': 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe',
|
|
240
|
+
}
|
|
235
241
|
}
|
|
236
242
|
});
|
|
237
|
-
if (!response.ok) {
|
|
238
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
239
|
-
}
|
|
240
|
-
const data = await response.json();
|
|
241
243
|
if (debug)
|
|
242
244
|
console.log("Fetched eBay Item Data:", data);
|
|
243
245
|
// Store in Cache
|
|
@@ -269,14 +271,30 @@ export function getEbayRateLimits(props) {
|
|
|
269
271
|
if (debug)
|
|
270
272
|
console.log("Fetching all eBay API Rate Limits");
|
|
271
273
|
try {
|
|
274
|
+
const rateLimitUrl = buildUrl({
|
|
275
|
+
baseUrl: apiProps.baseAnalyticsURL,
|
|
276
|
+
pathSegments: ['rate_limit'],
|
|
277
|
+
proxyUrl: apiProps.proxyURL,
|
|
278
|
+
});
|
|
279
|
+
const userRateLimitUrl = buildUrl({
|
|
280
|
+
baseUrl: apiProps.baseAnalyticsURL,
|
|
281
|
+
pathSegments: ['user_rate_limit'],
|
|
282
|
+
proxyUrl: apiProps.proxyURL,
|
|
283
|
+
});
|
|
272
284
|
const [rateLimitRes, userRateLimitRes] = await Promise.all([
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
285
|
+
smartFetch(rateLimitUrl, {
|
|
286
|
+
responseType: 'ok',
|
|
287
|
+
requestInit: {
|
|
288
|
+
method: 'GET',
|
|
289
|
+
headers: { 'Authorization': 'Bearer ' + token }
|
|
290
|
+
}
|
|
276
291
|
}),
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
292
|
+
smartFetch(userRateLimitUrl, {
|
|
293
|
+
responseType: 'ok',
|
|
294
|
+
requestInit: {
|
|
295
|
+
method: 'GET',
|
|
296
|
+
headers: { 'Authorization': 'Bearer ' + token }
|
|
297
|
+
}
|
|
280
298
|
})
|
|
281
299
|
]);
|
|
282
300
|
if (!rateLimitRes.ok || !userRateLimitRes.ok) {
|
|
@@ -371,19 +389,17 @@ export function getEbayItemsSearch(props) {
|
|
|
371
389
|
if (debug)
|
|
372
390
|
console.log("Fetching ebay API Items Search Data");
|
|
373
391
|
try {
|
|
374
|
-
const
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
392
|
+
const data = await smartFetch(apiProps.proxyURL + encodeURIComponent(fullURL), {
|
|
393
|
+
requestInit: {
|
|
394
|
+
method: 'GET',
|
|
395
|
+
headers: {
|
|
396
|
+
'Authorization': 'Bearer ' + token,
|
|
397
|
+
'X-EBAY-C-MARKETPLACE-ID': 'EBAY_US',
|
|
398
|
+
'X-EBAY-C-ENDUSERCTX': 'affiliateCampaignId=<ePNCampaignId>,affiliateReferenceId=<referenceId>',
|
|
399
|
+
'X-EBAY-SOA-SECURITY-APPNAME': 'BrianWha-Pixelate-PRD-1fb4458de-1a8431fe',
|
|
400
|
+
}
|
|
381
401
|
}
|
|
382
402
|
});
|
|
383
|
-
if (!response.ok) {
|
|
384
|
-
throw new Error(`HTTP error! status: ${response.status}`);
|
|
385
|
-
}
|
|
386
|
-
const data = await response.json();
|
|
387
403
|
// Store in Cache
|
|
388
404
|
ebayCache.set(cacheKey, data);
|
|
389
405
|
return data;
|
|
@@ -6,7 +6,7 @@ import { PayPal } from "./paypal";
|
|
|
6
6
|
import { CalloutHeader } from "../general/callout";
|
|
7
7
|
import { FormEngine } from "../sitebuilder/form/formengine";
|
|
8
8
|
import { FormButton } from '../sitebuilder/form/formcomponents';
|
|
9
|
-
import { emailJSON } from "../sitebuilder/form/
|
|
9
|
+
import { emailJSON } from "../sitebuilder/form/formsubmit";
|
|
10
10
|
import '../sitebuilder/form/form.css';
|
|
11
11
|
import { MicroInteractions } from '../general/microinteractions';
|
|
12
12
|
import { Modal, handleModalOpen } from '../general/modal';
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
* Fetches and caches Google Fonts data for use in visual design forms
|
|
4
4
|
*/
|
|
5
5
|
import { getFullPixelatedConfig } from '../../config/config';
|
|
6
|
+
import { smartFetch } from '../../general/smartfetch';
|
|
7
|
+
import { buildUrl } from '../../general/urlbuilder';
|
|
6
8
|
// Cache for Google Fonts data
|
|
7
9
|
let fontsCache = null;
|
|
8
10
|
let cacheTimestamp = 0;
|
|
@@ -46,11 +48,12 @@ export async function fetchGoogleFonts() {
|
|
|
46
48
|
return [];
|
|
47
49
|
}
|
|
48
50
|
try {
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
51
|
+
const url = buildUrl({
|
|
52
|
+
baseUrl: 'https://www.googleapis.com',
|
|
53
|
+
pathSegments: ['webfonts', 'v1', 'webfonts'],
|
|
54
|
+
params: { key: apiKey, sort: 'popularity' }
|
|
55
|
+
});
|
|
56
|
+
const data = await smartFetch(url);
|
|
54
57
|
// Cache the results
|
|
55
58
|
fontsCache = data.items;
|
|
56
59
|
cacheTimestamp = Date.now();
|
|
@@ -99,7 +102,11 @@ export function generateGoogleFontsUrl(fonts) {
|
|
|
99
102
|
const fontParam = cleanFonts
|
|
100
103
|
.map(font => font.replace(/\s+/g, '+'))
|
|
101
104
|
.join('|');
|
|
102
|
-
return
|
|
105
|
+
return buildUrl({
|
|
106
|
+
baseUrl: 'https://fonts.googleapis.com',
|
|
107
|
+
pathSegments: ['css2'],
|
|
108
|
+
params: { family: fontParam, display: 'swap' }
|
|
109
|
+
});
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
105
112
|
* Generate HTML link tag for Google Fonts
|
|
@@ -3,7 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
3
3
|
import { useState } from 'react';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import * as FC from './formcomponents';
|
|
6
|
-
import { mapTypeToComponent, generateTypeField } from './
|
|
6
|
+
import { mapTypeToComponent, generateTypeField } from './formengineutilities';
|
|
7
7
|
import { FormEngine } from './formengine';
|
|
8
8
|
const debug = false;
|
|
9
9
|
/* ===== FORM BUILDER =====
|
|
@@ -4,6 +4,7 @@ import React from 'react';
|
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import { generateKey } from '../../general/utilities';
|
|
6
6
|
import { FormValidationProvider, useFormValidation } from './formvalidator';
|
|
7
|
+
import { FormSubmitWrapper, useFormSubmitContext } from './formsubmit';
|
|
7
8
|
import * as FC from './formcomponents';
|
|
8
9
|
import { CompoundFontSelector } from '../config/CompoundFontSelector';
|
|
9
10
|
import { FontSelector } from '../config/FontSelector';
|
|
@@ -19,26 +20,34 @@ Generate all the elements to display a form */
|
|
|
19
20
|
/**
|
|
20
21
|
* FormEngine — Render a form defined by a JSON `formData` schema. Converts `formData.fields` to React components and manages submission handling and validation.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
23
|
+
* Can be used in two ways:
|
|
24
|
+
* 1. JSON-driven (recommended): formData.properties defines form behavior. Wrap in FormSubmitWrapper or use alone (defaults apply)
|
|
25
|
+
* 2. Code-driven (backwards compatible): Pass name, id, onSubmitHandler as props
|
|
26
|
+
*
|
|
27
|
+
* @param {string} [props.name] - Form HTML name attribute. Falls back to formData.properties.name
|
|
28
|
+
* @param {string} [props.id] - Form HTML id attribute. Falls back to formData.properties.id
|
|
24
29
|
* @param {string} [props.method] - HTTP method for form submission (default: 'post').
|
|
25
30
|
* @param {function} [props.onSubmitHandler] - Optional submit handler invoked with the submit event.
|
|
26
|
-
* @param {object} [props.formData] - JSON schema describing fields (object with `fields` array).
|
|
31
|
+
* @param {object} [props.formData] - JSON schema describing fields (object with `fields` array and optional `properties` object).
|
|
27
32
|
*/
|
|
28
33
|
FormEngine.propTypes = {
|
|
29
|
-
/** Form name attribute */
|
|
34
|
+
/** Form name attribute. Falls back to formData.properties.name */
|
|
30
35
|
name: PropTypes.string,
|
|
31
|
-
/** Form id attribute */
|
|
36
|
+
/** Form id attribute. Falls back to formData.properties.id */
|
|
32
37
|
id: PropTypes.string,
|
|
33
38
|
/** HTTP method (e.g., 'post') */
|
|
34
39
|
method: PropTypes.string,
|
|
35
|
-
/** Submit handler called when the form is valid and submitted */
|
|
40
|
+
/** Submit handler called when the form is valid and submitted. Falls back to FormSubmitWrapper context if available */
|
|
36
41
|
onSubmitHandler: PropTypes.func,
|
|
37
|
-
/** JSON schema describing form fields */
|
|
42
|
+
/** JSON schema describing form fields and submission properties */
|
|
38
43
|
formData: PropTypes.object.isRequired
|
|
39
44
|
};
|
|
40
45
|
export function FormEngine(props) {
|
|
41
|
-
|
|
46
|
+
// Check if form should use internal FormSubmitWrapper
|
|
47
|
+
const hasJsonProperties = props.formData?.properties;
|
|
48
|
+
const hasNoSubmitHandler = !props.onSubmitHandler;
|
|
49
|
+
const shouldUseFormSubmitWrapper = hasJsonProperties && hasNoSubmitHandler;
|
|
50
|
+
return (_jsx(FormValidationProvider, { children: shouldUseFormSubmitWrapper ? (_jsx(FormSubmitWrapper, { ...props.formData.properties, children: _jsx(FormEngineInner, { ...props }) })) : (_jsx(FormEngineInner, { ...props })) }));
|
|
42
51
|
}
|
|
43
52
|
/**
|
|
44
53
|
* FormEngineInner — Internal implementation of the `FormEngine` that renders the generated fields and handles form submit/validation.
|
|
@@ -63,12 +72,28 @@ FormEngineInner.propTypes = {
|
|
|
63
72
|
};
|
|
64
73
|
function FormEngineInner(props) {
|
|
65
74
|
const { validateAllFields } = useFormValidation();
|
|
75
|
+
// Try to get handleSubmit from FormSubmitWrapper context
|
|
76
|
+
let contextSubmitHandler;
|
|
77
|
+
try {
|
|
78
|
+
const context = useFormSubmitContext();
|
|
79
|
+
contextSubmitHandler = context?.handleSubmit;
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
82
|
+
// Not inside FormSubmitWrapper - use provided handler
|
|
83
|
+
}
|
|
66
84
|
function generateFormProps(props) {
|
|
67
85
|
// GENERATE PROPS TO RENDER THE FORM CONTAINER, INTERNAL FUNCTION
|
|
68
86
|
if (debug)
|
|
69
87
|
console.log("Generating Form Props");
|
|
70
88
|
// Create a clean copy without non-serializable properties
|
|
71
89
|
const { formData, onSubmitHandler, ...formProps } = props;
|
|
90
|
+
// Extract name/id from properties with fallback to props
|
|
91
|
+
if (!formProps.name && formData?.properties?.name) {
|
|
92
|
+
formProps.name = formData.properties.name;
|
|
93
|
+
}
|
|
94
|
+
if (!formProps.id && formData?.properties?.id) {
|
|
95
|
+
formProps.id = formData.properties.id;
|
|
96
|
+
}
|
|
72
97
|
// Safety: default to POST to avoid accidental GET navigation (prevents query leakage)
|
|
73
98
|
if (!formProps.method)
|
|
74
99
|
formProps.method = 'post';
|
|
@@ -127,8 +152,10 @@ function FormEngineInner(props) {
|
|
|
127
152
|
// Form has validation errors, don't submit
|
|
128
153
|
return false;
|
|
129
154
|
}
|
|
130
|
-
|
|
131
|
-
|
|
155
|
+
// Try context handler first (from FormSubmitWrapper), then props handler
|
|
156
|
+
const handler = contextSubmitHandler || props.onSubmitHandler;
|
|
157
|
+
if (handler)
|
|
158
|
+
handler(event);
|
|
132
159
|
return true;
|
|
133
160
|
}
|
|
134
161
|
return (_jsx("form", { ...generateFormProps(props), onSubmit: (event) => { handleSubmit(event); }, suppressHydrationWarning: true, children: generateNewFields(props) }));
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useCallback, useState } from 'react';
|
|
4
|
+
import { createContext, useContext } from 'react';
|
|
5
|
+
import PropTypes from 'prop-types';
|
|
6
|
+
import { ToggleLoading } from '../../general/loading';
|
|
7
|
+
import { handleModalOpen } from '../../general/modal';
|
|
8
|
+
import { Loading } from '../../general/loading';
|
|
9
|
+
import { Modal } from '../../general/modal';
|
|
10
|
+
import { smartFetch } from '../../general/smartfetch';
|
|
11
|
+
export async function emailFormData(e, callback) {
|
|
12
|
+
const debug = false;
|
|
13
|
+
// const sendmail_api = "https://nlbqdrixmj.execute-api.us-east-2.amazonaws.com/default/sendmail";
|
|
14
|
+
const sendmail_api = "https://sendmail.pixelated.tech/default/sendmail";
|
|
15
|
+
const target = e.target;
|
|
16
|
+
const myform = document.getElementById(target.id);
|
|
17
|
+
e.preventDefault?.();
|
|
18
|
+
const myFormData = {};
|
|
19
|
+
const formData = new FormData(myform);
|
|
20
|
+
for (const [key, value] of formData.entries()) {
|
|
21
|
+
myFormData[key] = value;
|
|
22
|
+
}
|
|
23
|
+
const hpField = myform?.elements.namedItem('winnie');
|
|
24
|
+
const hpFieldVal = hpField?.value.toString();
|
|
25
|
+
// If either DOM or FormData indicate a filled honeypot, silently drop the submission.
|
|
26
|
+
if ((hpField && hpFieldVal.trim())) {
|
|
27
|
+
// Prevent native navigation where possible and mirror success path.
|
|
28
|
+
try {
|
|
29
|
+
e?.preventDefault?.();
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (debug)
|
|
33
|
+
console.debug('preventDefault failed in honeypot guard', err);
|
|
34
|
+
}
|
|
35
|
+
if (debug)
|
|
36
|
+
console.info('honeypot triggered — dropping submit');
|
|
37
|
+
callback?.(e);
|
|
38
|
+
return { success: true, response: null };
|
|
39
|
+
}
|
|
40
|
+
myFormData.Date = new Date().toLocaleDateString();
|
|
41
|
+
myFormData.Status = "Submitted";
|
|
42
|
+
const startTime = new Date().toISOString();
|
|
43
|
+
if (debug)
|
|
44
|
+
console.info('[emailFormData] submit-start', { sendmail_api, startTime, myFormData });
|
|
45
|
+
try {
|
|
46
|
+
const responseData = await smartFetch(sendmail_api, {
|
|
47
|
+
requestInit: {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
mode: 'cors',
|
|
50
|
+
headers: {
|
|
51
|
+
Accept: 'application/json',
|
|
52
|
+
'Content-Type': 'application/json',
|
|
53
|
+
},
|
|
54
|
+
body: JSON.stringify(myFormData),
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
const elapsedMs2 = new Date().getTime() - new Date(startTime).getTime();
|
|
58
|
+
if (debug)
|
|
59
|
+
console.info('[emailFormData] submit-finish', { sendmail_api, elapsedMs: elapsedMs2, responseData });
|
|
60
|
+
const parsed = responseData;
|
|
61
|
+
if (debug)
|
|
62
|
+
console.debug('emailFormData — submission data:', myFormData, 'response:', parsed);
|
|
63
|
+
callback?.(e);
|
|
64
|
+
return { success: true, response: parsed };
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
console.error('emailFormData error', err);
|
|
68
|
+
callback?.(e);
|
|
69
|
+
return { success: false, error: err };
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
export async function emailJSON(jsonData, callback) {
|
|
73
|
+
// const sendmail_api = "https://nlbqdrixmj.execute-api.us-east-2.amazonaws.com/default/sendmail";
|
|
74
|
+
const sendmail_api = "https://sendmail.pixelated.tech/default/sendmail";
|
|
75
|
+
const myJsonData = {};
|
|
76
|
+
for (const [key, value] of Object.entries(jsonData)) {
|
|
77
|
+
myJsonData[key] = value;
|
|
78
|
+
}
|
|
79
|
+
// MVP honeypot guard: check both the canonical id/key 'winnie' and the
|
|
80
|
+
// FormHoneypot default name 'website' to cover both DOM- and JSON-based calls.
|
|
81
|
+
if (myJsonData['winnie'] || myJsonData['website']) {
|
|
82
|
+
if (callback)
|
|
83
|
+
callback();
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
myJsonData.Date = new Date().toLocaleDateString();
|
|
87
|
+
myJsonData.Status = "Submitted";
|
|
88
|
+
try {
|
|
89
|
+
await smartFetch(sendmail_api, {
|
|
90
|
+
requestInit: {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
mode: 'cors',
|
|
93
|
+
headers: {
|
|
94
|
+
Accept: 'application/json',
|
|
95
|
+
'Content-Type': 'application/json',
|
|
96
|
+
},
|
|
97
|
+
body: JSON.stringify(myJsonData),
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
if (callback)
|
|
101
|
+
callback();
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
console.error('emailJSON error', err);
|
|
105
|
+
if (callback)
|
|
106
|
+
callback();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
const DEFAULT_MODAL_CONTENT = (_jsxs("div", { className: "centered", children: [_jsx("br", {}), _jsx("br", {}), "Thank you for your submission!", _jsx("br", {}), "We will get back to you as soon as we can.", _jsx("br", {}), _jsx("br", {}), _jsx("br", {})] }));
|
|
110
|
+
export function useFormSubmit(options = {}) {
|
|
111
|
+
const { toggleLoading = true, openModal = true, resetForm = true, modalContent = DEFAULT_MODAL_CONTENT, onStart, onSuccess, onError, onFinally, } = options;
|
|
112
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
113
|
+
const [submitError, setSubmitError] = useState(null);
|
|
114
|
+
const [submitResponse, setSubmitResponse] = useState(null);
|
|
115
|
+
const handleSubmit = useCallback(async (event) => {
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
const nativeEvent = event.nativeEvent;
|
|
118
|
+
// Start
|
|
119
|
+
if (toggleLoading)
|
|
120
|
+
ToggleLoading({ show: true });
|
|
121
|
+
onStart?.();
|
|
122
|
+
setIsSubmitting(true);
|
|
123
|
+
setSubmitError(null);
|
|
124
|
+
// Submit
|
|
125
|
+
const result = await emailFormData(nativeEvent);
|
|
126
|
+
setSubmitResponse(result.response);
|
|
127
|
+
// Handle result
|
|
128
|
+
if (result.success) {
|
|
129
|
+
// Run defaults first
|
|
130
|
+
if (openModal) {
|
|
131
|
+
// Use the Modal context if available, or try handleModalOpen
|
|
132
|
+
try {
|
|
133
|
+
handleModalOpen(nativeEvent);
|
|
134
|
+
}
|
|
135
|
+
catch (err) {
|
|
136
|
+
// Modal system not available in test/story context
|
|
137
|
+
if (typeof window !== 'undefined' && window.__DEV__) {
|
|
138
|
+
console.debug('handleModalOpen not available', err);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (resetForm) {
|
|
143
|
+
const form = nativeEvent.target;
|
|
144
|
+
if (form)
|
|
145
|
+
form.reset();
|
|
146
|
+
}
|
|
147
|
+
// Run custom callback
|
|
148
|
+
onSuccess?.(nativeEvent, result.response);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const error = result.error ?? new Error('Form submission failed');
|
|
152
|
+
setSubmitError(error);
|
|
153
|
+
// Run custom callback
|
|
154
|
+
onError?.(nativeEvent, error);
|
|
155
|
+
}
|
|
156
|
+
// Finalize
|
|
157
|
+
onFinally?.(nativeEvent);
|
|
158
|
+
if (toggleLoading)
|
|
159
|
+
ToggleLoading({ show: false });
|
|
160
|
+
setIsSubmitting(false);
|
|
161
|
+
}, [toggleLoading, openModal, resetForm, onStart, onSuccess, onError, onFinally]);
|
|
162
|
+
return {
|
|
163
|
+
isSubmitting,
|
|
164
|
+
submitError,
|
|
165
|
+
submitResponse,
|
|
166
|
+
handleSubmit,
|
|
167
|
+
modalContent: modalContent,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
const FormSubmitContext = createContext(undefined);
|
|
171
|
+
FormSubmitWrapper.propTypes = {
|
|
172
|
+
/** Enable/disable loading spinner during submission (default: true) */
|
|
173
|
+
toggleLoading: PropTypes.bool,
|
|
174
|
+
/** Enable/disable thank you modal after submission (default: true) */
|
|
175
|
+
openModal: PropTypes.bool,
|
|
176
|
+
/** Enable/disable form reset after submission (default: true) */
|
|
177
|
+
resetForm: PropTypes.bool,
|
|
178
|
+
/** Custom content to display in thank you modal */
|
|
179
|
+
modalContent: PropTypes.node,
|
|
180
|
+
/** Callback invoked at start of submission */
|
|
181
|
+
onStart: PropTypes.func,
|
|
182
|
+
/** Callback invoked on successful submission */
|
|
183
|
+
onSuccess: PropTypes.func,
|
|
184
|
+
/** Callback invoked on submission error */
|
|
185
|
+
onError: PropTypes.func,
|
|
186
|
+
/** Callback invoked at end of submission lifecycle */
|
|
187
|
+
onFinally: PropTypes.func,
|
|
188
|
+
/** Form components to render within the wrapper */
|
|
189
|
+
children: PropTypes.node.isRequired,
|
|
190
|
+
};
|
|
191
|
+
export function FormSubmitWrapper(props) {
|
|
192
|
+
const { children, ...options } = props;
|
|
193
|
+
const { handleSubmit, isSubmitting, modalContent } = useFormSubmit(options);
|
|
194
|
+
return (_jsxs(FormSubmitContext.Provider, { value: { handleSubmit, isSubmitting }, children: [_jsx(Loading, {}), _jsx(Modal, { modalContent: modalContent }), children] }));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Hook to access form submission context within FormSubmitWrapper
|
|
198
|
+
*/
|
|
199
|
+
export function useFormSubmitContext() {
|
|
200
|
+
const context = useContext(FormSubmitContext);
|
|
201
|
+
if (!context) {
|
|
202
|
+
throw new Error('useFormSubmitContext must be used within FormSubmitWrapper');
|
|
203
|
+
}
|
|
204
|
+
return context;
|
|
205
|
+
}
|