@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.
Files changed (52) hide show
  1. package/README.md +739 -724
  2. package/dist/admin.cjs +1840 -741
  3. package/dist/admin.cjs.map +1 -1
  4. package/dist/admin.d.cts +4 -0
  5. package/dist/admin.d.ts +4 -0
  6. package/dist/admin.js +1795 -681
  7. package/dist/admin.js.map +1 -1
  8. package/dist/api.cjs +700 -77
  9. package/dist/api.cjs.map +1 -1
  10. package/dist/api.d.cts +1 -1
  11. package/dist/api.d.ts +1 -1
  12. package/dist/api.js +696 -75
  13. package/dist/api.js.map +1 -1
  14. package/dist/auth.cjs.map +1 -1
  15. package/dist/auth.js.map +1 -1
  16. package/dist/cli.cjs +21 -6
  17. package/dist/cli.cjs.map +1 -1
  18. package/dist/cli.js +21 -6
  19. package/dist/cli.js.map +1 -1
  20. package/dist/hooks.cjs +159 -0
  21. package/dist/hooks.cjs.map +1 -1
  22. package/dist/hooks.d.cts +24 -1
  23. package/dist/hooks.d.ts +24 -1
  24. package/dist/hooks.js +165 -0
  25. package/dist/hooks.js.map +1 -1
  26. package/dist/{index-BQnqJ7EO.d.cts → index-D2C1O9b4.d.cts} +22 -3
  27. package/dist/{index-BiagwMjV.d.ts → index-GMn7-9PX.d.ts} +22 -3
  28. package/dist/index.cjs +5334 -4336
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +89 -11
  31. package/dist/index.d.ts +89 -11
  32. package/dist/index.js +5333 -4352
  33. package/dist/index.js.map +1 -1
  34. package/dist/migrations/1772178563554-InitialSchema.ts +304 -304
  35. package/dist/migrations/1772178563555-ChatAndKnowledgeBase.ts +55 -55
  36. package/dist/migrations/1772178563556-KnowledgeBaseVector.ts +16 -16
  37. package/dist/migrations/1774300000000-RbacSeedGroupsAndPermissionUnique.ts +24 -24
  38. package/dist/migrations/1774300000001-SeedAdministratorUsersPermission.ts +35 -35
  39. package/dist/migrations/1774400000000-CustomerAdminAccessContactUser.ts +37 -37
  40. package/dist/migrations/1774400000001-StorefrontCartWishlist.ts +100 -100
  41. package/dist/migrations/1774400000002-WishlistGuestId.ts +29 -29
  42. package/dist/migrations/1774500000000-ProductCollectionHsn.ts +15 -15
  43. package/dist/migrations/1774600000000-OrderKindParentOrderNumber.ts +36 -36
  44. package/dist/migrations/1774800000000-OtpChallengesUserPhone.ts +41 -41
  45. package/dist/migrations/1774900000000-MessageTemplates.ts +39 -39
  46. package/dist/migrations/1775000000000-ProductUomTypeOrderItemSnapshots.ts +29 -29
  47. package/dist/migrations/1775200000000-MediaDriveFolders.ts +38 -0
  48. package/dist/migrations/README.md +3 -3
  49. package/dist/theme.cjs.map +1 -1
  50. package/dist/theme.js.map +1 -1
  51. package/package.json +20 -15
  52. 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
- /** When set, CRUD and admin routes enforce entity-level permissions from session */
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, createFormBySlugHandler as H, type InviteAcceptConfig as I, createInviteAcceptHandler as J, createSetPasswordHandler as K, createSettingsApiHandlers as L, createStorefrontApiHandler as M, createUploadHandler as N, type OrderPlacedLineItem as O, createUserAuthApiRouter as P, createUserAvatarHandler as Q, createUserProfileHandler as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createUsersApiHandlers as V, getCompanyDetailsFromSettings as W, getPublicSettingsGroup as X, mergeEmailLayoutCompanyDetails as Y, 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 FormBySlugConfig as h, type GetPublicSettingsGroupDataSource as i, type SetPasswordConfig as j, type SettingsApiConfig as k, type SocialLinkItem as l, type StorefrontApiConfig as m, type StorefrontOtpFlags as n, type UserAuthApiConfig as o, type UserAvatarConfig as p, type UserProfileConfig as q, type UsersApiConfig as r, createAnalyticsHandlers as s, createBlogBySlugHandler as t, createChangePasswordHandler as u, createCmsApiHandler as v, createCrudByIdHandler as w, createCrudHandler as x, createDashboardStatsHandler as y, createForgotPasswordHandler as z };
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
- /** When set, CRUD and admin routes enforce entity-level permissions from session */
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, createFormBySlugHandler as H, type InviteAcceptConfig as I, createInviteAcceptHandler as J, createSetPasswordHandler as K, createSettingsApiHandlers as L, createStorefrontApiHandler as M, createUploadHandler as N, type OrderPlacedLineItem as O, createUserAuthApiRouter as P, createUserAvatarHandler as Q, createUserProfileHandler as R, type StorageService as S, type TemplateContext as T, type UploadHandlerConfig as U, createUsersApiHandlers as V, getCompanyDetailsFromSettings as W, getPublicSettingsGroup as X, mergeEmailLayoutCompanyDetails as Y, 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 FormBySlugConfig as h, type GetPublicSettingsGroupDataSource as i, type SetPasswordConfig as j, type SettingsApiConfig as k, type SocialLinkItem as l, type StorefrontApiConfig as m, type StorefrontOtpFlags as n, type UserAuthApiConfig as o, type UserAvatarConfig as p, type UserProfileConfig as q, type UsersApiConfig as r, createAnalyticsHandlers as s, createBlogBySlugHandler as t, createChangePasswordHandler as u, createCmsApiHandler as v, createCrudByIdHandler as w, createCrudHandler as x, createDashboardStatsHandler as y, createForgotPasswordHandler as z };
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 };