@uptrademedia/site-kit 1.2.5 → 1.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/dist/{chunk-TJUON7TH.mjs → chunk-24HSWZ42.mjs} +44 -22
  2. package/dist/chunk-24HSWZ42.mjs.map +1 -0
  3. package/dist/chunk-3VHHDNLH.mjs +1 -0
  4. package/dist/chunk-3WMD3TE6.js +1 -0
  5. package/dist/chunk-5TTUNB63.js +1 -0
  6. package/dist/chunk-7557OTHW.js +62 -0
  7. package/dist/chunk-7557OTHW.js.map +1 -0
  8. package/dist/chunk-FR6DV5QX.js +1 -0
  9. package/dist/chunk-G7RSD56P.js +1 -0
  10. package/dist/chunk-GCJXQ4AG.mjs +59 -0
  11. package/dist/chunk-GCJXQ4AG.mjs.map +1 -0
  12. package/dist/chunk-GHSZWROI.js +1 -0
  13. package/dist/chunk-IARDGI5N.mjs +1 -0
  14. package/dist/chunk-KUGMH4ZF.js +1 -0
  15. package/dist/chunk-LBHEVL6U.js +1 -0
  16. package/dist/chunk-UJQ73OS6.js +1 -0
  17. package/dist/{chunk-V7QPQBFG.js → chunk-WJD3MZGY.js} +44 -22
  18. package/dist/chunk-WJD3MZGY.js.map +1 -0
  19. package/dist/commerce/index.js +1 -0
  20. package/dist/commerce/index.mjs +1 -0
  21. package/dist/forms/index.d.mts +1 -1
  22. package/dist/forms/index.d.ts +1 -1
  23. package/dist/forms/index.js +31 -9
  24. package/dist/forms/index.js.map +1 -1
  25. package/dist/forms/index.mjs +31 -9
  26. package/dist/forms/index.mjs.map +1 -1
  27. package/dist/index.d.mts +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +6 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/index.mjs +2 -1
  32. package/dist/index.mjs.map +1 -1
  33. package/dist/redirects/index.d.mts +15 -9
  34. package/dist/redirects/index.d.ts +15 -9
  35. package/dist/redirects/index.js +6 -5
  36. package/dist/redirects/index.mjs +2 -1
  37. package/dist/robots/index.d.mts +12 -20
  38. package/dist/robots/index.d.ts +12 -20
  39. package/dist/robots/index.js +37 -19
  40. package/dist/robots/index.js.map +1 -1
  41. package/dist/robots/index.mjs +37 -19
  42. package/dist/robots/index.mjs.map +1 -1
  43. package/dist/site-config/index.d.mts +24 -0
  44. package/dist/site-config/index.d.ts +24 -0
  45. package/dist/site-config/index.js +17 -0
  46. package/dist/site-config/index.js.map +1 -0
  47. package/dist/site-config/index.mjs +4 -0
  48. package/dist/site-config/index.mjs.map +1 -0
  49. package/dist/sitemap/index.d.mts +2 -2
  50. package/dist/sitemap/index.d.ts +2 -2
  51. package/dist/sitemap/index.js +8 -1
  52. package/dist/sitemap/index.js.map +1 -1
  53. package/dist/sitemap/index.mjs +8 -1
  54. package/dist/sitemap/index.mjs.map +1 -1
  55. package/dist/{types-BYSB7zNY.d.mts → types-mqEAmRhJ.d.mts} +3 -0
  56. package/dist/{types-BYSB7zNY.d.ts → types-mqEAmRhJ.d.ts} +3 -0
  57. package/package.json +6 -1
  58. package/dist/chunk-TJUON7TH.mjs.map +0 -1
  59. package/dist/chunk-V7QPQBFG.js.map +0 -1
@@ -1,33 +1,54 @@
1
+ import { getSiteConfig } from './chunk-GCJXQ4AG.mjs';
1
2
  import { NextResponse } from 'next/server';
2
3
 
3
- // src/redirects/index.ts
4
- var cachedRules = [];
5
- var cacheExpiry = 0;
4
+ var DEFAULT_PORTAL_URL = "https://api.uptrademedia.com";
5
+ var rulesCache = /* @__PURE__ */ new Map();
6
+ function getRedirectCacheKey(apiKey, domain) {
7
+ if (apiKey) return `key:${apiKey}`;
8
+ return `domain:${domain || ""}`;
9
+ }
6
10
  async function fetchRedirectRules(config) {
7
11
  const now = Date.now();
8
12
  const cacheSeconds = config.cacheSeconds ?? 300;
9
- if (cachedRules.length > 0 && now < cacheExpiry) {
10
- return cachedRules;
13
+ const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
14
+ const rawKey = config.apiKey ?? (typeof process !== "undefined" && process.env ? process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY : void 0);
15
+ const apiKey = typeof rawKey === "string" ? rawKey : void 0;
16
+ let domain = config.domain;
17
+ if (!apiKey && !domain) {
18
+ const siteConfig = await getSiteConfig({ apiUrl: baseUrl });
19
+ domain = siteConfig?.domain;
20
+ }
21
+ const cacheKey = getRedirectCacheKey(apiKey, domain);
22
+ const cached = rulesCache.get(cacheKey);
23
+ if (cached && now < cached.expiry) {
24
+ return cached.rules;
11
25
  }
12
26
  try {
13
- const baseUrl = config.portalApiUrl || "https://api.uptrademedia.com";
14
- const domain = config.domain || "";
15
- const url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
27
+ let url;
28
+ const headers = { "Content-Type": "application/json" };
29
+ if (apiKey) {
30
+ url = `${baseUrl}/api/public/seo/redirects`;
31
+ headers["x-api-key"] = apiKey;
32
+ } else if (domain) {
33
+ url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
34
+ } else {
35
+ return [];
36
+ }
16
37
  const res = await fetch(url, {
17
- headers: { "Content-Type": "application/json" },
38
+ headers,
18
39
  next: { revalidate: cacheSeconds }
19
40
  });
20
41
  if (!res.ok) {
21
42
  console.error(`[site-kit] Failed to fetch redirects: ${res.status}`);
22
- return cachedRules;
43
+ return cached?.rules ?? [];
23
44
  }
24
45
  const data = await res.json();
25
- cachedRules = (data.redirects || []).filter((r) => r.is_enabled);
26
- cacheExpiry = now + cacheSeconds * 1e3;
27
- return cachedRules;
46
+ const rules = (data.redirects || []).filter((r) => r.is_enabled);
47
+ rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1e3 });
48
+ return rules;
28
49
  } catch (error) {
29
50
  console.error("[site-kit] Error fetching redirects:", error);
30
- return cachedRules;
51
+ return cached?.rules ?? [];
31
52
  }
32
53
  }
33
54
  async function handleManagedRedirects(request, config) {
@@ -49,18 +70,20 @@ async function handleManagedRedirects(request, config) {
49
70
  request.nextUrl.searchParams.forEach((value, key) => {
50
71
  redirectUrl.searchParams.set(key, value);
51
72
  });
52
- trackRedirectHit(config, match.from_path).catch(() => {
73
+ trackRedirectHit(config, match.from_path, config.domain).catch(() => {
53
74
  });
54
75
  const statusCode = parseInt(match.redirect_type);
55
76
  return NextResponse.redirect(redirectUrl, statusCode);
56
77
  }
57
- async function trackRedirectHit(config, fromPath) {
78
+ async function trackRedirectHit(config, fromPath, domainForHit) {
58
79
  try {
59
- const baseUrl = config.portalApiUrl || "https://api.uptrademedia.com";
80
+ const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
81
+ const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain;
82
+ if (!domain) return;
60
83
  await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {
61
84
  method: "POST",
62
85
  headers: { "Content-Type": "application/json" },
63
- body: JSON.stringify({ domain: config.domain, from_path: fromPath })
86
+ body: JSON.stringify({ domain, from_path: fromPath })
64
87
  });
65
88
  } catch {
66
89
  }
@@ -74,10 +97,9 @@ async function generateNextRedirects(config) {
74
97
  }));
75
98
  }
76
99
  function clearRedirectCache() {
77
- cachedRules = [];
78
- cacheExpiry = 0;
100
+ rulesCache.clear();
79
101
  }
80
102
 
81
103
  export { clearRedirectCache, fetchRedirectRules, generateNextRedirects, handleManagedRedirects };
82
- //# sourceMappingURL=chunk-TJUON7TH.mjs.map
83
- //# sourceMappingURL=chunk-TJUON7TH.mjs.map
104
+ //# sourceMappingURL=chunk-24HSWZ42.mjs.map
105
+ //# sourceMappingURL=chunk-24HSWZ42.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/redirects/index.ts"],"names":[],"mappings":"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","file":"chunk-24HSWZ42.mjs","sourcesContent":["/**\n * Managed Redirects - Next.js Middleware Helper\n * \n * Fetches redirect rules from Portal and applies them.\n * Supports API key only (domain resolved from Portal) or domain-based lookup.\n * \n * Usage in middleware.ts (API key only):\n * \n * import { handleManagedRedirects } from '@uptrade/site-kit/redirects'\n * \n * export async function middleware(request: NextRequest) {\n * const redirect = await handleManagedRedirects(request, {\n * apiKey: process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n * portalApiUrl: process.env.NEXT_PUBLIC_UPTRADE_API_URL,\n * })\n * if (redirect) return redirect\n * return NextResponse.next()\n * }\n * \n * Or with domain (legacy):\n * const redirect = await handleManagedRedirects(request, { domain: 'example.com' })\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { getSiteConfig } from '../site-config'\n\nexport interface RedirectRule {\n from_path: string\n to_path: string\n redirect_type: '301' | '302' | '307' | '308'\n is_enabled: boolean\n}\n\nexport interface RedirectConfig {\n /** Domain to fetch redirects for (optional when apiKey is set) */\n domain?: string\n /** Project API key; when set, redirects are fetched by key (no domain needed) */\n apiKey?: string\n portalApiUrl?: string\n cacheSeconds?: number\n}\n\nconst DEFAULT_PORTAL_URL = 'https://api.uptrademedia.com'\n\n// Cache for redirect rules keyed by apiKey or 'domain:'+domain\nconst rulesCache = new Map<string, { rules: RedirectRule[]; expiry: number }>()\n\nfunction getRedirectCacheKey(apiKey: string | undefined, domain: string | undefined): string {\n if (apiKey) return `key:${apiKey}`\n return `domain:${domain || ''}`\n}\n\n/**\n * Fetch redirect rules from Portal API.\n * When apiKey is set (or from env), uses key-based endpoint (no domain required).\n * Otherwise uses domain query (or domain from getSiteConfig when only env API key is set).\n */\nexport async function fetchRedirectRules(config: RedirectConfig): Promise<RedirectRule[]> {\n const now = Date.now()\n const cacheSeconds = config.cacheSeconds ?? 300\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n\n const rawKey = config.apiKey ?? (typeof process !== 'undefined' && process.env ? (process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY) : undefined)\n const apiKey = typeof rawKey === 'string' ? rawKey : undefined\n let domain = config.domain\n\n if (!apiKey && !domain) {\n const siteConfig = await getSiteConfig({ apiUrl: baseUrl })\n domain = siteConfig?.domain\n }\n\n const cacheKey = getRedirectCacheKey(apiKey, domain)\n const cached = rulesCache.get(cacheKey)\n if (cached && now < cached.expiry) {\n return cached.rules\n }\n\n try {\n let url: string\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n\n if (apiKey) {\n url = `${baseUrl}/api/public/seo/redirects`\n headers['x-api-key'] = apiKey\n } else if (domain) {\n url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`\n } else {\n return []\n }\n\n const res = await fetch(url, {\n headers,\n next: { revalidate: cacheSeconds },\n })\n\n if (!res.ok) {\n console.error(`[site-kit] Failed to fetch redirects: ${res.status}`)\n return cached?.rules ?? []\n }\n\n const data = await res.json()\n const rules = (data.redirects || []).filter((r: RedirectRule) => r.is_enabled)\n rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1000 })\n return rules\n } catch (error) {\n console.error('[site-kit] Error fetching redirects:', error)\n return cached?.rules ?? []\n }\n}\n\n/**\n * Handle managed redirects in middleware\n * Returns a NextResponse.redirect if a match is found, otherwise undefined\n */\nexport async function handleManagedRedirects(\n request: NextRequest,\n config: RedirectConfig,\n): Promise<NextResponse | undefined> {\n const pathname = request.nextUrl.pathname\n \n // Skip for static assets and API routes\n if (\n pathname.startsWith('/_next') ||\n pathname.startsWith('/api') ||\n pathname.includes('.') // Has file extension\n ) {\n return undefined\n }\n\n const rules = await fetchRedirectRules(config)\n \n // Find matching redirect\n const match = rules.find(r => {\n // Exact match\n if (r.from_path === pathname) return true\n \n // Match with trailing slash variance\n const normalizedFrom = r.from_path.endsWith('/') \n ? r.from_path.slice(0, -1) \n : r.from_path\n const normalizedPath = pathname.endsWith('/') \n ? pathname.slice(0, -1) \n : pathname\n \n return normalizedFrom === normalizedPath\n })\n\n if (!match) {\n return undefined\n }\n\n // Build redirect URL\n const redirectUrl = new URL(match.to_path, request.url)\n \n // Preserve query params\n request.nextUrl.searchParams.forEach((value, key) => {\n redirectUrl.searchParams.set(key, value)\n })\n\n // Track hit (fire and forget); domain may be from config or resolved by apiKey\n trackRedirectHit(config, match.from_path, config.domain).catch(() => {})\n\n // Return redirect response\n const statusCode = parseInt(match.redirect_type) as 301 | 302 | 307 | 308\n return NextResponse.redirect(redirectUrl, statusCode)\n}\n\n/**\n * Track redirect hit for analytics (needs domain; resolved from config or getSiteConfig when using apiKey)\n */\nasync function trackRedirectHit(config: RedirectConfig, fromPath: string, domainForHit?: string): Promise<void> {\n try {\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain\n if (!domain) return\n await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain, from_path: fromPath }),\n })\n } catch {\n // Ignore tracking errors\n }\n}\n\n/**\n * Generate Next.js redirects config from Portal\n * Use this in next.config.js for build-time redirects\n * \n * Usage in next.config.js:\n * \n * const { generateNextRedirects } = require('@uptrade/site-kit/redirects')\n * \n * module.exports = {\n * async redirects() {\n * return generateNextRedirects({\n * domain: 'example.com',\n * portalApiUrl: process.env.PORTAL_API_URL,\n * })\n * }\n * }\n */\nexport async function generateNextRedirects(config: RedirectConfig): Promise<Array<{\n source: string\n destination: string\n permanent: boolean\n}>> {\n const rules = await fetchRedirectRules({ ...config, cacheSeconds: 0 })\n \n return rules.map(r => ({\n source: r.from_path,\n destination: r.to_path,\n permanent: r.redirect_type === '301' || r.redirect_type === '308',\n }))\n}\n\n/**\n * Clear redirect cache (useful for development)\n */\nexport function clearRedirectCache(): void {\n rulesCache.clear()\n}\n"]}
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import React2, { useState, useRef, useEffect, useCallback, createElement } from 'react';
2
3
  import ReactMarkdown from 'react-markdown';
3
4
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var React2 = require('react');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+
3
+ // src/site-config/index.ts
4
+ var DEFAULT_API_URL = "https://api.uptrademedia.com";
5
+ var CACHE_TTL_MS = 10 * 60 * 1e3;
6
+ var cache = /* @__PURE__ */ new Map();
7
+ function getApiKey(options) {
8
+ if (options?.apiKey) return options.apiKey;
9
+ if (typeof process !== "undefined" && process.env) {
10
+ return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY;
11
+ }
12
+ return void 0;
13
+ }
14
+ function getApiUrl(options) {
15
+ if (options?.apiUrl) return options.apiUrl.replace(/\/$/, "");
16
+ if (typeof process !== "undefined" && process.env) {
17
+ const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL;
18
+ if (u) return u.replace(/\/$/, "");
19
+ }
20
+ return DEFAULT_API_URL;
21
+ }
22
+ async function getSiteConfig(options) {
23
+ const apiKey = getApiKey(options);
24
+ if (!apiKey) return null;
25
+ const now = Date.now();
26
+ const cached = cache.get(apiKey);
27
+ if (cached && now < cached.expiry) {
28
+ return { site_url: cached.site_url, domain: cached.domain };
29
+ }
30
+ const apiUrl = getApiUrl(options);
31
+ const url = `${apiUrl}/api/public/seo/project-info`;
32
+ try {
33
+ const res = await fetch(url, {
34
+ headers: {
35
+ "Content-Type": "application/json",
36
+ "x-api-key": apiKey
37
+ },
38
+ next: { revalidate: 600 }
39
+ });
40
+ if (!res.ok) return null;
41
+ const data = await res.json();
42
+ const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "")}` : null);
43
+ const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "") : null);
44
+ if (!site_url || !domain || typeof site_url !== "string" || typeof domain !== "string") return null;
45
+ cache.set(apiKey, {
46
+ site_url,
47
+ domain,
48
+ expiry: now + CACHE_TTL_MS
49
+ });
50
+ return { site_url, domain };
51
+ } catch {
52
+ return null;
53
+ }
54
+ }
55
+ function clearSiteConfigCache() {
56
+ cache.clear();
57
+ }
58
+
59
+ exports.clearSiteConfigCache = clearSiteConfigCache;
60
+ exports.getSiteConfig = getSiteConfig;
61
+ //# sourceMappingURL=chunk-7557OTHW.js.map
62
+ //# sourceMappingURL=chunk-7557OTHW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/site-config/index.ts"],"names":[],"mappings":"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","file":"chunk-7557OTHW.js","sourcesContent":["/**\n * Site config from Portal (project-info) by API key.\n * Resolves site_url and domain so consumers only need NEXT_PUBLIC_UPTRADE_API_KEY.\n */\n\nexport interface SiteConfig {\n site_url: string\n domain: string\n}\n\nexport interface GetSiteConfigOptions {\n apiKey?: string\n apiUrl?: string\n}\n\nconst DEFAULT_API_URL = 'https://api.uptrademedia.com'\nconst CACHE_TTL_MS = 10 * 60 * 1000 // 10 minutes\n\ninterface CacheEntry {\n site_url: string\n domain: string\n expiry: number\n}\nconst cache = new Map<string, CacheEntry>()\n\nfunction getApiKey(options?: GetSiteConfigOptions): string | undefined {\n if (options?.apiKey) return options.apiKey\n if (typeof process !== 'undefined' && process.env) {\n return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY\n }\n return undefined\n}\n\nfunction getApiUrl(options?: GetSiteConfigOptions): string {\n if (options?.apiUrl) return options.apiUrl.replace(/\\/$/, '')\n if (typeof process !== 'undefined' && process.env) {\n const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL\n if (u) return u.replace(/\\/$/, '')\n }\n return DEFAULT_API_URL\n}\n\n/**\n * Fetch site_url and domain from Portal project-info for the given API key.\n * Uses in-memory cache (TTL 10 min) keyed by apiKey.\n * Safe for Edge (middleware) and Node (route handlers).\n */\nexport async function getSiteConfig(\n options?: GetSiteConfigOptions\n): Promise<SiteConfig | null> {\n const apiKey = getApiKey(options)\n if (!apiKey) return null\n\n const now = Date.now()\n const cached = cache.get(apiKey)\n if (cached && now < cached.expiry) {\n return { site_url: cached.site_url, domain: cached.domain }\n }\n\n const apiUrl = getApiUrl(options)\n const url = `${apiUrl}/api/public/seo/project-info`\n\n try {\n const res = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 600 },\n })\n\n if (!res.ok) return null\n\n const data = await res.json()\n const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '')}` : null)\n const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '') : null)\n\n if (!site_url || !domain || typeof site_url !== 'string' || typeof domain !== 'string') return null\n\n cache.set(apiKey, {\n site_url,\n domain,\n expiry: now + CACHE_TTL_MS,\n })\n\n return { site_url, domain }\n } catch {\n return null\n }\n}\n\n/**\n * Clear in-memory site config cache (e.g. for tests).\n */\nexport function clearSiteConfigCache(): void {\n cache.clear()\n}\n"]}
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var jsxRuntime = require('react/jsx-runtime');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -0,0 +1,59 @@
1
+ // src/site-config/index.ts
2
+ var DEFAULT_API_URL = "https://api.uptrademedia.com";
3
+ var CACHE_TTL_MS = 10 * 60 * 1e3;
4
+ var cache = /* @__PURE__ */ new Map();
5
+ function getApiKey(options) {
6
+ if (options?.apiKey) return options.apiKey;
7
+ if (typeof process !== "undefined" && process.env) {
8
+ return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY;
9
+ }
10
+ return void 0;
11
+ }
12
+ function getApiUrl(options) {
13
+ if (options?.apiUrl) return options.apiUrl.replace(/\/$/, "");
14
+ if (typeof process !== "undefined" && process.env) {
15
+ const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL;
16
+ if (u) return u.replace(/\/$/, "");
17
+ }
18
+ return DEFAULT_API_URL;
19
+ }
20
+ async function getSiteConfig(options) {
21
+ const apiKey = getApiKey(options);
22
+ if (!apiKey) return null;
23
+ const now = Date.now();
24
+ const cached = cache.get(apiKey);
25
+ if (cached && now < cached.expiry) {
26
+ return { site_url: cached.site_url, domain: cached.domain };
27
+ }
28
+ const apiUrl = getApiUrl(options);
29
+ const url = `${apiUrl}/api/public/seo/project-info`;
30
+ try {
31
+ const res = await fetch(url, {
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ "x-api-key": apiKey
35
+ },
36
+ next: { revalidate: 600 }
37
+ });
38
+ if (!res.ok) return null;
39
+ const data = await res.json();
40
+ const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "")}` : null);
41
+ const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\/\/)?(www\.)?/, "").replace(/\/$/, "") : null);
42
+ if (!site_url || !domain || typeof site_url !== "string" || typeof domain !== "string") return null;
43
+ cache.set(apiKey, {
44
+ site_url,
45
+ domain,
46
+ expiry: now + CACHE_TTL_MS
47
+ });
48
+ return { site_url, domain };
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+ function clearSiteConfigCache() {
54
+ cache.clear();
55
+ }
56
+
57
+ export { clearSiteConfigCache, getSiteConfig };
58
+ //# sourceMappingURL=chunk-GCJXQ4AG.mjs.map
59
+ //# sourceMappingURL=chunk-GCJXQ4AG.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/site-config/index.ts"],"names":[],"mappings":"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","file":"chunk-GCJXQ4AG.mjs","sourcesContent":["/**\n * Site config from Portal (project-info) by API key.\n * Resolves site_url and domain so consumers only need NEXT_PUBLIC_UPTRADE_API_KEY.\n */\n\nexport interface SiteConfig {\n site_url: string\n domain: string\n}\n\nexport interface GetSiteConfigOptions {\n apiKey?: string\n apiUrl?: string\n}\n\nconst DEFAULT_API_URL = 'https://api.uptrademedia.com'\nconst CACHE_TTL_MS = 10 * 60 * 1000 // 10 minutes\n\ninterface CacheEntry {\n site_url: string\n domain: string\n expiry: number\n}\nconst cache = new Map<string, CacheEntry>()\n\nfunction getApiKey(options?: GetSiteConfigOptions): string | undefined {\n if (options?.apiKey) return options.apiKey\n if (typeof process !== 'undefined' && process.env) {\n return process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY\n }\n return undefined\n}\n\nfunction getApiUrl(options?: GetSiteConfigOptions): string {\n if (options?.apiUrl) return options.apiUrl.replace(/\\/$/, '')\n if (typeof process !== 'undefined' && process.env) {\n const u = process.env.NEXT_PUBLIC_UPTRADE_API_URL || process.env.UPTRADE_API_URL\n if (u) return u.replace(/\\/$/, '')\n }\n return DEFAULT_API_URL\n}\n\n/**\n * Fetch site_url and domain from Portal project-info for the given API key.\n * Uses in-memory cache (TTL 10 min) keyed by apiKey.\n * Safe for Edge (middleware) and Node (route handlers).\n */\nexport async function getSiteConfig(\n options?: GetSiteConfigOptions\n): Promise<SiteConfig | null> {\n const apiKey = getApiKey(options)\n if (!apiKey) return null\n\n const now = Date.now()\n const cached = cache.get(apiKey)\n if (cached && now < cached.expiry) {\n return { site_url: cached.site_url, domain: cached.domain }\n }\n\n const apiUrl = getApiUrl(options)\n const url = `${apiUrl}/api/public/seo/project-info`\n\n try {\n const res = await fetch(url, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 600 },\n })\n\n if (!res.ok) return null\n\n const data = await res.json()\n const site_url = data.site_url ?? (data.project_domain ? `https://${String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '')}` : null)\n const domain = data.domain ?? (data.project_domain ? String(data.project_domain).replace(/^(https?:\\/\\/)?(www\\.)?/, '').replace(/\\/$/, '') : null)\n\n if (!site_url || !domain || typeof site_url !== 'string' || typeof domain !== 'string') return null\n\n cache.set(apiKey, {\n site_url,\n domain,\n expiry: now + CACHE_TTL_MS,\n })\n\n return { site_url, domain }\n } catch {\n return null\n }\n}\n\n/**\n * Clear in-memory site config cache (e.g. for tests).\n */\nexport function clearSiteConfigCache(): void {\n cache.clear()\n}\n"]}
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
3
  import React5, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
3
4
 
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var react = require('react');
@@ -1,35 +1,56 @@
1
1
  'use strict';
2
2
 
3
+ var chunk7557OTHW_js = require('./chunk-7557OTHW.js');
3
4
  var server = require('next/server');
4
5
 
5
- // src/redirects/index.ts
6
- var cachedRules = [];
7
- var cacheExpiry = 0;
6
+ var DEFAULT_PORTAL_URL = "https://api.uptrademedia.com";
7
+ var rulesCache = /* @__PURE__ */ new Map();
8
+ function getRedirectCacheKey(apiKey, domain) {
9
+ if (apiKey) return `key:${apiKey}`;
10
+ return `domain:${domain || ""}`;
11
+ }
8
12
  async function fetchRedirectRules(config) {
9
13
  const now = Date.now();
10
14
  const cacheSeconds = config.cacheSeconds ?? 300;
11
- if (cachedRules.length > 0 && now < cacheExpiry) {
12
- return cachedRules;
15
+ const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
16
+ const rawKey = config.apiKey ?? (typeof process !== "undefined" && process.env ? process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY : void 0);
17
+ const apiKey = typeof rawKey === "string" ? rawKey : void 0;
18
+ let domain = config.domain;
19
+ if (!apiKey && !domain) {
20
+ const siteConfig = await chunk7557OTHW_js.getSiteConfig({ apiUrl: baseUrl });
21
+ domain = siteConfig?.domain;
22
+ }
23
+ const cacheKey = getRedirectCacheKey(apiKey, domain);
24
+ const cached = rulesCache.get(cacheKey);
25
+ if (cached && now < cached.expiry) {
26
+ return cached.rules;
13
27
  }
14
28
  try {
15
- const baseUrl = config.portalApiUrl || "https://api.uptrademedia.com";
16
- const domain = config.domain || "";
17
- const url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
29
+ let url;
30
+ const headers = { "Content-Type": "application/json" };
31
+ if (apiKey) {
32
+ url = `${baseUrl}/api/public/seo/redirects`;
33
+ headers["x-api-key"] = apiKey;
34
+ } else if (domain) {
35
+ url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`;
36
+ } else {
37
+ return [];
38
+ }
18
39
  const res = await fetch(url, {
19
- headers: { "Content-Type": "application/json" },
40
+ headers,
20
41
  next: { revalidate: cacheSeconds }
21
42
  });
22
43
  if (!res.ok) {
23
44
  console.error(`[site-kit] Failed to fetch redirects: ${res.status}`);
24
- return cachedRules;
45
+ return cached?.rules ?? [];
25
46
  }
26
47
  const data = await res.json();
27
- cachedRules = (data.redirects || []).filter((r) => r.is_enabled);
28
- cacheExpiry = now + cacheSeconds * 1e3;
29
- return cachedRules;
48
+ const rules = (data.redirects || []).filter((r) => r.is_enabled);
49
+ rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1e3 });
50
+ return rules;
30
51
  } catch (error) {
31
52
  console.error("[site-kit] Error fetching redirects:", error);
32
- return cachedRules;
53
+ return cached?.rules ?? [];
33
54
  }
34
55
  }
35
56
  async function handleManagedRedirects(request, config) {
@@ -51,18 +72,20 @@ async function handleManagedRedirects(request, config) {
51
72
  request.nextUrl.searchParams.forEach((value, key) => {
52
73
  redirectUrl.searchParams.set(key, value);
53
74
  });
54
- trackRedirectHit(config, match.from_path).catch(() => {
75
+ trackRedirectHit(config, match.from_path, config.domain).catch(() => {
55
76
  });
56
77
  const statusCode = parseInt(match.redirect_type);
57
78
  return server.NextResponse.redirect(redirectUrl, statusCode);
58
79
  }
59
- async function trackRedirectHit(config, fromPath) {
80
+ async function trackRedirectHit(config, fromPath, domainForHit) {
60
81
  try {
61
- const baseUrl = config.portalApiUrl || "https://api.uptrademedia.com";
82
+ const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL;
83
+ const domain = domainForHit ?? config.domain ?? (await chunk7557OTHW_js.getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain;
84
+ if (!domain) return;
62
85
  await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {
63
86
  method: "POST",
64
87
  headers: { "Content-Type": "application/json" },
65
- body: JSON.stringify({ domain: config.domain, from_path: fromPath })
88
+ body: JSON.stringify({ domain, from_path: fromPath })
66
89
  });
67
90
  } catch {
68
91
  }
@@ -76,13 +99,12 @@ async function generateNextRedirects(config) {
76
99
  }));
77
100
  }
78
101
  function clearRedirectCache() {
79
- cachedRules = [];
80
- cacheExpiry = 0;
102
+ rulesCache.clear();
81
103
  }
82
104
 
83
105
  exports.clearRedirectCache = clearRedirectCache;
84
106
  exports.fetchRedirectRules = fetchRedirectRules;
85
107
  exports.generateNextRedirects = generateNextRedirects;
86
108
  exports.handleManagedRedirects = handleManagedRedirects;
87
- //# sourceMappingURL=chunk-V7QPQBFG.js.map
88
- //# sourceMappingURL=chunk-V7QPQBFG.js.map
109
+ //# sourceMappingURL=chunk-WJD3MZGY.js.map
110
+ //# sourceMappingURL=chunk-WJD3MZGY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/redirects/index.ts"],"names":["getSiteConfig","NextResponse"],"mappings":"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","file":"chunk-WJD3MZGY.js","sourcesContent":["/**\n * Managed Redirects - Next.js Middleware Helper\n * \n * Fetches redirect rules from Portal and applies them.\n * Supports API key only (domain resolved from Portal) or domain-based lookup.\n * \n * Usage in middleware.ts (API key only):\n * \n * import { handleManagedRedirects } from '@uptrade/site-kit/redirects'\n * \n * export async function middleware(request: NextRequest) {\n * const redirect = await handleManagedRedirects(request, {\n * apiKey: process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n * portalApiUrl: process.env.NEXT_PUBLIC_UPTRADE_API_URL,\n * })\n * if (redirect) return redirect\n * return NextResponse.next()\n * }\n * \n * Or with domain (legacy):\n * const redirect = await handleManagedRedirects(request, { domain: 'example.com' })\n */\n\nimport { NextRequest, NextResponse } from 'next/server'\nimport { getSiteConfig } from '../site-config'\n\nexport interface RedirectRule {\n from_path: string\n to_path: string\n redirect_type: '301' | '302' | '307' | '308'\n is_enabled: boolean\n}\n\nexport interface RedirectConfig {\n /** Domain to fetch redirects for (optional when apiKey is set) */\n domain?: string\n /** Project API key; when set, redirects are fetched by key (no domain needed) */\n apiKey?: string\n portalApiUrl?: string\n cacheSeconds?: number\n}\n\nconst DEFAULT_PORTAL_URL = 'https://api.uptrademedia.com'\n\n// Cache for redirect rules keyed by apiKey or 'domain:'+domain\nconst rulesCache = new Map<string, { rules: RedirectRule[]; expiry: number }>()\n\nfunction getRedirectCacheKey(apiKey: string | undefined, domain: string | undefined): string {\n if (apiKey) return `key:${apiKey}`\n return `domain:${domain || ''}`\n}\n\n/**\n * Fetch redirect rules from Portal API.\n * When apiKey is set (or from env), uses key-based endpoint (no domain required).\n * Otherwise uses domain query (or domain from getSiteConfig when only env API key is set).\n */\nexport async function fetchRedirectRules(config: RedirectConfig): Promise<RedirectRule[]> {\n const now = Date.now()\n const cacheSeconds = config.cacheSeconds ?? 300\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n\n const rawKey = config.apiKey ?? (typeof process !== 'undefined' && process.env ? (process.env.NEXT_PUBLIC_UPTRADE_API_KEY || process.env.UPTRADE_API_KEY) : undefined)\n const apiKey = typeof rawKey === 'string' ? rawKey : undefined\n let domain = config.domain\n\n if (!apiKey && !domain) {\n const siteConfig = await getSiteConfig({ apiUrl: baseUrl })\n domain = siteConfig?.domain\n }\n\n const cacheKey = getRedirectCacheKey(apiKey, domain)\n const cached = rulesCache.get(cacheKey)\n if (cached && now < cached.expiry) {\n return cached.rules\n }\n\n try {\n let url: string\n const headers: Record<string, string> = { 'Content-Type': 'application/json' }\n\n if (apiKey) {\n url = `${baseUrl}/api/public/seo/redirects`\n headers['x-api-key'] = apiKey\n } else if (domain) {\n url = `${baseUrl}/api/public/seo/redirects?domain=${encodeURIComponent(domain)}`\n } else {\n return []\n }\n\n const res = await fetch(url, {\n headers,\n next: { revalidate: cacheSeconds },\n })\n\n if (!res.ok) {\n console.error(`[site-kit] Failed to fetch redirects: ${res.status}`)\n return cached?.rules ?? []\n }\n\n const data = await res.json()\n const rules = (data.redirects || []).filter((r: RedirectRule) => r.is_enabled)\n rulesCache.set(cacheKey, { rules, expiry: now + cacheSeconds * 1000 })\n return rules\n } catch (error) {\n console.error('[site-kit] Error fetching redirects:', error)\n return cached?.rules ?? []\n }\n}\n\n/**\n * Handle managed redirects in middleware\n * Returns a NextResponse.redirect if a match is found, otherwise undefined\n */\nexport async function handleManagedRedirects(\n request: NextRequest,\n config: RedirectConfig,\n): Promise<NextResponse | undefined> {\n const pathname = request.nextUrl.pathname\n \n // Skip for static assets and API routes\n if (\n pathname.startsWith('/_next') ||\n pathname.startsWith('/api') ||\n pathname.includes('.') // Has file extension\n ) {\n return undefined\n }\n\n const rules = await fetchRedirectRules(config)\n \n // Find matching redirect\n const match = rules.find(r => {\n // Exact match\n if (r.from_path === pathname) return true\n \n // Match with trailing slash variance\n const normalizedFrom = r.from_path.endsWith('/') \n ? r.from_path.slice(0, -1) \n : r.from_path\n const normalizedPath = pathname.endsWith('/') \n ? pathname.slice(0, -1) \n : pathname\n \n return normalizedFrom === normalizedPath\n })\n\n if (!match) {\n return undefined\n }\n\n // Build redirect URL\n const redirectUrl = new URL(match.to_path, request.url)\n \n // Preserve query params\n request.nextUrl.searchParams.forEach((value, key) => {\n redirectUrl.searchParams.set(key, value)\n })\n\n // Track hit (fire and forget); domain may be from config or resolved by apiKey\n trackRedirectHit(config, match.from_path, config.domain).catch(() => {})\n\n // Return redirect response\n const statusCode = parseInt(match.redirect_type) as 301 | 302 | 307 | 308\n return NextResponse.redirect(redirectUrl, statusCode)\n}\n\n/**\n * Track redirect hit for analytics (needs domain; resolved from config or getSiteConfig when using apiKey)\n */\nasync function trackRedirectHit(config: RedirectConfig, fromPath: string, domainForHit?: string): Promise<void> {\n try {\n const baseUrl = config.portalApiUrl || DEFAULT_PORTAL_URL\n const domain = domainForHit ?? config.domain ?? (await getSiteConfig({ apiKey: config.apiKey, apiUrl: baseUrl }))?.domain\n if (!domain) return\n await fetch(`${baseUrl}/api/public/seo/redirects/hit`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ domain, from_path: fromPath }),\n })\n } catch {\n // Ignore tracking errors\n }\n}\n\n/**\n * Generate Next.js redirects config from Portal\n * Use this in next.config.js for build-time redirects\n * \n * Usage in next.config.js:\n * \n * const { generateNextRedirects } = require('@uptrade/site-kit/redirects')\n * \n * module.exports = {\n * async redirects() {\n * return generateNextRedirects({\n * domain: 'example.com',\n * portalApiUrl: process.env.PORTAL_API_URL,\n * })\n * }\n * }\n */\nexport async function generateNextRedirects(config: RedirectConfig): Promise<Array<{\n source: string\n destination: string\n permanent: boolean\n}>> {\n const rules = await fetchRedirectRules({ ...config, cacheSeconds: 0 })\n \n return rules.map(r => ({\n source: r.from_path,\n destination: r.to_path,\n permanent: r.redirect_type === '301' || r.redirect_type === '308',\n }))\n}\n\n/**\n * Clear redirect cache (useful for development)\n */\nexport function clearRedirectCache(): void {\n rulesCache.clear()\n}\n"]}
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  'use strict';
2
3
 
3
4
  var chunkFR6DV5QX_js = require('../chunk-FR6DV5QX.js');
@@ -1,3 +1,4 @@
1
+ 'use client';
1
2
  export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, EventsWidget, OfferingCard, OfferingList, ProductDetail, ProductEmbed, ProductGrid, ProductPage, RegistrationForm, UpcomingEvents, createCheckoutSession, fetchActiveProcessor, fetchCategories, fetchLatestOffering, fetchNextEvent, fetchOffering, fetchOfferings, fetchProcessorConfig, fetchProductBySlug, fetchProducts, fetchProductsPublic, fetchServices, fetchShippingRates, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal, validateAddress } from '../chunk-IARDGI5N.mjs';
2
3
  import '../chunk-4XPGGLVP.mjs';
3
4
  //# sourceMappingURL=index.mjs.map
@@ -1,4 +1,4 @@
1
- import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-BYSB7zNY.mjs';
1
+ import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-mqEAmRhJ.mjs';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React__default from 'react';
4
4
 
@@ -1,4 +1,4 @@
1
- import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-BYSB7zNY.js';
1
+ import { F as FormSubmission, M as ManagedFormConfig, a as FormField$2, b as ManagedFormProps, c as FormRenderProps } from '../types-mqEAmRhJ.js';
2
2
  import * as react_jsx_runtime from 'react/jsx-runtime';
3
3
  import React__default from 'react';
4
4
 
@@ -174,8 +174,9 @@ function getDeviceType() {
174
174
  }
175
175
 
176
176
  // src/forms/recaptcha.ts
177
- function getSiteKey() {
177
+ function getSiteKey(explicitSiteKey) {
178
178
  if (typeof window === "undefined") return void 0;
179
+ if (explicitSiteKey) return explicitSiteKey;
179
180
  const win = window;
180
181
  return win.__SITE_KIT_RECAPTCHA_SITE_KEY__ ?? process.env.NEXT_PUBLIC_RECAPTCHA_SITE_KEY;
181
182
  }
@@ -207,8 +208,8 @@ function loadScript(siteKey) {
207
208
  document.head.appendChild(script);
208
209
  });
209
210
  }
210
- async function getRecaptchaToken() {
211
- const siteKey = getSiteKey();
211
+ async function getRecaptchaToken(explicitSiteKey) {
212
+ const siteKey = getSiteKey(explicitSiteKey);
212
213
  if (!siteKey || typeof window === "undefined") return null;
213
214
  try {
214
215
  const grecaptcha = await loadScript(siteKey);
@@ -283,6 +284,10 @@ function useForm(formIdOrSlug, options = {}) {
283
284
  }
284
285
  const data = await response.json();
285
286
  if (!data) throw new Error("Form not found");
287
+ if (typeof window !== "undefined" && data.recaptcha_site_key) {
288
+ ;
289
+ window.__SITE_KIT_RECAPTCHA_SITE_KEY__ = data.recaptcha_site_key;
290
+ }
286
291
  if (data.steps) {
287
292
  data.steps.sort((a, b) => (a.step_number || 0) - (b.step_number || 0));
288
293
  }
@@ -443,11 +448,18 @@ function useForm(formIdOrSlug, options = {}) {
443
448
  if (!apiKey) {
444
449
  throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
445
450
  }
446
- const recaptchaToken = await getRecaptchaToken();
451
+ const honeypotFieldName = form.honeypot_field || "website";
452
+ const recaptchaToken = form.recaptcha_enabled ? await getRecaptchaToken(form.recaptcha_site_key) : null;
453
+ if (form.recaptcha_enabled && !recaptchaToken) {
454
+ throw new Error("reCAPTCHA is required but could not be initialized.");
455
+ }
447
456
  const utm = getUTMParams();
448
457
  const submission = {
449
458
  formId: form.id,
450
- data: values,
459
+ data: {
460
+ ...values,
461
+ ...form.honeypot_enabled ? { [honeypotFieldName]: "" } : {}
462
+ },
451
463
  metadata: {
452
464
  pageUrl: typeof window !== "undefined" ? window.location.href : null,
453
465
  referrer: typeof document !== "undefined" ? document.referrer || null : null,
@@ -797,6 +809,8 @@ function FormClient({
797
809
  const [step, setStep] = react.useState(1);
798
810
  const [isSubmitting, setIsSubmitting] = react.useState(false);
799
811
  const [isComplete, setIsComplete] = react.useState(false);
812
+ const [honeypotValue, setHoneypotValue] = react.useState("");
813
+ const honeypotFieldName = config.honeypot_field || "website";
800
814
  const { trackStepChange, trackComplete } = useFormTracking({
801
815
  formId: config.id,
802
816
  totalSteps: config.total_steps
@@ -920,12 +934,18 @@ function FormClient({
920
934
  if (!apiKey) {
921
935
  throw new Error("API key is required. Set NEXT_PUBLIC_UPTRADE_API_KEY in your .env");
922
936
  }
923
- const recaptchaToken = await getRecaptchaToken();
937
+ const recaptchaToken = config.recaptcha_enabled ? await getRecaptchaToken(config.recaptcha_site_key) : null;
938
+ if (config.recaptcha_enabled && !recaptchaToken) {
939
+ throw new Error("reCAPTCHA is required but could not be initialized.");
940
+ }
924
941
  const utm = getUTMParams2();
925
942
  const submission = {
926
943
  form_id: config.id,
927
944
  project_id: config.project_id,
928
- data: values,
945
+ data: {
946
+ ...values,
947
+ ...config.honeypot_enabled ? { [honeypotFieldName]: honeypotValue } : {}
948
+ },
929
949
  routing_type: config.form_type,
930
950
  status: "new",
931
951
  metadata: {
@@ -964,7 +984,7 @@ function FormClient({
964
984
  } finally {
965
985
  setIsSubmitting(false);
966
986
  }
967
- }, [config, values, validateStep, trackComplete, onSuccess, onError]);
987
+ }, [config, honeypotFieldName, honeypotValue, values, validateStep, trackComplete, onSuccess, onError]);
968
988
  const progress = react.useMemo(() => {
969
989
  return Math.round(step / config.total_steps * 100);
970
990
  }, [step, config.total_steps]);
@@ -1052,7 +1072,7 @@ function FormClient({
1052
1072
  "input",
1053
1073
  {
1054
1074
  type: "text",
1055
- name: "website",
1075
+ name: honeypotFieldName,
1056
1076
  tabIndex: -1,
1057
1077
  autoComplete: "off",
1058
1078
  style: {
@@ -1061,7 +1081,9 @@ function FormClient({
1061
1081
  opacity: 0,
1062
1082
  pointerEvents: "none"
1063
1083
  },
1084
+ value: honeypotValue,
1064
1085
  onChange: (e) => {
1086
+ setHoneypotValue(e.target.value);
1065
1087
  if (e.target.value) {
1066
1088
  console.warn("[Forms] Honeypot triggered");
1067
1089
  }