@infuro/cms-core 1.0.15 → 1.0.18
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/README.md +739 -724
- 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 +700 -77
- 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 +696 -75
- package/dist/api.js.map +1 -1
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/cli.cjs +21 -6
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +21 -6
- package/dist/cli.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-BQnqJ7EO.d.cts → index-D2C1O9b4.d.cts} +22 -3
- package/dist/{index-BiagwMjV.d.ts → index-GMn7-9PX.d.ts} +22 -3
- package/dist/index.cjs +5334 -4336
- 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 +5333 -4352
- package/dist/index.js.map +1 -1
- package/dist/migrations/1772178563554-InitialSchema.ts +304 -304
- package/dist/migrations/1772178563555-ChatAndKnowledgeBase.ts +55 -55
- package/dist/migrations/1772178563556-KnowledgeBaseVector.ts +16 -16
- package/dist/migrations/1774300000000-RbacSeedGroupsAndPermissionUnique.ts +24 -24
- package/dist/migrations/1774300000001-SeedAdministratorUsersPermission.ts +35 -35
- package/dist/migrations/1774400000000-CustomerAdminAccessContactUser.ts +37 -37
- package/dist/migrations/1774400000001-StorefrontCartWishlist.ts +100 -100
- package/dist/migrations/1774400000002-WishlistGuestId.ts +29 -29
- package/dist/migrations/1774500000000-ProductCollectionHsn.ts +15 -15
- package/dist/migrations/1774600000000-OrderKindParentOrderNumber.ts +36 -36
- package/dist/migrations/1774800000000-OtpChallengesUserPhone.ts +41 -41
- package/dist/migrations/1774900000000-MessageTemplates.ts +39 -39
- package/dist/migrations/1775000000000-ProductUomTypeOrderItemSnapshots.ts +29 -29
- package/dist/migrations/1775200000000-MediaDriveFolders.ts +38 -0
- package/dist/migrations/README.md +3 -3
- package/dist/theme.cjs.map +1 -1
- package/dist/theme.js.map +1 -1
- package/package.json +20 -15
- package/src/admin/admin.css +72 -72
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';\r\n\r\nconst MOBILE_BREAKPOINT = 768;\r\n\r\nexport function useIsMobile(): boolean {\r\n const [isMobile, setIsMobile] = useState<boolean | undefined>(undefined);\r\n\r\n useEffect(() => {\r\n const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);\r\n const onChange = () => setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\r\n mql.addEventListener('change', onChange);\r\n setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);\r\n return () => mql.removeEventListener('change', onChange);\r\n }, []);\r\n\r\n return !!isMobile;\r\n}\r\n","import { useEffect } from 'react';\r\n\r\n/**\r\n * Call from app with pathname from usePathname() (next/navigation).\r\n * Disables tracking on admin routes when pathname starts with /admin.\r\n */\r\nexport function useAnalytics(pathname: string | null): { isAdminRoute: boolean; shouldTrack: boolean } {\r\n const isAdminRoute = pathname != null && pathname.startsWith('/admin');\r\n\r\n useEffect(() => {\r\n if (isAdminRoute && typeof window !== 'undefined') {\r\n if (typeof (window as unknown as { gtag?: () => void }).gtag === 'function') {\r\n (window as unknown as { gtag: () => void }).gtag = () => {};\r\n }\r\n if (Array.isArray((window as unknown as { dataLayer?: unknown[] }).dataLayer)) {\r\n (window as unknown as { dataLayer: unknown[] }).dataLayer = [];\r\n }\r\n }\r\n }, [isAdminRoute]);\r\n\r\n return { isAdminRoute, shouldTrack: !isAdminRoute };\r\n}\r\n","import React, { createContext, useContext, type ReactNode } from 'react';\r\n\r\nconst PluginContext = createContext<{ getPlugin: <T>(name: string) => T | undefined } | null>(null);\r\n\r\nexport function PluginProvider({\r\n getPlugin,\r\n children,\r\n}: {\r\n getPlugin: <T>(name: string) => T | undefined;\r\n children: ReactNode;\r\n}) {\r\n return React.createElement(PluginContext.Provider, { value: { getPlugin } }, children);\r\n}\r\n\r\nexport function usePlugin<T = unknown>(name: string): T | undefined {\r\n const ctx = useContext(PluginContext);\r\n if (!ctx) return undefined;\r\n return ctx.getPlugin<T>(name);\r\n}\r\n","'use client';\r\n\r\nimport React, {\r\n createContext,\r\n useCallback,\r\n useContext,\r\n useEffect,\r\n useMemo,\r\n useRef,\r\n useState,\r\n} from 'react';\r\nimport type { CaptchaPublicConfig, CaptchaProviderId } from '../plugins/captcha';\r\n\r\ndeclare global {\r\n interface Window {\r\n turnstile?: {\r\n render: (el: HTMLElement | string, opts: Record<string, unknown>) => string;\r\n execute: (id: string) => void;\r\n reset: (id: string) => void;\r\n remove: (id: string) => void;\r\n };\r\n grecaptcha?: {\r\n ready: (fn: () => void) => void;\r\n execute: (siteKey: string, opts: { action: string }) => Promise<string>;\r\n };\r\n }\r\n}\r\n\r\ntype CaptchaCtx = {\r\n config: CaptchaPublicConfig | null;\r\n ready: boolean;\r\n selectedProvider: CaptchaProviderId | null;\r\n setSelectedProvider: (p: CaptchaProviderId) => void;\r\n getCaptchaPayload: () => Promise<Record<string, string>>;\r\n};\r\n\r\nconst CaptchaReactContext = createContext<CaptchaCtx | null>(null);\r\n\r\nfunction loadScript(src: string): Promise<void> {\r\n return new Promise((resolve, reject) => {\r\n const base = src.split('?')[0] ?? src;\r\n const existing = Array.from(document.querySelectorAll('script[src]')).some((el) =>\r\n (el as HTMLScriptElement).src.startsWith(base)\r\n );\r\n if (existing) {\r\n resolve();\r\n return;\r\n }\r\n const s = document.createElement('script');\r\n s.src = src;\r\n s.async = true;\r\n s.onload = () => resolve();\r\n s.onerror = () => reject(new Error('Failed to load script'));\r\n document.head.appendChild(s);\r\n });\r\n}\r\n\r\nexport function CaptchaProvider({ children }: { children: React.ReactNode }) {\r\n const [config, setConfig] = useState<CaptchaPublicConfig | null>(null);\r\n const [selectedProvider, setSelectedProvider] = useState<CaptchaProviderId | null>(null);\r\n const [ready, setReady] = useState(false);\r\n const turnstileWidgetIdRef = useRef<string | null>(null);\r\n const turnstileContainerRef = useRef<HTMLDivElement | null>(null);\r\n const turnstileTokenRef = useRef<{\r\n resolve: (t: string) => void;\r\n reject: (e: Error) => void;\r\n } | null>(null);\r\n\r\n useEffect(() => {\r\n let cancelled = false;\r\n fetch('/api/public/captcha-config')\r\n .then((r) => (r.ok ? r.json() : null))\r\n .then((c: CaptchaPublicConfig | null) => {\r\n if (cancelled || !c) return;\r\n setConfig(c);\r\n if (c.enabled && c.activeProvider) setSelectedProvider(c.activeProvider);\r\n })\r\n .catch(() => {})\r\n .finally(() => {\r\n if (!cancelled) setReady(true);\r\n });\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, []);\r\n\r\n useEffect(() => {\r\n if (!config?.enabled || !selectedProvider) return;\r\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\r\n if (!siteKeyFor) return;\r\n\r\n let cancelled = false;\r\n (async () => {\r\n try {\r\n if (selectedProvider === 'recaptcha_v3') {\r\n await loadScript(\r\n `https://www.google.com/recaptcha/api.js?render=${encodeURIComponent(siteKeyFor)}`\r\n );\r\n } else {\r\n await loadScript('https://challenges.cloudflare.com/turnstile/v0/api.js');\r\n if (cancelled || !turnstileContainerRef.current || !window.turnstile) return;\r\n if (turnstileWidgetIdRef.current) {\r\n try {\r\n window.turnstile.remove(turnstileWidgetIdRef.current);\r\n } catch {\r\n /* ignore */\r\n }\r\n turnstileWidgetIdRef.current = null;\r\n }\r\n const wid = window.turnstile.render(turnstileContainerRef.current, {\r\n sitekey: siteKeyFor,\r\n execution: 'execute',\r\n callback: (token: string) => {\r\n const p = turnstileTokenRef.current;\r\n turnstileTokenRef.current = null;\r\n p?.resolve(token);\r\n },\r\n 'error-callback': () => {\r\n const p = turnstileTokenRef.current;\r\n turnstileTokenRef.current = null;\r\n p?.reject(new Error('Turnstile error'));\r\n },\r\n 'expired-callback': () => {\r\n const p = turnstileTokenRef.current;\r\n turnstileTokenRef.current = null;\r\n p?.reject(new Error('Turnstile expired'));\r\n },\r\n });\r\n turnstileWidgetIdRef.current = wid;\r\n }\r\n } catch {\r\n /* ignore */\r\n }\r\n })();\r\n return () => {\r\n cancelled = true;\r\n };\r\n }, [config, selectedProvider]);\r\n\r\n const getCaptchaPayload = useCallback(async (): Promise<Record<string, string>> => {\r\n if (!config?.enabled || !selectedProvider) return {};\r\n const siteKeyFor = config.availableProviders.find((p) => p.id === selectedProvider)?.siteKey;\r\n if (!siteKeyFor) return {};\r\n\r\n if (selectedProvider === 'recaptcha_v3') {\r\n if (!window.grecaptcha) return {};\r\n const token = await new Promise<string>((resolve, reject) => {\r\n window.grecaptcha!.ready(() => {\r\n window.grecaptcha!.execute(siteKeyFor, { action: 'submit' }).then(resolve).catch(reject);\r\n });\r\n });\r\n return { captchaToken: token, captchaProvider: 'recaptcha_v3' };\r\n }\r\n\r\n const wid = turnstileWidgetIdRef.current;\r\n if (!wid || !window.turnstile) return {};\r\n const token = await new Promise<string>((resolve, reject) => {\r\n turnstileTokenRef.current = { resolve, reject };\r\n try {\r\n window.turnstile!.reset(wid);\r\n window.turnstile!.execute(wid);\r\n } catch (e) {\r\n turnstileTokenRef.current = null;\r\n reject(e instanceof Error ? e : new Error('Turnstile execute failed'));\r\n }\r\n });\r\n return { captchaToken: token, captchaProvider: 'turnstile' };\r\n }, [config, selectedProvider]);\r\n\r\n const ctx = useMemo<CaptchaCtx>(\r\n () => ({\r\n config,\r\n ready,\r\n selectedProvider,\r\n setSelectedProvider,\r\n getCaptchaPayload,\r\n }),\r\n [config, ready, selectedProvider, getCaptchaPayload]\r\n );\r\n\r\n return (\r\n <CaptchaReactContext.Provider value={ctx}>\r\n {config?.enabled && config.multipleProviders ? (\r\n <div className=\"mb-2 text-sm text-muted-foreground max-w-md\">\r\n <label className=\"mr-2\" htmlFor=\"captcha-provider-select\">\r\n Verification\r\n </label>\r\n <select\r\n id=\"captcha-provider-select\"\r\n className=\"border rounded px-2 py-1 bg-background\"\r\n value={selectedProvider ?? ''}\r\n onChange={(e) => setSelectedProvider(e.target.value as CaptchaProviderId)}\r\n >\r\n {config.availableProviders.map((p) => (\r\n <option key={p.id} value={p.id}>\r\n {p.id === 'turnstile' ? 'Cloudflare Turnstile' : 'Google reCAPTCHA v3'}\r\n </option>\r\n ))}\r\n </select>\r\n </div>\r\n ) : null}\r\n <div ref={turnstileContainerRef} className=\"hidden\" aria-hidden />\r\n {children}\r\n </CaptchaReactContext.Provider>\r\n );\r\n}\r\n\r\n/** Returns a function that resolves to captcha fields for JSON bodies, or {} if disabled / not ready. */\r\nexport function useCaptchaPayload(): () => Promise<Record<string, string>> {\r\n const ctx = useContext(CaptchaReactContext);\r\n return useCallback(async () => {\r\n if (!ctx?.config?.enabled || !ctx.ready) return {};\r\n return ctx.getCaptchaPayload();\r\n }, [ctx]);\r\n}\r\n"],"mappings":"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","names":["useEffect","createContext","useContext","useEffect","useState","token"]}
|
|
@@ -61,6 +61,8 @@ interface CrudHandlerOptions {
|
|
|
61
61
|
getCms?: () => Promise<{
|
|
62
62
|
getPlugin: (name: string) => unknown;
|
|
63
63
|
}>;
|
|
64
|
+
/** When set, soft-delete sets `deletedBy` from the current admin user. */
|
|
65
|
+
getDeletedByUserId?: (req: Request) => Promise<number | null>;
|
|
64
66
|
}
|
|
65
67
|
declare function createCrudHandler(dataSource: DataSource, entityMap: EntityMap, options: CrudHandlerOptions): {
|
|
66
68
|
GET(req: Request, resource: string): Promise<Response>;
|
|
@@ -164,6 +166,11 @@ interface DashboardStatsConfig extends CmsHandlersBase {
|
|
|
164
166
|
requirePermission?: (req: Request, permission: string) => Promise<Response | null>;
|
|
165
167
|
}
|
|
166
168
|
declare function createDashboardStatsHandler(config: DashboardStatsConfig): (req: Request) => Promise<Response>;
|
|
169
|
+
interface EcommerceAnalyticsConfig extends CmsHandlersBase {
|
|
170
|
+
dataSource: DataSource;
|
|
171
|
+
entityMap: EntityMap;
|
|
172
|
+
}
|
|
173
|
+
declare function createEcommerceAnalyticsHandler(config: EcommerceAnalyticsConfig): (req: Request) => Promise<Response>;
|
|
167
174
|
interface AnalyticsHandlerConfig extends CmsHandlersBase {
|
|
168
175
|
getAnalyticsData?: (days: number) => Promise<unknown>;
|
|
169
176
|
getPropertyId?: () => ({
|
|
@@ -189,8 +196,13 @@ interface UploadHandlerConfig extends CmsHandlersBase {
|
|
|
189
196
|
localUploadDir?: string;
|
|
190
197
|
allowedTypes?: string[];
|
|
191
198
|
maxSizeBytes?: number;
|
|
199
|
+
/** Resolve folder chain for `parentId` (merged from createCmsApiHandler when omitted). */
|
|
200
|
+
dataSource?: DataSource;
|
|
201
|
+
entityMap?: EntityMap;
|
|
192
202
|
}
|
|
193
203
|
declare function createUploadHandler(config: UploadHandlerConfig): (req: Request) => Promise<Response>;
|
|
204
|
+
/** POST /api/media/extract/:id — expand a zip media row into folders/files under the zip’s parent folder. */
|
|
205
|
+
declare function createMediaZipExtractHandler(config: UploadHandlerConfig): (_req: Request, zipMediaId: string) => Promise<Response>;
|
|
194
206
|
interface BlogBySlugConfig extends CmsHandlersBase {
|
|
195
207
|
dataSource: DataSource;
|
|
196
208
|
entityMap: EntityMap;
|
|
@@ -234,6 +246,8 @@ interface UsersApiConfig extends CmsHandlersBase {
|
|
|
234
246
|
getPlugin: (name: string) => unknown;
|
|
235
247
|
}>;
|
|
236
248
|
getCompanyDetails?: () => Promise<CompanyDetails>;
|
|
249
|
+
/** When set, soft-delete sets `deletedBy` on the user row. */
|
|
250
|
+
getSessionUser?: () => Promise<SessionUser | null>;
|
|
237
251
|
}
|
|
238
252
|
declare function createUsersApiHandlers(config: UsersApiConfig): {
|
|
239
253
|
list(req: Request): Promise<Response>;
|
|
@@ -299,7 +313,7 @@ interface ChatApiConfig extends CmsHandlersBase {
|
|
|
299
313
|
}
|
|
300
314
|
|
|
301
315
|
/**
|
|
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).
|
|
316
|
+
* 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
317
|
*/
|
|
304
318
|
|
|
305
319
|
/** CMS instance with getPlugin; when provided, analytics and userAuth.sendEmail can be resolved from plugins when not passed. */
|
|
@@ -324,6 +338,8 @@ interface CmsApiHandlerConfig {
|
|
|
324
338
|
userAuth?: UserAuthApiConfig;
|
|
325
339
|
/** GET /api/dashboard/stats */
|
|
326
340
|
dashboard?: DashboardStatsConfig;
|
|
341
|
+
/** GET /api/dashboard/ecommerce */
|
|
342
|
+
ecommerceAnalytics?: EcommerceAnalyticsConfig;
|
|
327
343
|
/** GET /api/analytics. If omitted and getCms is set, uses getCms().getPlugin('analytics'). */
|
|
328
344
|
analytics?: AnalyticsHandlerConfig;
|
|
329
345
|
/** POST /api/upload (S3 or local) */
|
|
@@ -348,7 +364,10 @@ interface CmsApiHandlerConfig {
|
|
|
348
364
|
settings?: SettingsApiConfig;
|
|
349
365
|
/** POST /api/chat/identify, GET /api/chat/conversations/:id/messages, POST /api/chat/messages */
|
|
350
366
|
chat?: ChatApiConfig;
|
|
351
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* Entity-level RBAC (session `entityPerms` / Administrator). When omitted, admin routes that need it respond with 403
|
|
369
|
+
* (`entity_rbac_required`) so CRUD cannot run as auth-only. Pass `createAuthHelpers(...).requireEntityPermission` from the app.
|
|
370
|
+
*/
|
|
352
371
|
requireEntityPermission?: (req: Request, entity: string, action: EntityCrudAction) => Promise<Response | null>;
|
|
353
372
|
/** Required for GET/POST/PATCH/DELETE /api/admin/roles */
|
|
354
373
|
getSessionUser?: () => Promise<SessionUser | null>;
|
|
@@ -392,4 +411,4 @@ declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
|
|
|
392
411
|
handle(method: string, path: string[], req: Request): Promise<Response>;
|
|
393
412
|
};
|
|
394
413
|
|
|
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,
|
|
414
|
+
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 };
|
|
@@ -61,6 +61,8 @@ interface CrudHandlerOptions {
|
|
|
61
61
|
getCms?: () => Promise<{
|
|
62
62
|
getPlugin: (name: string) => unknown;
|
|
63
63
|
}>;
|
|
64
|
+
/** When set, soft-delete sets `deletedBy` from the current admin user. */
|
|
65
|
+
getDeletedByUserId?: (req: Request) => Promise<number | null>;
|
|
64
66
|
}
|
|
65
67
|
declare function createCrudHandler(dataSource: DataSource, entityMap: EntityMap, options: CrudHandlerOptions): {
|
|
66
68
|
GET(req: Request, resource: string): Promise<Response>;
|
|
@@ -164,6 +166,11 @@ interface DashboardStatsConfig extends CmsHandlersBase {
|
|
|
164
166
|
requirePermission?: (req: Request, permission: string) => Promise<Response | null>;
|
|
165
167
|
}
|
|
166
168
|
declare function createDashboardStatsHandler(config: DashboardStatsConfig): (req: Request) => Promise<Response>;
|
|
169
|
+
interface EcommerceAnalyticsConfig extends CmsHandlersBase {
|
|
170
|
+
dataSource: DataSource;
|
|
171
|
+
entityMap: EntityMap;
|
|
172
|
+
}
|
|
173
|
+
declare function createEcommerceAnalyticsHandler(config: EcommerceAnalyticsConfig): (req: Request) => Promise<Response>;
|
|
167
174
|
interface AnalyticsHandlerConfig extends CmsHandlersBase {
|
|
168
175
|
getAnalyticsData?: (days: number) => Promise<unknown>;
|
|
169
176
|
getPropertyId?: () => ({
|
|
@@ -189,8 +196,13 @@ interface UploadHandlerConfig extends CmsHandlersBase {
|
|
|
189
196
|
localUploadDir?: string;
|
|
190
197
|
allowedTypes?: string[];
|
|
191
198
|
maxSizeBytes?: number;
|
|
199
|
+
/** Resolve folder chain for `parentId` (merged from createCmsApiHandler when omitted). */
|
|
200
|
+
dataSource?: DataSource;
|
|
201
|
+
entityMap?: EntityMap;
|
|
192
202
|
}
|
|
193
203
|
declare function createUploadHandler(config: UploadHandlerConfig): (req: Request) => Promise<Response>;
|
|
204
|
+
/** POST /api/media/extract/:id — expand a zip media row into folders/files under the zip’s parent folder. */
|
|
205
|
+
declare function createMediaZipExtractHandler(config: UploadHandlerConfig): (_req: Request, zipMediaId: string) => Promise<Response>;
|
|
194
206
|
interface BlogBySlugConfig extends CmsHandlersBase {
|
|
195
207
|
dataSource: DataSource;
|
|
196
208
|
entityMap: EntityMap;
|
|
@@ -234,6 +246,8 @@ interface UsersApiConfig extends CmsHandlersBase {
|
|
|
234
246
|
getPlugin: (name: string) => unknown;
|
|
235
247
|
}>;
|
|
236
248
|
getCompanyDetails?: () => Promise<CompanyDetails>;
|
|
249
|
+
/** When set, soft-delete sets `deletedBy` on the user row. */
|
|
250
|
+
getSessionUser?: () => Promise<SessionUser | null>;
|
|
237
251
|
}
|
|
238
252
|
declare function createUsersApiHandlers(config: UsersApiConfig): {
|
|
239
253
|
list(req: Request): Promise<Response>;
|
|
@@ -299,7 +313,7 @@ interface ChatApiConfig extends CmsHandlersBase {
|
|
|
299
313
|
}
|
|
300
314
|
|
|
301
315
|
/**
|
|
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).
|
|
316
|
+
* 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
317
|
*/
|
|
304
318
|
|
|
305
319
|
/** CMS instance with getPlugin; when provided, analytics and userAuth.sendEmail can be resolved from plugins when not passed. */
|
|
@@ -324,6 +338,8 @@ interface CmsApiHandlerConfig {
|
|
|
324
338
|
userAuth?: UserAuthApiConfig;
|
|
325
339
|
/** GET /api/dashboard/stats */
|
|
326
340
|
dashboard?: DashboardStatsConfig;
|
|
341
|
+
/** GET /api/dashboard/ecommerce */
|
|
342
|
+
ecommerceAnalytics?: EcommerceAnalyticsConfig;
|
|
327
343
|
/** GET /api/analytics. If omitted and getCms is set, uses getCms().getPlugin('analytics'). */
|
|
328
344
|
analytics?: AnalyticsHandlerConfig;
|
|
329
345
|
/** POST /api/upload (S3 or local) */
|
|
@@ -348,7 +364,10 @@ interface CmsApiHandlerConfig {
|
|
|
348
364
|
settings?: SettingsApiConfig;
|
|
349
365
|
/** POST /api/chat/identify, GET /api/chat/conversations/:id/messages, POST /api/chat/messages */
|
|
350
366
|
chat?: ChatApiConfig;
|
|
351
|
-
/**
|
|
367
|
+
/**
|
|
368
|
+
* Entity-level RBAC (session `entityPerms` / Administrator). When omitted, admin routes that need it respond with 403
|
|
369
|
+
* (`entity_rbac_required`) so CRUD cannot run as auth-only. Pass `createAuthHelpers(...).requireEntityPermission` from the app.
|
|
370
|
+
*/
|
|
352
371
|
requireEntityPermission?: (req: Request, entity: string, action: EntityCrudAction) => Promise<Response | null>;
|
|
353
372
|
/** Required for GET/POST/PATCH/DELETE /api/admin/roles */
|
|
354
373
|
getSessionUser?: () => Promise<SessionUser | null>;
|
|
@@ -392,4 +411,4 @@ declare function createStorefrontApiHandler(config: StorefrontApiConfig): {
|
|
|
392
411
|
handle(method: string, path: string[], req: Request): Promise<Response>;
|
|
393
412
|
};
|
|
394
413
|
|
|
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,
|
|
414
|
+
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 };
|