@infuro/cms-core 1.0.15 → 1.0.16
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/admin.cjs +1840 -741
- package/dist/admin.cjs.map +1 -1
- package/dist/admin.d.cts +4 -0
- package/dist/admin.d.ts +4 -0
- package/dist/admin.js +1795 -681
- package/dist/admin.js.map +1 -1
- package/dist/api.cjs +577 -29
- package/dist/api.cjs.map +1 -1
- package/dist/api.d.cts +1 -1
- package/dist/api.d.ts +1 -1
- package/dist/api.js +572 -26
- package/dist/api.js.map +1 -1
- package/dist/hooks.cjs +159 -0
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.d.cts +24 -1
- package/dist/hooks.d.ts +24 -1
- package/dist/hooks.js +165 -0
- package/dist/hooks.js.map +1 -1
- package/dist/{index-BiagwMjV.d.ts → index-C85X7cc7.d.ts} +14 -2
- package/dist/{index-BQnqJ7EO.d.cts → index-h42MoUNq.d.cts} +14 -2
- package/dist/index.cjs +5225 -4305
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +89 -11
- package/dist/index.d.ts +89 -11
- package/dist/index.js +5220 -4317
- package/dist/index.js.map +1 -1
- package/dist/migrations/1775200000000-MediaDriveFolders.ts +38 -0
- package/package.json +10 -12
package/dist/hooks.cjs
CHANGED
|
@@ -30,8 +30,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
30
30
|
// src/hooks/index.ts
|
|
31
31
|
var hooks_exports = {};
|
|
32
32
|
__export(hooks_exports, {
|
|
33
|
+
CaptchaProvider: () => CaptchaProvider,
|
|
33
34
|
PluginProvider: () => PluginProvider,
|
|
34
35
|
useAnalytics: () => useAnalytics,
|
|
36
|
+
useCaptchaPayload: () => useCaptchaPayload,
|
|
35
37
|
useIsMobile: () => useIsMobile,
|
|
36
38
|
usePlugin: () => usePlugin
|
|
37
39
|
});
|
|
@@ -84,10 +86,167 @@ function usePlugin(name) {
|
|
|
84
86
|
if (!ctx) return void 0;
|
|
85
87
|
return ctx.getPlugin(name);
|
|
86
88
|
}
|
|
89
|
+
|
|
90
|
+
// src/components/CaptchaProvider.tsx
|
|
91
|
+
var import_react4 = require("react");
|
|
92
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
93
|
+
var CaptchaReactContext = (0, import_react4.createContext)(null);
|
|
94
|
+
function loadScript(src) {
|
|
95
|
+
return new Promise((resolve, reject) => {
|
|
96
|
+
const base = src.split("?")[0] ?? src;
|
|
97
|
+
const existing = Array.from(document.querySelectorAll("script[src]")).some(
|
|
98
|
+
(el) => el.src.startsWith(base)
|
|
99
|
+
);
|
|
100
|
+
if (existing) {
|
|
101
|
+
resolve();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const s = document.createElement("script");
|
|
105
|
+
s.src = src;
|
|
106
|
+
s.async = true;
|
|
107
|
+
s.onload = () => resolve();
|
|
108
|
+
s.onerror = () => reject(new Error("Failed to load script"));
|
|
109
|
+
document.head.appendChild(s);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
function CaptchaProvider({ children }) {
|
|
113
|
+
const [config, setConfig] = (0, import_react4.useState)(null);
|
|
114
|
+
const [selectedProvider, setSelectedProvider] = (0, import_react4.useState)(null);
|
|
115
|
+
const [ready, setReady] = (0, import_react4.useState)(false);
|
|
116
|
+
const turnstileWidgetIdRef = (0, import_react4.useRef)(null);
|
|
117
|
+
const turnstileContainerRef = (0, import_react4.useRef)(null);
|
|
118
|
+
const turnstileTokenRef = (0, import_react4.useRef)(null);
|
|
119
|
+
(0, import_react4.useEffect)(() => {
|
|
120
|
+
let cancelled = false;
|
|
121
|
+
fetch("/api/public/captcha-config").then((r) => r.ok ? r.json() : null).then((c) => {
|
|
122
|
+
if (cancelled || !c) return;
|
|
123
|
+
setConfig(c);
|
|
124
|
+
if (c.enabled && c.activeProvider) setSelectedProvider(c.activeProvider);
|
|
125
|
+
}).catch(() => {
|
|
126
|
+
}).finally(() => {
|
|
127
|
+
if (!cancelled) setReady(true);
|
|
128
|
+
});
|
|
129
|
+
return () => {
|
|
130
|
+
cancelled = true;
|
|
131
|
+
};
|
|
132
|
+
}, []);
|
|
133
|
+
(0, import_react4.useEffect)(() => {
|
|
134
|
+
if (!config?.enabled || !selectedProvider) return;
|
|
135
|
+
const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;
|
|
136
|
+
if (!siteKeyFor) return;
|
|
137
|
+
let cancelled = false;
|
|
138
|
+
(async () => {
|
|
139
|
+
try {
|
|
140
|
+
if (selectedProvider === "recaptcha_v3") {
|
|
141
|
+
await loadScript(
|
|
142
|
+
`https://www.google.com/recaptcha/api.js?render=${encodeURIComponent(siteKeyFor)}`
|
|
143
|
+
);
|
|
144
|
+
} else {
|
|
145
|
+
await loadScript("https://challenges.cloudflare.com/turnstile/v0/api.js");
|
|
146
|
+
if (cancelled || !turnstileContainerRef.current || !window.turnstile) return;
|
|
147
|
+
if (turnstileWidgetIdRef.current) {
|
|
148
|
+
try {
|
|
149
|
+
window.turnstile.remove(turnstileWidgetIdRef.current);
|
|
150
|
+
} catch {
|
|
151
|
+
}
|
|
152
|
+
turnstileWidgetIdRef.current = null;
|
|
153
|
+
}
|
|
154
|
+
const wid = window.turnstile.render(turnstileContainerRef.current, {
|
|
155
|
+
sitekey: siteKeyFor,
|
|
156
|
+
execution: "execute",
|
|
157
|
+
callback: (token) => {
|
|
158
|
+
const p = turnstileTokenRef.current;
|
|
159
|
+
turnstileTokenRef.current = null;
|
|
160
|
+
p?.resolve(token);
|
|
161
|
+
},
|
|
162
|
+
"error-callback": () => {
|
|
163
|
+
const p = turnstileTokenRef.current;
|
|
164
|
+
turnstileTokenRef.current = null;
|
|
165
|
+
p?.reject(new Error("Turnstile error"));
|
|
166
|
+
},
|
|
167
|
+
"expired-callback": () => {
|
|
168
|
+
const p = turnstileTokenRef.current;
|
|
169
|
+
turnstileTokenRef.current = null;
|
|
170
|
+
p?.reject(new Error("Turnstile expired"));
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
turnstileWidgetIdRef.current = wid;
|
|
174
|
+
}
|
|
175
|
+
} catch {
|
|
176
|
+
}
|
|
177
|
+
})();
|
|
178
|
+
return () => {
|
|
179
|
+
cancelled = true;
|
|
180
|
+
};
|
|
181
|
+
}, [config, selectedProvider]);
|
|
182
|
+
const getCaptchaPayload = (0, import_react4.useCallback)(async () => {
|
|
183
|
+
if (!config?.enabled || !selectedProvider) return {};
|
|
184
|
+
const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;
|
|
185
|
+
if (!siteKeyFor) return {};
|
|
186
|
+
if (selectedProvider === "recaptcha_v3") {
|
|
187
|
+
if (!window.grecaptcha) return {};
|
|
188
|
+
const token2 = await new Promise((resolve, reject) => {
|
|
189
|
+
window.grecaptcha.ready(() => {
|
|
190
|
+
window.grecaptcha.execute(siteKeyFor, { action: "submit" }).then(resolve).catch(reject);
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
return { captchaToken: token2, captchaProvider: "recaptcha_v3" };
|
|
194
|
+
}
|
|
195
|
+
const wid = turnstileWidgetIdRef.current;
|
|
196
|
+
if (!wid || !window.turnstile) return {};
|
|
197
|
+
const token = await new Promise((resolve, reject) => {
|
|
198
|
+
turnstileTokenRef.current = { resolve, reject };
|
|
199
|
+
try {
|
|
200
|
+
window.turnstile.reset(wid);
|
|
201
|
+
window.turnstile.execute(wid);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
turnstileTokenRef.current = null;
|
|
204
|
+
reject(e instanceof Error ? e : new Error("Turnstile execute failed"));
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
return { captchaToken: token, captchaProvider: "turnstile" };
|
|
208
|
+
}, [config, selectedProvider]);
|
|
209
|
+
const ctx = (0, import_react4.useMemo)(
|
|
210
|
+
() => ({
|
|
211
|
+
config,
|
|
212
|
+
ready,
|
|
213
|
+
selectedProvider,
|
|
214
|
+
setSelectedProvider,
|
|
215
|
+
getCaptchaPayload
|
|
216
|
+
}),
|
|
217
|
+
[config, ready, selectedProvider, getCaptchaPayload]
|
|
218
|
+
);
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(CaptchaReactContext.Provider, { value: ctx, children: [
|
|
220
|
+
config?.enabled && config.multipleProviders ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "mb-2 text-sm text-muted-foreground max-w-md", children: [
|
|
221
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { className: "mr-2", htmlFor: "captcha-provider-select", children: "Verification" }),
|
|
222
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
223
|
+
"select",
|
|
224
|
+
{
|
|
225
|
+
id: "captcha-provider-select",
|
|
226
|
+
className: "border rounded px-2 py-1 bg-background",
|
|
227
|
+
value: selectedProvider ?? "",
|
|
228
|
+
onChange: (e) => setSelectedProvider(e.target.value),
|
|
229
|
+
children: config.availableProviders.map((p) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: p.id, children: p.id === "turnstile" ? "Cloudflare Turnstile" : "Google reCAPTCHA v3" }, p.id))
|
|
230
|
+
}
|
|
231
|
+
)
|
|
232
|
+
] }) : null,
|
|
233
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { ref: turnstileContainerRef, className: "hidden", "aria-hidden": true }),
|
|
234
|
+
children
|
|
235
|
+
] });
|
|
236
|
+
}
|
|
237
|
+
function useCaptchaPayload() {
|
|
238
|
+
const ctx = (0, import_react4.useContext)(CaptchaReactContext);
|
|
239
|
+
return (0, import_react4.useCallback)(async () => {
|
|
240
|
+
if (!ctx?.config?.enabled || !ctx.ready) return {};
|
|
241
|
+
return ctx.getCaptchaPayload();
|
|
242
|
+
}, [ctx]);
|
|
243
|
+
}
|
|
87
244
|
// Annotate the CommonJS export names for ESM import in node:
|
|
88
245
|
0 && (module.exports = {
|
|
246
|
+
CaptchaProvider,
|
|
89
247
|
PluginProvider,
|
|
90
248
|
useAnalytics,
|
|
249
|
+
useCaptchaPayload,
|
|
91
250
|
useIsMobile,
|
|
92
251
|
usePlugin
|
|
93
252
|
});
|
package/dist/hooks.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/index.ts","../src/hooks/use-mobile.ts","../src/hooks/use-analytics.ts","../src/hooks/use-plugin.ts"],"sourcesContent":["export { useIsMobile } from './use-mobile';\nexport { useAnalytics } from './use-analytics';\nexport { usePlugin, PluginProvider } from './use-plugin';\n","import { useState, useEffect } from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile(): boolean {\n const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\n\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n mql.addEventListener('change', onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener('change', onChange);\n }, []);\n\n return !!isMobile;\n}\n","import { useEffect } from 'react';\n\n/**\n * Call from app with pathname from usePathname() (next/navigation).\n * Disables tracking on admin routes when pathname starts with /admin.\n */\nexport function useAnalytics(pathname: string | null): { isAdminRoute: boolean; shouldTrack: boolean } {\n const isAdminRoute = pathname != null && pathname.startsWith('/admin');\n\n useEffect(() => {\n if (isAdminRoute && typeof window !== 'undefined') {\n if (typeof (window as unknown as { gtag?: () => void }).gtag === 'function') {\n (window as unknown as { gtag: () => void }).gtag = () => {};\n }\n if (Array.isArray((window as unknown as { dataLayer?: unknown[] }).dataLayer)) {\n (window as unknown as { dataLayer: unknown[] }).dataLayer = [];\n }\n }\n }, [isAdminRoute]);\n\n return { isAdminRoute, shouldTrack: !isAdminRoute };\n}\n","import React, { createContext, useContext, type ReactNode } from 'react';\n\nconst PluginContext = createContext<{ getPlugin: <T>(name: string) => T | undefined } | null>(null);\n\nexport function PluginProvider({\n getPlugin,\n children,\n}: {\n getPlugin: <T>(name: string) => T | undefined;\n children: ReactNode;\n}) {\n return React.createElement(PluginContext.Provider, { value: { getPlugin } }, children);\n}\n\nexport function usePlugin<T = unknown>(name: string): T | undefined {\n const ctx = useContext(PluginContext);\n if (!ctx) return undefined;\n return ctx.getPlugin<T>(name);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,mBAAoC;AAEpC,IAAM,oBAAoB;AAEnB,SAAS,cAAuB;AACrC,QAAM,CAAC,UAAU,WAAW,QAAI,uBAA8B,MAAS;AAEvE,8BAAU,MAAM;AACd,UAAM,MAAM,OAAO,WAAW,eAAe,oBAAoB,CAAC,KAAK;AACvE,UAAM,WAAW,MAAM,YAAY,OAAO,aAAa,iBAAiB;AACxE,QAAI,iBAAiB,UAAU,QAAQ;AACvC,gBAAY,OAAO,aAAa,iBAAiB;AACjD,WAAO,MAAM,IAAI,oBAAoB,UAAU,QAAQ;AAAA,EACzD,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,CAAC;AACX;;;AChBA,IAAAA,gBAA0B;AAMnB,SAAS,aAAa,UAA0E;AACrG,QAAM,eAAe,YAAY,QAAQ,SAAS,WAAW,QAAQ;AAErE,+BAAU,MAAM;AACd,QAAI,gBAAgB,OAAO,WAAW,aAAa;AACjD,UAAI,OAAQ,OAA4C,SAAS,YAAY;AAC3E,QAAC,OAA2C,OAAO,MAAM;AAAA,QAAC;AAAA,MAC5D;AACA,UAAI,MAAM,QAAS,OAAgD,SAAS,GAAG;AAC7E,QAAC,OAA+C,YAAY,CAAC;AAAA,MAC/D;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,EAAE,cAAc,aAAa,CAAC,aAAa;AACpD;;;ACrBA,IAAAC,gBAAiE;AAEjE,IAAM,oBAAgB,6BAAwE,IAAI;AAE3F,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,SAAO,cAAAC,QAAM,cAAc,cAAc,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,QAAQ;AACvF;AAEO,SAAS,UAAuB,MAA6B;AAClE,QAAM,UAAM,0BAAW,aAAa;AACpC,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,UAAa,IAAI;AAC9B;","names":["import_react","import_react","React"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/index.ts","../src/hooks/use-mobile.ts","../src/hooks/use-analytics.ts","../src/hooks/use-plugin.ts","../src/components/CaptchaProvider.tsx"],"sourcesContent":["export { useIsMobile } from './use-mobile';\nexport { useAnalytics } from './use-analytics';\nexport { usePlugin, PluginProvider } from './use-plugin';\nexport { CaptchaProvider, useCaptchaPayload } from '../components/CaptchaProvider';\n","import { useState, useEffect } from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile(): boolean {\n const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\n\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n mql.addEventListener('change', onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener('change', onChange);\n }, []);\n\n return !!isMobile;\n}\n","import { useEffect } from 'react';\n\n/**\n * Call from app with pathname from usePathname() (next/navigation).\n * Disables tracking on admin routes when pathname starts with /admin.\n */\nexport function useAnalytics(pathname: string | null): { isAdminRoute: boolean; shouldTrack: boolean } {\n const isAdminRoute = pathname != null && pathname.startsWith('/admin');\n\n useEffect(() => {\n if (isAdminRoute && typeof window !== 'undefined') {\n if (typeof (window as unknown as { gtag?: () => void }).gtag === 'function') {\n (window as unknown as { gtag: () => void }).gtag = () => {};\n }\n if (Array.isArray((window as unknown as { dataLayer?: unknown[] }).dataLayer)) {\n (window as unknown as { dataLayer: unknown[] }).dataLayer = [];\n }\n }\n }, [isAdminRoute]);\n\n return { isAdminRoute, shouldTrack: !isAdminRoute };\n}\n","import React, { createContext, useContext, type ReactNode } from 'react';\n\nconst PluginContext = createContext<{ getPlugin: <T>(name: string) => T | undefined } | null>(null);\n\nexport function PluginProvider({\n getPlugin,\n children,\n}: {\n getPlugin: <T>(name: string) => T | undefined;\n children: ReactNode;\n}) {\n return React.createElement(PluginContext.Provider, { value: { getPlugin } }, children);\n}\n\nexport function usePlugin<T = unknown>(name: string): T | undefined {\n const ctx = useContext(PluginContext);\n if (!ctx) return undefined;\n return ctx.getPlugin<T>(name);\n}\n","'use client';\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { CaptchaPublicConfig, CaptchaProviderId } from '../plugins/captcha';\n\ndeclare global {\n interface Window {\n turnstile?: {\n render: (el: HTMLElement | string, opts: Record<string, unknown>) => string;\n execute: (id: string) => void;\n reset: (id: string) => void;\n remove: (id: string) => void;\n };\n grecaptcha?: {\n ready: (fn: () => void) => void;\n execute: (siteKey: string, opts: { action: string }) => Promise<string>;\n };\n }\n}\n\ntype CaptchaCtx = {\n config: CaptchaPublicConfig | null;\n ready: boolean;\n selectedProvider: CaptchaProviderId | null;\n setSelectedProvider: (p: CaptchaProviderId) => void;\n getCaptchaPayload: () => Promise<Record<string, string>>;\n};\n\nconst CaptchaReactContext = createContext<CaptchaCtx | null>(null);\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const base = src.split('?')[0] ?? src;\n const existing = Array.from(document.querySelectorAll('script[src]')).some((el) =>\n (el as HTMLScriptElement).src.startsWith(base)\n );\n if (existing) {\n resolve();\n return;\n }\n const s = document.createElement('script');\n s.src = src;\n s.async = true;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error('Failed to load script'));\n document.head.appendChild(s);\n });\n}\n\nexport function CaptchaProvider({ children }: { children: React.ReactNode }) {\n const [config, setConfig] = useState<CaptchaPublicConfig | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<CaptchaProviderId | null>(null);\n const [ready, setReady] = useState(false);\n const turnstileWidgetIdRef = useRef<string | null>(null);\n const turnstileContainerRef = useRef<HTMLDivElement | null>(null);\n const turnstileTokenRef = useRef<{\n resolve: (t: string) => void;\n reject: (e: Error) => void;\n } | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n fetch('/api/public/captcha-config')\n .then((r) => (r.ok ? r.json() : null))\n .then((c: CaptchaPublicConfig | null) => {\n if (cancelled || !c) return;\n setConfig(c);\n if (c.enabled && c.activeProvider) setSelectedProvider(c.activeProvider);\n })\n .catch(() => {})\n .finally(() => {\n if (!cancelled) setReady(true);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (!config?.enabled || !selectedProvider) return;\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\n if (!siteKeyFor) return;\n\n let cancelled = false;\n (async () => {\n try {\n if (selectedProvider === 'recaptcha_v3') {\n await loadScript(\n `https://www.google.com/recaptcha/api.js?render=${encodeURIComponent(siteKeyFor)}`\n );\n } else {\n await loadScript('https://challenges.cloudflare.com/turnstile/v0/api.js');\n if (cancelled || !turnstileContainerRef.current || !window.turnstile) return;\n if (turnstileWidgetIdRef.current) {\n try {\n window.turnstile.remove(turnstileWidgetIdRef.current);\n } catch {\n /* ignore */\n }\n turnstileWidgetIdRef.current = null;\n }\n const wid = window.turnstile.render(turnstileContainerRef.current, {\n sitekey: siteKeyFor,\n execution: 'execute',\n callback: (token: string) => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.resolve(token);\n },\n 'error-callback': () => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.reject(new Error('Turnstile error'));\n },\n 'expired-callback': () => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.reject(new Error('Turnstile expired'));\n },\n });\n turnstileWidgetIdRef.current = wid;\n }\n } catch {\n /* ignore */\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [config, selectedProvider]);\n\n const getCaptchaPayload = useCallback(async (): Promise<Record<string, string>> => {\n if (!config?.enabled || !selectedProvider) return {};\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\n if (!siteKeyFor) return {};\n\n if (selectedProvider === 'recaptcha_v3') {\n if (!window.grecaptcha) return {};\n const token = await new Promise<string>((resolve, reject) => {\n window.grecaptcha!.ready(() => {\n window.grecaptcha!.execute(siteKeyFor, { action: 'submit' }).then(resolve).catch(reject);\n });\n });\n return { captchaToken: token, captchaProvider: 'recaptcha_v3' };\n }\n\n const wid = turnstileWidgetIdRef.current;\n if (!wid || !window.turnstile) return {};\n const token = await new Promise<string>((resolve, reject) => {\n turnstileTokenRef.current = { resolve, reject };\n try {\n window.turnstile!.reset(wid);\n window.turnstile!.execute(wid);\n } catch (e) {\n turnstileTokenRef.current = null;\n reject(e instanceof Error ? e : new Error('Turnstile execute failed'));\n }\n });\n return { captchaToken: token, captchaProvider: 'turnstile' };\n }, [config, selectedProvider]);\n\n const ctx = useMemo<CaptchaCtx>(\n () => ({\n config,\n ready,\n selectedProvider,\n setSelectedProvider,\n getCaptchaPayload,\n }),\n [config, ready, selectedProvider, getCaptchaPayload]\n );\n\n return (\n <CaptchaReactContext.Provider value={ctx}>\n {config?.enabled && config.multipleProviders ? (\n <div className=\"mb-2 text-sm text-muted-foreground max-w-md\">\n <label className=\"mr-2\" htmlFor=\"captcha-provider-select\">\n Verification\n </label>\n <select\n id=\"captcha-provider-select\"\n className=\"border rounded px-2 py-1 bg-background\"\n value={selectedProvider ?? ''}\n onChange={(e) => setSelectedProvider(e.target.value as CaptchaProviderId)}\n >\n {config.availableProviders.map((p) => (\n <option key={p.id} value={p.id}>\n {p.id === 'turnstile' ? 'Cloudflare Turnstile' : 'Google reCAPTCHA v3'}\n </option>\n ))}\n </select>\n </div>\n ) : null}\n <div ref={turnstileContainerRef} className=\"hidden\" aria-hidden />\n {children}\n </CaptchaReactContext.Provider>\n );\n}\n\n/** Returns a function that resolves to captcha fields for JSON bodies, or {} if disabled / not ready. */\nexport function useCaptchaPayload(): () => Promise<Record<string, string>> {\n const ctx = useContext(CaptchaReactContext);\n return useCallback(async () => {\n if (!ctx?.config?.enabled || !ctx.ready) return {};\n return ctx.getCaptchaPayload();\n }, [ctx]);\n}\n"],"mappings":"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","names":["import_react","import_react","React","import_react","token"]}
|
package/dist/hooks.d.cts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
declare function useIsMobile(): boolean;
|
|
4
5
|
|
|
@@ -19,4 +20,26 @@ declare function PluginProvider({ getPlugin, children, }: {
|
|
|
19
20
|
} | null>>;
|
|
20
21
|
declare function usePlugin<T = unknown>(name: string): T | undefined;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
declare global {
|
|
24
|
+
interface Window {
|
|
25
|
+
turnstile?: {
|
|
26
|
+
render: (el: HTMLElement | string, opts: Record<string, unknown>) => string;
|
|
27
|
+
execute: (id: string) => void;
|
|
28
|
+
reset: (id: string) => void;
|
|
29
|
+
remove: (id: string) => void;
|
|
30
|
+
};
|
|
31
|
+
grecaptcha?: {
|
|
32
|
+
ready: (fn: () => void) => void;
|
|
33
|
+
execute: (siteKey: string, opts: {
|
|
34
|
+
action: string;
|
|
35
|
+
}) => Promise<string>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
declare function CaptchaProvider({ children }: {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}): react_jsx_runtime.JSX.Element;
|
|
42
|
+
/** Returns a function that resolves to captcha fields for JSON bodies, or {} if disabled / not ready. */
|
|
43
|
+
declare function useCaptchaPayload(): () => Promise<Record<string, string>>;
|
|
44
|
+
|
|
45
|
+
export { CaptchaProvider, PluginProvider, useAnalytics, useCaptchaPayload, useIsMobile, usePlugin };
|
package/dist/hooks.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { ReactNode } from 'react';
|
|
2
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
3
|
|
|
3
4
|
declare function useIsMobile(): boolean;
|
|
4
5
|
|
|
@@ -19,4 +20,26 @@ declare function PluginProvider({ getPlugin, children, }: {
|
|
|
19
20
|
} | null>>;
|
|
20
21
|
declare function usePlugin<T = unknown>(name: string): T | undefined;
|
|
21
22
|
|
|
22
|
-
|
|
23
|
+
declare global {
|
|
24
|
+
interface Window {
|
|
25
|
+
turnstile?: {
|
|
26
|
+
render: (el: HTMLElement | string, opts: Record<string, unknown>) => string;
|
|
27
|
+
execute: (id: string) => void;
|
|
28
|
+
reset: (id: string) => void;
|
|
29
|
+
remove: (id: string) => void;
|
|
30
|
+
};
|
|
31
|
+
grecaptcha?: {
|
|
32
|
+
ready: (fn: () => void) => void;
|
|
33
|
+
execute: (siteKey: string, opts: {
|
|
34
|
+
action: string;
|
|
35
|
+
}) => Promise<string>;
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
declare function CaptchaProvider({ children }: {
|
|
40
|
+
children: React.ReactNode;
|
|
41
|
+
}): react_jsx_runtime.JSX.Element;
|
|
42
|
+
/** Returns a function that resolves to captcha fields for JSON bodies, or {} if disabled / not ready. */
|
|
43
|
+
declare function useCaptchaPayload(): () => Promise<Record<string, string>>;
|
|
44
|
+
|
|
45
|
+
export { CaptchaProvider, PluginProvider, useAnalytics, useCaptchaPayload, useIsMobile, usePlugin };
|
package/dist/hooks.js
CHANGED
|
@@ -45,9 +45,174 @@ function usePlugin(name) {
|
|
|
45
45
|
if (!ctx) return void 0;
|
|
46
46
|
return ctx.getPlugin(name);
|
|
47
47
|
}
|
|
48
|
+
|
|
49
|
+
// src/components/CaptchaProvider.tsx
|
|
50
|
+
import {
|
|
51
|
+
createContext as createContext2,
|
|
52
|
+
useCallback,
|
|
53
|
+
useContext as useContext2,
|
|
54
|
+
useEffect as useEffect3,
|
|
55
|
+
useMemo,
|
|
56
|
+
useRef,
|
|
57
|
+
useState as useState2
|
|
58
|
+
} from "react";
|
|
59
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
60
|
+
var CaptchaReactContext = createContext2(null);
|
|
61
|
+
function loadScript(src) {
|
|
62
|
+
return new Promise((resolve, reject) => {
|
|
63
|
+
const base = src.split("?")[0] ?? src;
|
|
64
|
+
const existing = Array.from(document.querySelectorAll("script[src]")).some(
|
|
65
|
+
(el) => el.src.startsWith(base)
|
|
66
|
+
);
|
|
67
|
+
if (existing) {
|
|
68
|
+
resolve();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const s = document.createElement("script");
|
|
72
|
+
s.src = src;
|
|
73
|
+
s.async = true;
|
|
74
|
+
s.onload = () => resolve();
|
|
75
|
+
s.onerror = () => reject(new Error("Failed to load script"));
|
|
76
|
+
document.head.appendChild(s);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
function CaptchaProvider({ children }) {
|
|
80
|
+
const [config, setConfig] = useState2(null);
|
|
81
|
+
const [selectedProvider, setSelectedProvider] = useState2(null);
|
|
82
|
+
const [ready, setReady] = useState2(false);
|
|
83
|
+
const turnstileWidgetIdRef = useRef(null);
|
|
84
|
+
const turnstileContainerRef = useRef(null);
|
|
85
|
+
const turnstileTokenRef = useRef(null);
|
|
86
|
+
useEffect3(() => {
|
|
87
|
+
let cancelled = false;
|
|
88
|
+
fetch("/api/public/captcha-config").then((r) => r.ok ? r.json() : null).then((c) => {
|
|
89
|
+
if (cancelled || !c) return;
|
|
90
|
+
setConfig(c);
|
|
91
|
+
if (c.enabled && c.activeProvider) setSelectedProvider(c.activeProvider);
|
|
92
|
+
}).catch(() => {
|
|
93
|
+
}).finally(() => {
|
|
94
|
+
if (!cancelled) setReady(true);
|
|
95
|
+
});
|
|
96
|
+
return () => {
|
|
97
|
+
cancelled = true;
|
|
98
|
+
};
|
|
99
|
+
}, []);
|
|
100
|
+
useEffect3(() => {
|
|
101
|
+
if (!config?.enabled || !selectedProvider) return;
|
|
102
|
+
const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;
|
|
103
|
+
if (!siteKeyFor) return;
|
|
104
|
+
let cancelled = false;
|
|
105
|
+
(async () => {
|
|
106
|
+
try {
|
|
107
|
+
if (selectedProvider === "recaptcha_v3") {
|
|
108
|
+
await loadScript(
|
|
109
|
+
`https://www.google.com/recaptcha/api.js?render=${encodeURIComponent(siteKeyFor)}`
|
|
110
|
+
);
|
|
111
|
+
} else {
|
|
112
|
+
await loadScript("https://challenges.cloudflare.com/turnstile/v0/api.js");
|
|
113
|
+
if (cancelled || !turnstileContainerRef.current || !window.turnstile) return;
|
|
114
|
+
if (turnstileWidgetIdRef.current) {
|
|
115
|
+
try {
|
|
116
|
+
window.turnstile.remove(turnstileWidgetIdRef.current);
|
|
117
|
+
} catch {
|
|
118
|
+
}
|
|
119
|
+
turnstileWidgetIdRef.current = null;
|
|
120
|
+
}
|
|
121
|
+
const wid = window.turnstile.render(turnstileContainerRef.current, {
|
|
122
|
+
sitekey: siteKeyFor,
|
|
123
|
+
execution: "execute",
|
|
124
|
+
callback: (token) => {
|
|
125
|
+
const p = turnstileTokenRef.current;
|
|
126
|
+
turnstileTokenRef.current = null;
|
|
127
|
+
p?.resolve(token);
|
|
128
|
+
},
|
|
129
|
+
"error-callback": () => {
|
|
130
|
+
const p = turnstileTokenRef.current;
|
|
131
|
+
turnstileTokenRef.current = null;
|
|
132
|
+
p?.reject(new Error("Turnstile error"));
|
|
133
|
+
},
|
|
134
|
+
"expired-callback": () => {
|
|
135
|
+
const p = turnstileTokenRef.current;
|
|
136
|
+
turnstileTokenRef.current = null;
|
|
137
|
+
p?.reject(new Error("Turnstile expired"));
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
turnstileWidgetIdRef.current = wid;
|
|
141
|
+
}
|
|
142
|
+
} catch {
|
|
143
|
+
}
|
|
144
|
+
})();
|
|
145
|
+
return () => {
|
|
146
|
+
cancelled = true;
|
|
147
|
+
};
|
|
148
|
+
}, [config, selectedProvider]);
|
|
149
|
+
const getCaptchaPayload = useCallback(async () => {
|
|
150
|
+
if (!config?.enabled || !selectedProvider) return {};
|
|
151
|
+
const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;
|
|
152
|
+
if (!siteKeyFor) return {};
|
|
153
|
+
if (selectedProvider === "recaptcha_v3") {
|
|
154
|
+
if (!window.grecaptcha) return {};
|
|
155
|
+
const token2 = await new Promise((resolve, reject) => {
|
|
156
|
+
window.grecaptcha.ready(() => {
|
|
157
|
+
window.grecaptcha.execute(siteKeyFor, { action: "submit" }).then(resolve).catch(reject);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
return { captchaToken: token2, captchaProvider: "recaptcha_v3" };
|
|
161
|
+
}
|
|
162
|
+
const wid = turnstileWidgetIdRef.current;
|
|
163
|
+
if (!wid || !window.turnstile) return {};
|
|
164
|
+
const token = await new Promise((resolve, reject) => {
|
|
165
|
+
turnstileTokenRef.current = { resolve, reject };
|
|
166
|
+
try {
|
|
167
|
+
window.turnstile.reset(wid);
|
|
168
|
+
window.turnstile.execute(wid);
|
|
169
|
+
} catch (e) {
|
|
170
|
+
turnstileTokenRef.current = null;
|
|
171
|
+
reject(e instanceof Error ? e : new Error("Turnstile execute failed"));
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
return { captchaToken: token, captchaProvider: "turnstile" };
|
|
175
|
+
}, [config, selectedProvider]);
|
|
176
|
+
const ctx = useMemo(
|
|
177
|
+
() => ({
|
|
178
|
+
config,
|
|
179
|
+
ready,
|
|
180
|
+
selectedProvider,
|
|
181
|
+
setSelectedProvider,
|
|
182
|
+
getCaptchaPayload
|
|
183
|
+
}),
|
|
184
|
+
[config, ready, selectedProvider, getCaptchaPayload]
|
|
185
|
+
);
|
|
186
|
+
return /* @__PURE__ */ jsxs(CaptchaReactContext.Provider, { value: ctx, children: [
|
|
187
|
+
config?.enabled && config.multipleProviders ? /* @__PURE__ */ jsxs("div", { className: "mb-2 text-sm text-muted-foreground max-w-md", children: [
|
|
188
|
+
/* @__PURE__ */ jsx("label", { className: "mr-2", htmlFor: "captcha-provider-select", children: "Verification" }),
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
"select",
|
|
191
|
+
{
|
|
192
|
+
id: "captcha-provider-select",
|
|
193
|
+
className: "border rounded px-2 py-1 bg-background",
|
|
194
|
+
value: selectedProvider ?? "",
|
|
195
|
+
onChange: (e) => setSelectedProvider(e.target.value),
|
|
196
|
+
children: config.availableProviders.map((p) => /* @__PURE__ */ jsx("option", { value: p.id, children: p.id === "turnstile" ? "Cloudflare Turnstile" : "Google reCAPTCHA v3" }, p.id))
|
|
197
|
+
}
|
|
198
|
+
)
|
|
199
|
+
] }) : null,
|
|
200
|
+
/* @__PURE__ */ jsx("div", { ref: turnstileContainerRef, className: "hidden", "aria-hidden": true }),
|
|
201
|
+
children
|
|
202
|
+
] });
|
|
203
|
+
}
|
|
204
|
+
function useCaptchaPayload() {
|
|
205
|
+
const ctx = useContext2(CaptchaReactContext);
|
|
206
|
+
return useCallback(async () => {
|
|
207
|
+
if (!ctx?.config?.enabled || !ctx.ready) return {};
|
|
208
|
+
return ctx.getCaptchaPayload();
|
|
209
|
+
}, [ctx]);
|
|
210
|
+
}
|
|
48
211
|
export {
|
|
212
|
+
CaptchaProvider,
|
|
49
213
|
PluginProvider,
|
|
50
214
|
useAnalytics,
|
|
215
|
+
useCaptchaPayload,
|
|
51
216
|
useIsMobile,
|
|
52
217
|
usePlugin
|
|
53
218
|
};
|
package/dist/hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/use-mobile.ts","../src/hooks/use-analytics.ts","../src/hooks/use-plugin.ts"],"sourcesContent":["import { useState, useEffect } from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile(): boolean {\n const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\n\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n mql.addEventListener('change', onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener('change', onChange);\n }, []);\n\n return !!isMobile;\n}\n","import { useEffect } from 'react';\n\n/**\n * Call from app with pathname from usePathname() (next/navigation).\n * Disables tracking on admin routes when pathname starts with /admin.\n */\nexport function useAnalytics(pathname: string | null): { isAdminRoute: boolean; shouldTrack: boolean } {\n const isAdminRoute = pathname != null && pathname.startsWith('/admin');\n\n useEffect(() => {\n if (isAdminRoute && typeof window !== 'undefined') {\n if (typeof (window as unknown as { gtag?: () => void }).gtag === 'function') {\n (window as unknown as { gtag: () => void }).gtag = () => {};\n }\n if (Array.isArray((window as unknown as { dataLayer?: unknown[] }).dataLayer)) {\n (window as unknown as { dataLayer: unknown[] }).dataLayer = [];\n }\n }\n }, [isAdminRoute]);\n\n return { isAdminRoute, shouldTrack: !isAdminRoute };\n}\n","import React, { createContext, useContext, type ReactNode } from 'react';\n\nconst PluginContext = createContext<{ getPlugin: <T>(name: string) => T | undefined } | null>(null);\n\nexport function PluginProvider({\n getPlugin,\n children,\n}: {\n getPlugin: <T>(name: string) => T | undefined;\n children: ReactNode;\n}) {\n return React.createElement(PluginContext.Provider, { value: { getPlugin } }, children);\n}\n\nexport function usePlugin<T = unknown>(name: string): T | undefined {\n const ctx = useContext(PluginContext);\n if (!ctx) return undefined;\n return ctx.getPlugin<T>(name);\n}\n"],"mappings":";AAAA,SAAS,UAAU,iBAAiB;AAEpC,IAAM,oBAAoB;AAEnB,SAAS,cAAuB;AACrC,QAAM,CAAC,UAAU,WAAW,IAAI,SAA8B,MAAS;AAEvE,YAAU,MAAM;AACd,UAAM,MAAM,OAAO,WAAW,eAAe,oBAAoB,CAAC,KAAK;AACvE,UAAM,WAAW,MAAM,YAAY,OAAO,aAAa,iBAAiB;AACxE,QAAI,iBAAiB,UAAU,QAAQ;AACvC,gBAAY,OAAO,aAAa,iBAAiB;AACjD,WAAO,MAAM,IAAI,oBAAoB,UAAU,QAAQ;AAAA,EACzD,GAAG,CAAC,CAAC;AAEL,SAAO,CAAC,CAAC;AACX;;;AChBA,SAAS,aAAAA,kBAAiB;AAMnB,SAAS,aAAa,UAA0E;AACrG,QAAM,eAAe,YAAY,QAAQ,SAAS,WAAW,QAAQ;AAErE,EAAAA,WAAU,MAAM;AACd,QAAI,gBAAgB,OAAO,WAAW,aAAa;AACjD,UAAI,OAAQ,OAA4C,SAAS,YAAY;AAC3E,QAAC,OAA2C,OAAO,MAAM;AAAA,QAAC;AAAA,MAC5D;AACA,UAAI,MAAM,QAAS,OAAgD,SAAS,GAAG;AAC7E,QAAC,OAA+C,YAAY,CAAC;AAAA,MAC/D;AAAA,IACF;AAAA,EACF,GAAG,CAAC,YAAY,CAAC;AAEjB,SAAO,EAAE,cAAc,aAAa,CAAC,aAAa;AACpD;;;ACrBA,OAAO,SAAS,eAAe,kBAAkC;AAEjE,IAAM,gBAAgB,cAAwE,IAAI;AAE3F,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AACF,GAGG;AACD,SAAO,MAAM,cAAc,cAAc,UAAU,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,QAAQ;AACvF;AAEO,SAAS,UAAuB,MAA6B;AAClE,QAAM,MAAM,WAAW,aAAa;AACpC,MAAI,CAAC,IAAK,QAAO;AACjB,SAAO,IAAI,UAAa,IAAI;AAC9B;","names":["useEffect"]}
|
|
1
|
+
{"version":3,"sources":["../src/hooks/use-mobile.ts","../src/hooks/use-analytics.ts","../src/hooks/use-plugin.ts","../src/components/CaptchaProvider.tsx"],"sourcesContent":["import { useState, useEffect } from 'react';\n\nconst MOBILE_BREAKPOINT = 768;\n\nexport function useIsMobile(): boolean {\n const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\n\n useEffect(() => {\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n mql.addEventListener('change', onChange);\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\n return () => mql.removeEventListener('change', onChange);\n }, []);\n\n return !!isMobile;\n}\n","import { useEffect } from 'react';\n\n/**\n * Call from app with pathname from usePathname() (next/navigation).\n * Disables tracking on admin routes when pathname starts with /admin.\n */\nexport function useAnalytics(pathname: string | null): { isAdminRoute: boolean; shouldTrack: boolean } {\n const isAdminRoute = pathname != null && pathname.startsWith('/admin');\n\n useEffect(() => {\n if (isAdminRoute && typeof window !== 'undefined') {\n if (typeof (window as unknown as { gtag?: () => void }).gtag === 'function') {\n (window as unknown as { gtag: () => void }).gtag = () => {};\n }\n if (Array.isArray((window as unknown as { dataLayer?: unknown[] }).dataLayer)) {\n (window as unknown as { dataLayer: unknown[] }).dataLayer = [];\n }\n }\n }, [isAdminRoute]);\n\n return { isAdminRoute, shouldTrack: !isAdminRoute };\n}\n","import React, { createContext, useContext, type ReactNode } from 'react';\n\nconst PluginContext = createContext<{ getPlugin: <T>(name: string) => T | undefined } | null>(null);\n\nexport function PluginProvider({\n getPlugin,\n children,\n}: {\n getPlugin: <T>(name: string) => T | undefined;\n children: ReactNode;\n}) {\n return React.createElement(PluginContext.Provider, { value: { getPlugin } }, children);\n}\n\nexport function usePlugin<T = unknown>(name: string): T | undefined {\n const ctx = useContext(PluginContext);\n if (!ctx) return undefined;\n return ctx.getPlugin<T>(name);\n}\n","'use client';\n\nimport React, {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from 'react';\nimport type { CaptchaPublicConfig, CaptchaProviderId } from '../plugins/captcha';\n\ndeclare global {\n interface Window {\n turnstile?: {\n render: (el: HTMLElement | string, opts: Record<string, unknown>) => string;\n execute: (id: string) => void;\n reset: (id: string) => void;\n remove: (id: string) => void;\n };\n grecaptcha?: {\n ready: (fn: () => void) => void;\n execute: (siteKey: string, opts: { action: string }) => Promise<string>;\n };\n }\n}\n\ntype CaptchaCtx = {\n config: CaptchaPublicConfig | null;\n ready: boolean;\n selectedProvider: CaptchaProviderId | null;\n setSelectedProvider: (p: CaptchaProviderId) => void;\n getCaptchaPayload: () => Promise<Record<string, string>>;\n};\n\nconst CaptchaReactContext = createContext<CaptchaCtx | null>(null);\n\nfunction loadScript(src: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const base = src.split('?')[0] ?? src;\n const existing = Array.from(document.querySelectorAll('script[src]')).some((el) =>\n (el as HTMLScriptElement).src.startsWith(base)\n );\n if (existing) {\n resolve();\n return;\n }\n const s = document.createElement('script');\n s.src = src;\n s.async = true;\n s.onload = () => resolve();\n s.onerror = () => reject(new Error('Failed to load script'));\n document.head.appendChild(s);\n });\n}\n\nexport function CaptchaProvider({ children }: { children: React.ReactNode }) {\n const [config, setConfig] = useState<CaptchaPublicConfig | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<CaptchaProviderId | null>(null);\n const [ready, setReady] = useState(false);\n const turnstileWidgetIdRef = useRef<string | null>(null);\n const turnstileContainerRef = useRef<HTMLDivElement | null>(null);\n const turnstileTokenRef = useRef<{\n resolve: (t: string) => void;\n reject: (e: Error) => void;\n } | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n fetch('/api/public/captcha-config')\n .then((r) => (r.ok ? r.json() : null))\n .then((c: CaptchaPublicConfig | null) => {\n if (cancelled || !c) return;\n setConfig(c);\n if (c.enabled && c.activeProvider) setSelectedProvider(c.activeProvider);\n })\n .catch(() => {})\n .finally(() => {\n if (!cancelled) setReady(true);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n useEffect(() => {\n if (!config?.enabled || !selectedProvider) return;\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\n if (!siteKeyFor) return;\n\n let cancelled = false;\n (async () => {\n try {\n if (selectedProvider === 'recaptcha_v3') {\n await loadScript(\n `https://www.google.com/recaptcha/api.js?render=${encodeURIComponent(siteKeyFor)}`\n );\n } else {\n await loadScript('https://challenges.cloudflare.com/turnstile/v0/api.js');\n if (cancelled || !turnstileContainerRef.current || !window.turnstile) return;\n if (turnstileWidgetIdRef.current) {\n try {\n window.turnstile.remove(turnstileWidgetIdRef.current);\n } catch {\n /* ignore */\n }\n turnstileWidgetIdRef.current = null;\n }\n const wid = window.turnstile.render(turnstileContainerRef.current, {\n sitekey: siteKeyFor,\n execution: 'execute',\n callback: (token: string) => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.resolve(token);\n },\n 'error-callback': () => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.reject(new Error('Turnstile error'));\n },\n 'expired-callback': () => {\n const p = turnstileTokenRef.current;\n turnstileTokenRef.current = null;\n p?.reject(new Error('Turnstile expired'));\n },\n });\n turnstileWidgetIdRef.current = wid;\n }\n } catch {\n /* ignore */\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [config, selectedProvider]);\n\n const getCaptchaPayload = useCallback(async (): Promise<Record<string, string>> => {\n if (!config?.enabled || !selectedProvider) return {};\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\n if (!siteKeyFor) return {};\n\n if (selectedProvider === 'recaptcha_v3') {\n if (!window.grecaptcha) return {};\n const token = await new Promise<string>((resolve, reject) => {\n window.grecaptcha!.ready(() => {\n window.grecaptcha!.execute(siteKeyFor, { action: 'submit' }).then(resolve).catch(reject);\n });\n });\n return { captchaToken: token, captchaProvider: 'recaptcha_v3' };\n }\n\n const wid = turnstileWidgetIdRef.current;\n if (!wid || !window.turnstile) return {};\n const token = await new Promise<string>((resolve, reject) => {\n turnstileTokenRef.current = { resolve, reject };\n try {\n window.turnstile!.reset(wid);\n window.turnstile!.execute(wid);\n } catch (e) {\n turnstileTokenRef.current = null;\n reject(e instanceof Error ? e : new Error('Turnstile execute failed'));\n }\n });\n return { captchaToken: token, captchaProvider: 'turnstile' };\n }, [config, selectedProvider]);\n\n const ctx = useMemo<CaptchaCtx>(\n () => ({\n config,\n ready,\n selectedProvider,\n setSelectedProvider,\n getCaptchaPayload,\n }),\n [config, ready, selectedProvider, getCaptchaPayload]\n );\n\n return (\n <CaptchaReactContext.Provider value={ctx}>\n {config?.enabled && config.multipleProviders ? (\n <div className=\"mb-2 text-sm text-muted-foreground max-w-md\">\n <label className=\"mr-2\" htmlFor=\"captcha-provider-select\">\n Verification\n </label>\n <select\n id=\"captcha-provider-select\"\n className=\"border rounded px-2 py-1 bg-background\"\n value={selectedProvider ?? ''}\n onChange={(e) => setSelectedProvider(e.target.value as CaptchaProviderId)}\n >\n {config.availableProviders.map((p) => (\n <option key={p.id} value={p.id}>\n {p.id === 'turnstile' ? 'Cloudflare Turnstile' : 'Google reCAPTCHA v3'}\n </option>\n ))}\n </select>\n </div>\n ) : null}\n <div ref={turnstileContainerRef} className=\"hidden\" aria-hidden />\n {children}\n </CaptchaReactContext.Provider>\n );\n}\n\n/** Returns a function that resolves to captcha fields for JSON bodies, or {} if disabled / not ready. */\nexport function useCaptchaPayload(): () => Promise<Record<string, string>> {\n const ctx = useContext(CaptchaReactContext);\n return useCallback(async () => {\n if (!ctx?.config?.enabled || !ctx.ready) return {};\n return ctx.getCaptchaPayload();\n }, [ctx]);\n}\n"],"mappings":"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","names":["useEffect","createContext","useContext","useEffect","useState","token"]}
|
|
@@ -164,6 +164,11 @@ interface DashboardStatsConfig extends CmsHandlersBase {
|
|
|
164
164
|
requirePermission?: (req: Request, permission: string) => Promise<Response | null>;
|
|
165
165
|
}
|
|
166
166
|
declare function createDashboardStatsHandler(config: DashboardStatsConfig): (req: Request) => Promise<Response>;
|
|
167
|
+
interface EcommerceAnalyticsConfig extends CmsHandlersBase {
|
|
168
|
+
dataSource: DataSource;
|
|
169
|
+
entityMap: EntityMap;
|
|
170
|
+
}
|
|
171
|
+
declare function createEcommerceAnalyticsHandler(config: EcommerceAnalyticsConfig): (req: Request) => Promise<Response>;
|
|
167
172
|
interface AnalyticsHandlerConfig extends CmsHandlersBase {
|
|
168
173
|
getAnalyticsData?: (days: number) => Promise<unknown>;
|
|
169
174
|
getPropertyId?: () => ({
|
|
@@ -189,8 +194,13 @@ interface UploadHandlerConfig extends CmsHandlersBase {
|
|
|
189
194
|
localUploadDir?: string;
|
|
190
195
|
allowedTypes?: string[];
|
|
191
196
|
maxSizeBytes?: number;
|
|
197
|
+
/** Resolve folder chain for `parentId` (merged from createCmsApiHandler when omitted). */
|
|
198
|
+
dataSource?: DataSource;
|
|
199
|
+
entityMap?: EntityMap;
|
|
192
200
|
}
|
|
193
201
|
declare function createUploadHandler(config: UploadHandlerConfig): (req: Request) => Promise<Response>;
|
|
202
|
+
/** POST /api/media/extract/:id — expand a zip media row into folders/files under the zip’s parent folder. */
|
|
203
|
+
declare function createMediaZipExtractHandler(config: UploadHandlerConfig): (_req: Request, zipMediaId: string) => Promise<Response>;
|
|
194
204
|
interface BlogBySlugConfig extends CmsHandlersBase {
|
|
195
205
|
dataSource: DataSource;
|
|
196
206
|
entityMap: EntityMap;
|
|
@@ -299,7 +309,7 @@ interface ChatApiConfig extends CmsHandlersBase {
|
|
|
299
309
|
}
|
|
300
310
|
|
|
301
311
|
/**
|
|
302
|
-
* Single CMS API handler: dashboard, analytics, upload, blog/form by slug, users API, user auth, CRUD. Mount once (e.g. app/api/[[...path]]/route.ts).
|
|
312
|
+
* Single CMS API handler: dashboard, analytics, upload, media zip extract, blog/form by slug, users API, user auth, CRUD. Mount once (e.g. app/api/[[...path]]/route.ts).
|
|
303
313
|
*/
|
|
304
314
|
|
|
305
315
|
/** CMS instance with getPlugin; when provided, analytics and userAuth.sendEmail can be resolved from plugins when not passed. */
|
|
@@ -324,6 +334,8 @@ interface CmsApiHandlerConfig {
|
|
|
324
334
|
userAuth?: UserAuthApiConfig;
|
|
325
335
|
/** GET /api/dashboard/stats */
|
|
326
336
|
dashboard?: DashboardStatsConfig;
|
|
337
|
+
/** GET /api/dashboard/ecommerce */
|
|
338
|
+
ecommerceAnalytics?: EcommerceAnalyticsConfig;
|
|
327
339
|
/** GET /api/analytics. If omitted and getCms is set, uses getCms().getPlugin('analytics'). */
|
|
328
340
|
analytics?: AnalyticsHandlerConfig;
|
|
329
341
|
/** POST /api/upload (S3 or local) */
|
|
@@ -392,4 +404,4 @@ declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
|
|
|
392
404
|
handle(method: string, path: string[], req: Request): Promise<Response>;
|
|
393
405
|
};
|
|
394
406
|
|
|
395
|
-
export { type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G,
|
|
407
|
+
export { mergeEmailLayoutCompanyDetails as $, type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createEcommerceAnalyticsHandler as H, type InviteAcceptConfig as I, createForgotPasswordHandler as J, createFormBySlugHandler as K, createInviteAcceptHandler as L, createMediaZipExtractHandler as M, createSetPasswordHandler as N, type OrderPlacedLineItem as O, createSettingsApiHandlers as P, createStorefrontApiHandler as Q, createUploadHandler as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createUserAuthApiRouter as V, createUserAvatarHandler as W, createUserProfileHandler as X, createUsersApiHandlers as Y, getCompanyDetailsFromSettings as Z, getPublicSettingsGroup as _, type EmailTemplateName as a, type EntityMap as b, type AuthHandlersConfig as c, type ChangePasswordConfig as d, type CmsApiHandlerConfig as e, type CmsGetter as f, type CrudHandlerOptions as g, type EcommerceAnalyticsConfig as h, type FormBySlugConfig as i, type GetPublicSettingsGroupDataSource as j, type SetPasswordConfig as k, type SettingsApiConfig as l, type SocialLinkItem as m, type StorefrontApiConfig as n, type StorefrontOtpFlags as o, type UserAuthApiConfig as p, type UserAvatarConfig as q, type UserProfileConfig as r, type UsersApiConfig as s, createAnalyticsHandlers as t, createBlogBySlugHandler as u, createChangePasswordHandler as v, createCmsApiHandler as w, createCrudByIdHandler as x, createCrudHandler as y, createDashboardStatsHandler as z };
|
|
@@ -164,6 +164,11 @@ interface DashboardStatsConfig extends CmsHandlersBase {
|
|
|
164
164
|
requirePermission?: (req: Request, permission: string) => Promise<Response | null>;
|
|
165
165
|
}
|
|
166
166
|
declare function createDashboardStatsHandler(config: DashboardStatsConfig): (req: Request) => Promise<Response>;
|
|
167
|
+
interface EcommerceAnalyticsConfig extends CmsHandlersBase {
|
|
168
|
+
dataSource: DataSource;
|
|
169
|
+
entityMap: EntityMap;
|
|
170
|
+
}
|
|
171
|
+
declare function createEcommerceAnalyticsHandler(config: EcommerceAnalyticsConfig): (req: Request) => Promise<Response>;
|
|
167
172
|
interface AnalyticsHandlerConfig extends CmsHandlersBase {
|
|
168
173
|
getAnalyticsData?: (days: number) => Promise<unknown>;
|
|
169
174
|
getPropertyId?: () => ({
|
|
@@ -189,8 +194,13 @@ interface UploadHandlerConfig extends CmsHandlersBase {
|
|
|
189
194
|
localUploadDir?: string;
|
|
190
195
|
allowedTypes?: string[];
|
|
191
196
|
maxSizeBytes?: number;
|
|
197
|
+
/** Resolve folder chain for `parentId` (merged from createCmsApiHandler when omitted). */
|
|
198
|
+
dataSource?: DataSource;
|
|
199
|
+
entityMap?: EntityMap;
|
|
192
200
|
}
|
|
193
201
|
declare function createUploadHandler(config: UploadHandlerConfig): (req: Request) => Promise<Response>;
|
|
202
|
+
/** POST /api/media/extract/:id — expand a zip media row into folders/files under the zip’s parent folder. */
|
|
203
|
+
declare function createMediaZipExtractHandler(config: UploadHandlerConfig): (_req: Request, zipMediaId: string) => Promise<Response>;
|
|
194
204
|
interface BlogBySlugConfig extends CmsHandlersBase {
|
|
195
205
|
dataSource: DataSource;
|
|
196
206
|
entityMap: EntityMap;
|
|
@@ -299,7 +309,7 @@ interface ChatApiConfig extends CmsHandlersBase {
|
|
|
299
309
|
}
|
|
300
310
|
|
|
301
311
|
/**
|
|
302
|
-
* Single CMS API handler: dashboard, analytics, upload, blog/form by slug, users API, user auth, CRUD. Mount once (e.g. app/api/[[...path]]/route.ts).
|
|
312
|
+
* Single CMS API handler: dashboard, analytics, upload, media zip extract, blog/form by slug, users API, user auth, CRUD. Mount once (e.g. app/api/[[...path]]/route.ts).
|
|
303
313
|
*/
|
|
304
314
|
|
|
305
315
|
/** CMS instance with getPlugin; when provided, analytics and userAuth.sendEmail can be resolved from plugins when not passed. */
|
|
@@ -324,6 +334,8 @@ interface CmsApiHandlerConfig {
|
|
|
324
334
|
userAuth?: UserAuthApiConfig;
|
|
325
335
|
/** GET /api/dashboard/stats */
|
|
326
336
|
dashboard?: DashboardStatsConfig;
|
|
337
|
+
/** GET /api/dashboard/ecommerce */
|
|
338
|
+
ecommerceAnalytics?: EcommerceAnalyticsConfig;
|
|
327
339
|
/** GET /api/analytics. If omitted and getCms is set, uses getCms().getPlugin('analytics'). */
|
|
328
340
|
analytics?: AnalyticsHandlerConfig;
|
|
329
341
|
/** POST /api/upload (S3 or local) */
|
|
@@ -392,4 +404,4 @@ declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
|
|
|
392
404
|
handle(method: string, path: string[], req: Request): Promise<Response>;
|
|
393
405
|
};
|
|
394
406
|
|
|
395
|
-
export { type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G,
|
|
407
|
+
export { mergeEmailLayoutCompanyDetails as $, type AnalyticsHandlerConfig as A, type BlogBySlugConfig as B, type CompanyDetails as C, type DashboardStatsConfig as D, type EmailTemplateResult as E, type ForgotPasswordConfig as F, type GetPublicSettingsGroupConfig as G, createEcommerceAnalyticsHandler as H, type InviteAcceptConfig as I, createForgotPasswordHandler as J, createFormBySlugHandler as K, createInviteAcceptHandler as L, createMediaZipExtractHandler as M, createSetPasswordHandler as N, type OrderPlacedLineItem as O, createSettingsApiHandlers as P, createStorefrontApiHandler as Q, createUploadHandler as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createUserAuthApiRouter as V, createUserAvatarHandler as W, createUserProfileHandler as X, createUsersApiHandlers as Y, getCompanyDetailsFromSettings as Z, getPublicSettingsGroup as _, type EmailTemplateName as a, type EntityMap as b, type AuthHandlersConfig as c, type ChangePasswordConfig as d, type CmsApiHandlerConfig as e, type CmsGetter as f, type CrudHandlerOptions as g, type EcommerceAnalyticsConfig as h, type FormBySlugConfig as i, type GetPublicSettingsGroupDataSource as j, type SetPasswordConfig as k, type SettingsApiConfig as l, type SocialLinkItem as m, type StorefrontApiConfig as n, type StorefrontOtpFlags as o, type UserAuthApiConfig as p, type UserAvatarConfig as q, type UserProfileConfig as r, type UsersApiConfig as s, createAnalyticsHandlers as t, createBlogBySlugHandler as u, createChangePasswordHandler as v, createCmsApiHandler as w, createCrudByIdHandler as x, createCrudHandler as y, createDashboardStatsHandler as z };
|