@uptrademedia/site-kit 1.0.34 → 1.0.36
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/{chunk-WUIDGCAI.mjs → chunk-CSB2IVNY.mjs} +11 -3
- package/dist/chunk-CSB2IVNY.mjs.map +1 -0
- package/dist/{chunk-77PEDSV5.js → chunk-KHP4AK3O.js} +11 -3
- package/dist/chunk-KHP4AK3O.js.map +1 -0
- package/dist/{chunk-A37Z47FZ.mjs → chunk-R4OKQVFD.mjs} +143 -13
- package/dist/chunk-R4OKQVFD.mjs.map +1 -0
- package/dist/{chunk-7BEMFSF6.js → chunk-WTMMXY7S.js} +142 -11
- package/dist/chunk-WTMMXY7S.js.map +1 -0
- package/dist/commerce/index.d.mts +1 -1
- package/dist/commerce/index.d.ts +1 -1
- package/dist/commerce/index.js +43 -39
- package/dist/commerce/index.mjs +1 -1
- package/dist/images/index.js +3 -3
- package/dist/images/index.mjs +1 -1
- package/dist/images/server.js +4 -4
- package/dist/images/server.mjs +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +26 -26
- package/dist/index.mjs +2 -2
- package/dist/{useEventModal-DHO4-xhg.d.ts → useEventModal-BRRT7ZSv.d.ts} +15 -1
- package/dist/{useEventModal-Dbg2fYYk.d.mts → useEventModal-NSrCRDlG.d.mts} +15 -1
- package/package.json +1 -1
- package/dist/chunk-77PEDSV5.js.map +0 -1
- package/dist/chunk-7BEMFSF6.js.map +0 -1
- package/dist/chunk-A37Z47FZ.mjs.map +0 -1
- package/dist/chunk-WUIDGCAI.mjs.map +0 -1
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
|
|
2
2
|
|
|
3
3
|
// src/images/ManagedFavicon.tsx
|
|
4
|
+
function toAbsoluteFaviconUrl(url, apiBase) {
|
|
5
|
+
if (!url) return void 0;
|
|
6
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
7
|
+
const base = apiBase.replace(/\/$/, "");
|
|
8
|
+
return url.startsWith("/") ? `${base}${url}` : `${base}/${url}`;
|
|
9
|
+
}
|
|
4
10
|
async function fetchFaviconData(apiUrl, apiKey) {
|
|
5
11
|
try {
|
|
6
12
|
const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {
|
|
@@ -17,8 +23,10 @@ async function fetchFaviconData(apiUrl, apiKey) {
|
|
|
17
23
|
}
|
|
18
24
|
const data = await res.json();
|
|
19
25
|
if (data.image && !data.is_placeholder) {
|
|
26
|
+
const raw = data.image.public_url || data.image.external_url;
|
|
27
|
+
const public_url = toAbsoluteFaviconUrl(raw, apiUrl);
|
|
20
28
|
return {
|
|
21
|
-
public_url:
|
|
29
|
+
public_url: public_url || void 0,
|
|
22
30
|
mime_type: data.image.file?.mime_type,
|
|
23
31
|
is_placeholder: false
|
|
24
32
|
};
|
|
@@ -56,5 +64,5 @@ async function ManagedFavicon({
|
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
export { ManagedFavicon, fetchFaviconData, getManagedFaviconUrl };
|
|
59
|
-
//# sourceMappingURL=chunk-
|
|
60
|
-
//# sourceMappingURL=chunk-
|
|
67
|
+
//# sourceMappingURL=chunk-CSB2IVNY.mjs.map
|
|
68
|
+
//# sourceMappingURL=chunk-CSB2IVNY.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/images/ManagedFavicon.tsx"],"names":[],"mappings":"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","file":"chunk-CSB2IVNY.mjs","sourcesContent":["/**\n * ManagedFavicon Component\n * \n * Server component that renders favicon link tags using the project's logo.\n * When a logo is uploaded in Project Settings, it's automatically synced\n * to the 'favicon' slot in site_managed_images.\n * \n * Usage:\n * ```tsx\n * import { ManagedFavicon } from '@uptrade/site-kit/images'\n * \n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <head>\n * <ManagedFavicon />\n * </head>\n * <body>{children}</body>\n * </html>\n * )\n * }\n * ```\n * \n * Supports:\n * - SVG favicons (best for modern browsers, scales perfectly)\n * - PNG favicons with multiple sizes\n * - Apple touch icons\n * - Theme color for mobile browsers\n */\n\nimport React from 'react'\n\nexport interface ManagedFaviconProps {\n /**\n * API key for Portal API authentication\n * Defaults to NEXT_PUBLIC_UPTRADE_API_KEY env var\n */\n apiKey?: string\n \n /**\n * API URL (defaults to https://api.uptrademedia.com)\n */\n apiUrl?: string\n \n /**\n * Fallback favicon URL if no managed favicon is set\n */\n fallback?: string\n \n /**\n * Theme color for mobile browser chrome\n * Defaults to #4bbf39 (Uptrade brand primary)\n */\n themeColor?: string\n}\n\ninterface FaviconData {\n public_url?: string\n mime_type?: string\n is_placeholder?: boolean\n}\n\n/** If url is relative (e.g. /uploads/... from API), resolve against base so production can load it. */\nfunction toAbsoluteFaviconUrl(url: string | undefined, apiBase: string): string | undefined {\n if (!url) return undefined\n if (url.startsWith('http://') || url.startsWith('https://')) return url\n const base = apiBase.replace(/\\/$/, '')\n return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`\n}\n\n/**\n * Server-side fetch of favicon data.\n * Exported for use in generateMetadata (single source for favicon URL).\n */\nexport async function fetchFaviconData(apiUrl: string, apiKey: string): Promise<FaviconData | null> {\n try {\n const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 3600 }, // Cache 1hr – favicon rarely changes, allows static generation\n })\n\n if (!res.ok) {\n console.warn('[ManagedFavicon] Failed to fetch:', res.status)\n return null\n }\n\n const data = await res.json()\n if (data.image && !data.is_placeholder) {\n const raw = data.image.public_url || data.image.external_url\n const public_url = toAbsoluteFaviconUrl(raw, apiUrl)\n return {\n public_url: public_url || undefined,\n mime_type: data.image.file?.mime_type,\n is_placeholder: false,\n }\n }\n return null\n } catch (err) {\n console.error('[ManagedFavicon] Error fetching favicon:', err)\n return null\n }\n}\n\n/**\n * Return the managed favicon URL for the project (from API key).\n * Use in generateMetadata to set metadata.icons so favicon is consistent across all sites.\n */\nexport async function getManagedFaviconUrl(\n apiKey?: string,\n apiUrl: string = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com'\n): Promise<string | null> {\n const key = apiKey ?? process.env.NEXT_PUBLIC_UPTRADE_API_KEY ?? process.env.UPTRADE_API_KEY\n if (!key) return null\n const data = await fetchFaviconData(apiUrl, key)\n return data?.public_url ?? null\n}\n\nexport async function ManagedFavicon({\n apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com',\n fallback = '/favicon.ico',\n themeColor = '#4bbf39',\n}: ManagedFaviconProps) {\n // Fetch favicon data during SSR\n const faviconData = apiKey && apiUrl ? await fetchFaviconData(apiUrl, apiKey) : null\n \n const faviconUrl = faviconData?.public_url || fallback\n const mimeType = faviconData?.mime_type || 'image/x-icon'\n const isSvg = mimeType === 'image/svg+xml' || faviconUrl.endsWith('.svg')\n\n return (\n <>\n {/* Primary favicon */}\n {isSvg ? (\n <link rel=\"icon\" type=\"image/svg+xml\" href={faviconUrl} />\n ) : (\n <>\n <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href={faviconUrl} />\n <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href={faviconUrl} />\n </>\n )}\n \n {/* Apple touch icon (for iOS home screen) */}\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href={faviconUrl} />\n \n {/* Theme color for mobile browsers */}\n <meta name=\"theme-color\" content={themeColor} />\n </>\n )\n}"]}
|
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
|
|
5
5
|
// src/images/ManagedFavicon.tsx
|
|
6
|
+
function toAbsoluteFaviconUrl(url, apiBase) {
|
|
7
|
+
if (!url) return void 0;
|
|
8
|
+
if (url.startsWith("http://") || url.startsWith("https://")) return url;
|
|
9
|
+
const base = apiBase.replace(/\/$/, "");
|
|
10
|
+
return url.startsWith("/") ? `${base}${url}` : `${base}/${url}`;
|
|
11
|
+
}
|
|
6
12
|
async function fetchFaviconData(apiUrl, apiKey) {
|
|
7
13
|
try {
|
|
8
14
|
const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {
|
|
@@ -19,8 +25,10 @@ async function fetchFaviconData(apiUrl, apiKey) {
|
|
|
19
25
|
}
|
|
20
26
|
const data = await res.json();
|
|
21
27
|
if (data.image && !data.is_placeholder) {
|
|
28
|
+
const raw = data.image.public_url || data.image.external_url;
|
|
29
|
+
const public_url = toAbsoluteFaviconUrl(raw, apiUrl);
|
|
22
30
|
return {
|
|
23
|
-
public_url:
|
|
31
|
+
public_url: public_url || void 0,
|
|
24
32
|
mime_type: data.image.file?.mime_type,
|
|
25
33
|
is_placeholder: false
|
|
26
34
|
};
|
|
@@ -60,5 +68,5 @@ async function ManagedFavicon({
|
|
|
60
68
|
exports.ManagedFavicon = ManagedFavicon;
|
|
61
69
|
exports.fetchFaviconData = fetchFaviconData;
|
|
62
70
|
exports.getManagedFaviconUrl = getManagedFaviconUrl;
|
|
63
|
-
//# sourceMappingURL=chunk-
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
71
|
+
//# sourceMappingURL=chunk-KHP4AK3O.js.map
|
|
72
|
+
//# sourceMappingURL=chunk-KHP4AK3O.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/images/ManagedFavicon.tsx"],"names":["jsxs","Fragment","jsx"],"mappings":";;;;;AA+DA,SAAS,oBAAA,CAAqB,KAAyB,OAAA,EAAqC;AAC1F,EAAA,IAAI,CAAC,KAAK,OAAO,MAAA;AACjB,EAAA,IAAI,GAAA,CAAI,WAAW,SAAS,CAAA,IAAK,IAAI,UAAA,CAAW,UAAU,GAAG,OAAO,GAAA;AACpE,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,KAAA,EAAO,EAAE,CAAA;AACtC,EAAA,OAAO,GAAA,CAAI,UAAA,CAAW,GAAG,CAAA,GAAI,CAAA,EAAG,IAAI,CAAA,EAAG,GAAG,CAAA,CAAA,GAAK,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,GAAG,CAAA,CAAA;AAC/D;AAMA,eAAsB,gBAAA,CAAiB,QAAgB,MAAA,EAA6C;AAClG,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,CAAA,EAAG,MAAM,CAAA,2BAAA,CAAA,EAA+B;AAAA,MAC9D,OAAA,EAAS;AAAA,QACP,cAAA,EAAgB,kBAAA;AAAA,QAChB,WAAA,EAAa;AAAA,OACf;AAAA,MACA,IAAA,EAAM,EAAE,UAAA,EAAY,IAAA;AAAK;AAAA,KAC1B,CAAA;AAED,IAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,MAAA,OAAA,CAAQ,IAAA,CAAK,mCAAA,EAAqC,GAAA,CAAI,MAAM,CAAA;AAC5D,MAAA,OAAO,IAAA;AAAA,IACT;AAEA,IAAA,MAAM,IAAA,GAAO,MAAM,GAAA,CAAI,IAAA,EAAK;AAC5B,IAAA,IAAI,IAAA,CAAK,KAAA,IAAS,CAAC,IAAA,CAAK,cAAA,EAAgB;AACtC,MAAA,MAAM,GAAA,GAAM,IAAA,CAAK,KAAA,CAAM,UAAA,IAAc,KAAK,KAAA,CAAM,YAAA;AAChD,MAAA,MAAM,UAAA,GAAa,oBAAA,CAAqB,GAAA,EAAK,MAAM,CAAA;AACnD,MAAA,OAAO;AAAA,QACL,YAAY,UAAA,IAAc,KAAA,CAAA;AAAA,QAC1B,SAAA,EAAW,IAAA,CAAK,KAAA,CAAM,IAAA,EAAM,SAAA;AAAA,QAC5B,cAAA,EAAgB;AAAA,OAClB;AAAA,IACF;AACA,IAAA,OAAO,IAAA;AAAA,EACT,SAAS,GAAA,EAAK;AACZ,IAAA,OAAA,CAAQ,KAAA,CAAM,4CAA4C,GAAG,CAAA;AAC7D,IAAA,OAAO,IAAA;AAAA,EACT;AACF;AAMA,eAAsB,qBACpB,MAAA,EACA,MAAA,GAAiB,OAAA,CAAQ,GAAA,CAAI,+BAA+B,8BAAA,EACpC;AACxB,EAAA,MAAM,MAAM,MAAA,IAAU,OAAA,CAAQ,GAAA,CAAI,2BAAA,IAA+B,QAAQ,GAAA,CAAI,eAAA;AAC7E,EAAA,IAAI,CAAC,KAAK,OAAO,IAAA;AACjB,EAAA,MAAM,IAAA,GAAO,MAAM,gBAAA,CAAiB,MAAA,EAAQ,GAAG,CAAA;AAC/C,EAAA,OAAO,MAAM,UAAA,IAAc,IAAA;AAC7B;AAEA,eAAsB,cAAA,CAAe;AAAA,EACnC,MAAA,GAAS,QAAQ,GAAA,CAAI,2BAAA;AAAA,EACrB,MAAA,GAAS,OAAA,CAAQ,GAAA,CAAI,2BAAA,IAA+B,8BAAA;AAAA,EACpD,QAAA,GAAW,cAAA;AAAA,EACX,UAAA,GAAa;AACf,CAAA,EAAwB;AAEtB,EAAA,MAAM,cAAc,MAAA,IAAU,MAAA,GAAS,MAAM,gBAAA,CAAiB,MAAA,EAAQ,MAAM,CAAA,GAAI,IAAA;AAEhF,EAAA,MAAM,UAAA,GAAa,aAAa,UAAA,IAAc,QAAA;AAC9C,EAAA,MAAM,QAAA,GAAW,aAAa,SAAA,IAAa,cAAA;AAC3C,EAAA,MAAM,KAAA,GAAQ,QAAA,KAAa,eAAA,IAAmB,UAAA,CAAW,SAAS,MAAM,CAAA;AAExE,EAAA,uBACEA,eAAA,CAAAC,mBAAA,EAAA,EAEG,QAAA,EAAA;AAAA,IAAA,KAAA,mBACCC,cAAA,CAAC,UAAK,GAAA,EAAI,MAAA,EAAO,MAAK,eAAA,EAAgB,IAAA,EAAM,UAAA,EAAY,CAAA,mBAExDF,eAAA,CAAAC,mBAAA,EAAA,EACE,QAAA,EAAA;AAAA,sBAAAC,cAAA,CAAC,MAAA,EAAA,EAAK,KAAI,MAAA,EAAO,IAAA,EAAK,aAAY,KAAA,EAAM,OAAA,EAAQ,MAAM,UAAA,EAAY,CAAA;AAAA,sBAClEA,cAAA,CAAC,UAAK,GAAA,EAAI,MAAA,EAAO,MAAK,WAAA,EAAY,KAAA,EAAM,OAAA,EAAQ,IAAA,EAAM,UAAA,EAAY;AAAA,KAAA,EACpE,CAAA;AAAA,mCAID,MAAA,EAAA,EAAK,GAAA,EAAI,oBAAmB,KAAA,EAAM,SAAA,EAAU,MAAM,UAAA,EAAY,CAAA;AAAA,oBAG/DA,cAAA,CAAC,MAAA,EAAA,EAAK,IAAA,EAAK,aAAA,EAAc,SAAS,UAAA,EAAY;AAAA,GAAA,EAChD,CAAA;AAEJ","file":"chunk-KHP4AK3O.js","sourcesContent":["/**\n * ManagedFavicon Component\n * \n * Server component that renders favicon link tags using the project's logo.\n * When a logo is uploaded in Project Settings, it's automatically synced\n * to the 'favicon' slot in site_managed_images.\n * \n * Usage:\n * ```tsx\n * import { ManagedFavicon } from '@uptrade/site-kit/images'\n * \n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <head>\n * <ManagedFavicon />\n * </head>\n * <body>{children}</body>\n * </html>\n * )\n * }\n * ```\n * \n * Supports:\n * - SVG favicons (best for modern browsers, scales perfectly)\n * - PNG favicons with multiple sizes\n * - Apple touch icons\n * - Theme color for mobile browsers\n */\n\nimport React from 'react'\n\nexport interface ManagedFaviconProps {\n /**\n * API key for Portal API authentication\n * Defaults to NEXT_PUBLIC_UPTRADE_API_KEY env var\n */\n apiKey?: string\n \n /**\n * API URL (defaults to https://api.uptrademedia.com)\n */\n apiUrl?: string\n \n /**\n * Fallback favicon URL if no managed favicon is set\n */\n fallback?: string\n \n /**\n * Theme color for mobile browser chrome\n * Defaults to #4bbf39 (Uptrade brand primary)\n */\n themeColor?: string\n}\n\ninterface FaviconData {\n public_url?: string\n mime_type?: string\n is_placeholder?: boolean\n}\n\n/** If url is relative (e.g. /uploads/... from API), resolve against base so production can load it. */\nfunction toAbsoluteFaviconUrl(url: string | undefined, apiBase: string): string | undefined {\n if (!url) return undefined\n if (url.startsWith('http://') || url.startsWith('https://')) return url\n const base = apiBase.replace(/\\/$/, '')\n return url.startsWith('/') ? `${base}${url}` : `${base}/${url}`\n}\n\n/**\n * Server-side fetch of favicon data.\n * Exported for use in generateMetadata (single source for favicon URL).\n */\nexport async function fetchFaviconData(apiUrl: string, apiKey: string): Promise<FaviconData | null> {\n try {\n const res = await fetch(`${apiUrl}/public/images/slot/favicon`, {\n headers: {\n 'Content-Type': 'application/json',\n 'x-api-key': apiKey,\n },\n next: { revalidate: 3600 }, // Cache 1hr – favicon rarely changes, allows static generation\n })\n\n if (!res.ok) {\n console.warn('[ManagedFavicon] Failed to fetch:', res.status)\n return null\n }\n\n const data = await res.json()\n if (data.image && !data.is_placeholder) {\n const raw = data.image.public_url || data.image.external_url\n const public_url = toAbsoluteFaviconUrl(raw, apiUrl)\n return {\n public_url: public_url || undefined,\n mime_type: data.image.file?.mime_type,\n is_placeholder: false,\n }\n }\n return null\n } catch (err) {\n console.error('[ManagedFavicon] Error fetching favicon:', err)\n return null\n }\n}\n\n/**\n * Return the managed favicon URL for the project (from API key).\n * Use in generateMetadata to set metadata.icons so favicon is consistent across all sites.\n */\nexport async function getManagedFaviconUrl(\n apiKey?: string,\n apiUrl: string = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com'\n): Promise<string | null> {\n const key = apiKey ?? process.env.NEXT_PUBLIC_UPTRADE_API_KEY ?? process.env.UPTRADE_API_KEY\n if (!key) return null\n const data = await fetchFaviconData(apiUrl, key)\n return data?.public_url ?? null\n}\n\nexport async function ManagedFavicon({\n apiKey = process.env.NEXT_PUBLIC_UPTRADE_API_KEY,\n apiUrl = process.env.NEXT_PUBLIC_UPTRADE_API_URL || 'https://api.uptrademedia.com',\n fallback = '/favicon.ico',\n themeColor = '#4bbf39',\n}: ManagedFaviconProps) {\n // Fetch favicon data during SSR\n const faviconData = apiKey && apiUrl ? await fetchFaviconData(apiUrl, apiKey) : null\n \n const faviconUrl = faviconData?.public_url || fallback\n const mimeType = faviconData?.mime_type || 'image/x-icon'\n const isSvg = mimeType === 'image/svg+xml' || faviconUrl.endsWith('.svg')\n\n return (\n <>\n {/* Primary favicon */}\n {isSvg ? (\n <link rel=\"icon\" type=\"image/svg+xml\" href={faviconUrl} />\n ) : (\n <>\n <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href={faviconUrl} />\n <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href={faviconUrl} />\n </>\n )}\n \n {/* Apple touch icon (for iOS home screen) */}\n <link rel=\"apple-touch-icon\" sizes=\"180x180\" href={faviconUrl} />\n \n {/* Theme color for mobile browsers */}\n <meta name=\"theme-color\" content={themeColor} />\n </>\n )\n}"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
2
|
-
import React5, { useState, useEffect, useMemo, useCallback } from 'react';
|
|
2
|
+
import React5, { useState, useEffect, useMemo, useRef, useCallback } from 'react';
|
|
3
3
|
|
|
4
4
|
// src/commerce/utils.ts
|
|
5
5
|
function formatPrice(amount, currency = "USD") {
|
|
@@ -248,6 +248,24 @@ async function registerForEvent(eventId, scheduleId, customer) {
|
|
|
248
248
|
return { success: false, error: "Network error. Please try again." };
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
|
+
async function fetchProcessorConfig() {
|
|
252
|
+
const { apiUrl, apiKey } = getApiConfig();
|
|
253
|
+
if (!apiKey) return null;
|
|
254
|
+
try {
|
|
255
|
+
const response = await fetch(`${apiUrl}/api/public/commerce/processor-config`, {
|
|
256
|
+
method: "POST",
|
|
257
|
+
headers: {
|
|
258
|
+
"Content-Type": "application/json",
|
|
259
|
+
"x-api-key": apiKey
|
|
260
|
+
},
|
|
261
|
+
body: JSON.stringify({})
|
|
262
|
+
});
|
|
263
|
+
if (!response.ok) return null;
|
|
264
|
+
return await response.json();
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
251
269
|
async function createCheckoutSession(optionsOrOfferingId, legacyOptions) {
|
|
252
270
|
const { apiUrl, apiKey } = getApiConfig();
|
|
253
271
|
const options = typeof optionsOrOfferingId === "string" ? { offeringId: optionsOrOfferingId, ...legacyOptions } : optionsOrOfferingId;
|
|
@@ -268,8 +286,12 @@ async function createCheckoutSession(optionsOrOfferingId, legacyOptions) {
|
|
|
268
286
|
})
|
|
269
287
|
});
|
|
270
288
|
if (!response.ok) {
|
|
271
|
-
const error = await response.json();
|
|
272
|
-
|
|
289
|
+
const error = await response.json().catch(() => ({}));
|
|
290
|
+
const message = error?.message || (typeof error?.error === "string" ? error.error : null) || "Checkout failed";
|
|
291
|
+
if (typeof console !== "undefined" && console.error) {
|
|
292
|
+
console.error("[Commerce] Checkout failed:", response.status, message, error);
|
|
293
|
+
}
|
|
294
|
+
return { success: false, error: message };
|
|
273
295
|
}
|
|
274
296
|
const result = await response.json();
|
|
275
297
|
return {
|
|
@@ -2805,6 +2827,17 @@ function CalendarView({
|
|
|
2805
2827
|
}, children: "Loading..." })
|
|
2806
2828
|
] });
|
|
2807
2829
|
}
|
|
2830
|
+
function loadSquareSDK(environment) {
|
|
2831
|
+
return new Promise((resolve, reject) => {
|
|
2832
|
+
if (typeof window === "undefined") return reject(new Error("No window"));
|
|
2833
|
+
if (window.Square) return resolve();
|
|
2834
|
+
const script = document.createElement("script");
|
|
2835
|
+
script.src = environment === "sandbox" ? "https://sandbox.web.squarecdn.com/v1/square.js" : "https://web.squarecdn.com/v1/square.js";
|
|
2836
|
+
script.onload = () => resolve();
|
|
2837
|
+
script.onerror = () => reject(new Error("Failed to load Square SDK"));
|
|
2838
|
+
document.head.appendChild(script);
|
|
2839
|
+
});
|
|
2840
|
+
}
|
|
2808
2841
|
function EventModal({
|
|
2809
2842
|
event,
|
|
2810
2843
|
schedule: propSchedule,
|
|
@@ -2828,7 +2861,56 @@ function EventModal({
|
|
|
2828
2861
|
phone: ""
|
|
2829
2862
|
});
|
|
2830
2863
|
const [additionalData, setAdditionalData] = useState({});
|
|
2864
|
+
const [processor, setProcessor] = useState(null);
|
|
2865
|
+
const [processorConfig, setProcessorConfig] = useState(null);
|
|
2866
|
+
const [squareCard, setSquareCard] = useState(null);
|
|
2867
|
+
const [cardReady, setCardReady] = useState(false);
|
|
2868
|
+
const cardContainerRef = useRef(null);
|
|
2831
2869
|
const schedule = propSchedule || event?.schedules?.[0] || event?.next_schedule;
|
|
2870
|
+
useEffect(() => {
|
|
2871
|
+
if (!isOpen) return;
|
|
2872
|
+
fetchProcessorConfig().then(async (config) => {
|
|
2873
|
+
setProcessorConfig(config);
|
|
2874
|
+
if (config?.processor) setProcessor(config.processor);
|
|
2875
|
+
if (config?.processor === "square" && config.squareAppId && config.squareLocationId) {
|
|
2876
|
+
try {
|
|
2877
|
+
await loadSquareSDK(config.squareEnvironment || "production");
|
|
2878
|
+
const Square = window.Square;
|
|
2879
|
+
const payments = Square.payments(config.squareAppId, config.squareLocationId);
|
|
2880
|
+
const card = await payments.card({
|
|
2881
|
+
style: {
|
|
2882
|
+
".input-container": {
|
|
2883
|
+
borderColor: "#d1d5db",
|
|
2884
|
+
borderRadius: "6px"
|
|
2885
|
+
},
|
|
2886
|
+
".input-container.is-focus": {
|
|
2887
|
+
borderColor: "#2563eb"
|
|
2888
|
+
},
|
|
2889
|
+
".input-container.is-error": {
|
|
2890
|
+
borderColor: "#dc2626"
|
|
2891
|
+
}
|
|
2892
|
+
}
|
|
2893
|
+
});
|
|
2894
|
+
await card.attach("#sq-card-container");
|
|
2895
|
+
setSquareCard(card);
|
|
2896
|
+
setCardReady(true);
|
|
2897
|
+
} catch (err) {
|
|
2898
|
+
console.error("[Commerce] Square card init failed:", err);
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
return () => {
|
|
2903
|
+
setSquareCard((prev) => {
|
|
2904
|
+
if (prev) {
|
|
2905
|
+
prev.destroy?.().catch(() => {
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
return null;
|
|
2909
|
+
});
|
|
2910
|
+
setCardReady(false);
|
|
2911
|
+
setProcessorConfig(null);
|
|
2912
|
+
};
|
|
2913
|
+
}, [isOpen]);
|
|
2832
2914
|
useEffect(() => {
|
|
2833
2915
|
if (isOpen && event) {
|
|
2834
2916
|
setError(null);
|
|
@@ -2889,17 +2971,34 @@ function EventModal({
|
|
|
2889
2971
|
onError?.(result.error || "Registration failed");
|
|
2890
2972
|
}
|
|
2891
2973
|
} else {
|
|
2892
|
-
|
|
2974
|
+
let sourceId;
|
|
2975
|
+
if (squareCard && cardReady) {
|
|
2976
|
+
const tokenResult = await squareCard.tokenize();
|
|
2977
|
+
if (tokenResult.status !== "OK") {
|
|
2978
|
+
const msg = tokenResult.errors?.[0]?.message || "Card verification failed. Please check your card details.";
|
|
2979
|
+
setError(msg);
|
|
2980
|
+
onError?.(msg);
|
|
2981
|
+
setLoading(false);
|
|
2982
|
+
return;
|
|
2983
|
+
}
|
|
2984
|
+
sourceId = tokenResult.token;
|
|
2985
|
+
}
|
|
2986
|
+
const result = await createCheckoutSession({
|
|
2987
|
+
offeringId: event.id,
|
|
2893
2988
|
scheduleId: schedule.id,
|
|
2894
2989
|
quantity,
|
|
2895
2990
|
customer: {
|
|
2896
2991
|
...customer,
|
|
2897
2992
|
...additionalData
|
|
2898
2993
|
},
|
|
2994
|
+
sourceId,
|
|
2899
2995
|
successUrl: window.location.href + "?registration=success",
|
|
2900
2996
|
cancelUrl: window.location.href
|
|
2901
2997
|
});
|
|
2902
|
-
if (result.success && result.payment_url) {
|
|
2998
|
+
if (result.success && !result.payment_url) {
|
|
2999
|
+
setSuccess(true);
|
|
3000
|
+
onSuccess?.(result);
|
|
3001
|
+
} else if (result.success && result.payment_url) {
|
|
2903
3002
|
window.location.href = result.payment_url;
|
|
2904
3003
|
} else {
|
|
2905
3004
|
setError(result.error || "Checkout failed");
|
|
@@ -3223,6 +3322,30 @@ function EventModal({
|
|
|
3223
3322
|
)
|
|
3224
3323
|
] }, field.name))
|
|
3225
3324
|
] }),
|
|
3325
|
+
!isFree && processor === "square" && /* @__PURE__ */ jsxs("div", { style: { marginTop: "1rem" }, children: [
|
|
3326
|
+
/* @__PURE__ */ jsx("label", { style: {
|
|
3327
|
+
display: "block",
|
|
3328
|
+
fontSize: "0.875rem",
|
|
3329
|
+
fontWeight: 500,
|
|
3330
|
+
color: "#374151",
|
|
3331
|
+
marginBottom: "0.375rem"
|
|
3332
|
+
}, children: "Card Details *" }),
|
|
3333
|
+
/* @__PURE__ */ jsx(
|
|
3334
|
+
"div",
|
|
3335
|
+
{
|
|
3336
|
+
id: "sq-card-container",
|
|
3337
|
+
ref: cardContainerRef,
|
|
3338
|
+
style: {
|
|
3339
|
+
minHeight: "42px",
|
|
3340
|
+
border: "1px solid #d1d5db",
|
|
3341
|
+
borderRadius: "6px",
|
|
3342
|
+
padding: "0.5rem 0.75rem",
|
|
3343
|
+
background: "#fff"
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
),
|
|
3347
|
+
!cardReady && /* @__PURE__ */ jsx("p", { style: { fontSize: "0.75rem", color: "#9ca3af", marginTop: "0.25rem" }, children: "Loading card form..." })
|
|
3348
|
+
] }),
|
|
3226
3349
|
error && /* @__PURE__ */ jsx("div", { style: {
|
|
3227
3350
|
marginTop: "1rem",
|
|
3228
3351
|
padding: "0.75rem",
|
|
@@ -3236,7 +3359,7 @@ function EventModal({
|
|
|
3236
3359
|
"button",
|
|
3237
3360
|
{
|
|
3238
3361
|
type: "submit",
|
|
3239
|
-
disabled: loading,
|
|
3362
|
+
disabled: loading || !isFree && processor === "square" && !cardReady,
|
|
3240
3363
|
style: {
|
|
3241
3364
|
width: "100%",
|
|
3242
3365
|
marginTop: "1.25rem",
|
|
@@ -3245,19 +3368,26 @@ function EventModal({
|
|
|
3245
3368
|
fontWeight: 600,
|
|
3246
3369
|
borderRadius: "8px",
|
|
3247
3370
|
border: "none",
|
|
3248
|
-
background: loading ? "#93c5fd" : "#2563eb",
|
|
3371
|
+
background: loading || !isFree && processor === "square" && !cardReady ? "#93c5fd" : "#2563eb",
|
|
3249
3372
|
color: "white",
|
|
3250
|
-
cursor: loading ? "not-allowed" : "pointer"
|
|
3373
|
+
cursor: loading || !isFree && processor === "square" && !cardReady ? "not-allowed" : "pointer"
|
|
3251
3374
|
},
|
|
3252
3375
|
children: loading ? "Processing..." : isFree ? "Register Free" : `Pay ${formatPrice(total, event.currency)}`
|
|
3253
3376
|
}
|
|
3254
3377
|
),
|
|
3255
|
-
!isFree && /* @__PURE__ */
|
|
3378
|
+
!isFree && /* @__PURE__ */ jsxs("p", { style: {
|
|
3256
3379
|
textAlign: "center",
|
|
3257
3380
|
fontSize: "0.75rem",
|
|
3258
3381
|
color: "#666",
|
|
3259
3382
|
margin: "0.75rem 0 0"
|
|
3260
|
-
}, children:
|
|
3383
|
+
}, children: [
|
|
3384
|
+
"\u{1F512} Secure checkout via ",
|
|
3385
|
+
processor === "square" ? "Square" : processor === "stripe" ? "Stripe" : "secure payment"
|
|
3386
|
+
] }),
|
|
3387
|
+
!isFree && processor && /* @__PURE__ */ jsxs("p", { style: { fontSize: "0.625rem", color: "#999", margin: "0.25rem 0 0", textAlign: "center" }, children: [
|
|
3388
|
+
"Powered by ",
|
|
3389
|
+
processor === "stripe" ? "Stripe" : "Square"
|
|
3390
|
+
] })
|
|
3261
3391
|
] })
|
|
3262
3392
|
] }) })
|
|
3263
3393
|
]
|
|
@@ -3894,6 +4024,6 @@ function getApiKey() {
|
|
|
3894
4024
|
return "";
|
|
3895
4025
|
}
|
|
3896
4026
|
|
|
3897
|
-
export { CalendarView, CheckoutForm, EventCalendar, EventEmbed, EventModal, EventTile, EventsWidget, OfferingCard, OfferingList, ProductDetail, ProductEmbed, ProductGrid, ProductPage, RegistrationForm, UpcomingEvents, createCheckoutSession, fetchActiveProcessor, fetchCategories, fetchLatestOffering, fetchNextEvent, fetchOffering, fetchOfferings, fetchProductBySlug, fetchProducts, fetchProductsPublic, fetchServices, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal };
|
|
3898
|
-
//# sourceMappingURL=chunk-
|
|
3899
|
-
//# sourceMappingURL=chunk-
|
|
4027
|
+
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, fetchUpcomingEvents, formatDate, formatDateRange, formatDateTime, formatPrice, formatTime, getOfferingUrl, getRelativeTimeUntil, getSpotsRemaining, isEventSoldOut, registerForEvent, useEventModal };
|
|
4028
|
+
//# sourceMappingURL=chunk-R4OKQVFD.mjs.map
|
|
4029
|
+
//# sourceMappingURL=chunk-R4OKQVFD.mjs.map
|