@flowselections/core 1.0.21 → 1.0.23

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 (60) hide show
  1. package/README.md +61 -0
  2. package/dist/auth/redirect.d.ts +52 -0
  3. package/dist/auth/redirect.d.ts.map +1 -0
  4. package/dist/auth/redirect.js +98 -0
  5. package/dist/auth/useAuth.d.ts +1 -1
  6. package/dist/auth/useAuth.d.ts.map +1 -1
  7. package/dist/auth/useIsSuperadmin.d.ts +19 -0
  8. package/dist/auth/useIsSuperadmin.d.ts.map +1 -0
  9. package/dist/auth/useIsSuperadmin.js +16 -0
  10. package/dist/components/layout/AuthenticatedLayout.d.ts +29 -0
  11. package/dist/components/layout/AuthenticatedLayout.d.ts.map +1 -0
  12. package/dist/components/layout/AuthenticatedLayout.js +49 -0
  13. package/dist/components/layout/HandoverPage.d.ts.map +1 -1
  14. package/dist/components/layout/HandoverPage.js +4 -9
  15. package/dist/components/layout/Header.js +1 -1
  16. package/dist/components/layout/InstellingenPage.d.ts.map +1 -1
  17. package/dist/components/layout/InstellingenPage.js +10 -2
  18. package/dist/components/layout/LoginPage.d.ts.map +1 -1
  19. package/dist/components/layout/LoginPage.js +6 -6
  20. package/dist/components/layout/ResetWachtwoordPage.d.ts +3 -1
  21. package/dist/components/layout/ResetWachtwoordPage.d.ts.map +1 -1
  22. package/dist/components/layout/ResetWachtwoordPage.js +5 -3
  23. package/dist/components/layout/Sidebar.d.ts +37 -3
  24. package/dist/components/layout/Sidebar.d.ts.map +1 -1
  25. package/dist/components/layout/Sidebar.js +52 -13
  26. package/dist/extensions/ShellExtensions.d.ts +14 -1
  27. package/dist/extensions/ShellExtensions.d.ts.map +1 -1
  28. package/dist/extensions/ShellExtensions.js +21 -2
  29. package/dist/hooks/useLogoSrc.d.ts +7 -0
  30. package/dist/hooks/useLogoSrc.d.ts.map +1 -0
  31. package/dist/hooks/useLogoSrc.js +29 -0
  32. package/dist/index.d.ts +20 -6
  33. package/dist/index.d.ts.map +1 -1
  34. package/dist/index.js +12 -3
  35. package/dist/permissions/resolve.d.ts +22 -0
  36. package/dist/permissions/resolve.d.ts.map +1 -0
  37. package/dist/permissions/resolve.js +76 -0
  38. package/dist/permissions/store.d.ts +38 -0
  39. package/dist/permissions/store.d.ts.map +1 -0
  40. package/dist/permissions/store.js +180 -0
  41. package/dist/permissions/useModulePermission.d.ts +52 -32
  42. package/dist/permissions/useModulePermission.d.ts.map +1 -1
  43. package/dist/permissions/useModulePermission.js +72 -104
  44. package/dist/routes/index.d.ts +48 -0
  45. package/dist/routes/index.d.ts.map +1 -0
  46. package/dist/routes/index.js +63 -0
  47. package/dist/server/index.d.ts +1 -1
  48. package/dist/server/index.d.ts.map +1 -1
  49. package/dist/server/index.js +1 -1
  50. package/dist/server/permissions.server.d.ts +21 -8
  51. package/dist/server/permissions.server.d.ts.map +1 -1
  52. package/dist/server/permissions.server.js +54 -9
  53. package/dist/shell/ShellContext.d.ts +26 -0
  54. package/dist/shell/ShellContext.d.ts.map +1 -0
  55. package/dist/shell/ShellContext.js +14 -0
  56. package/dist/styles.css +127 -0
  57. package/dist/types/module.d.ts +39 -4
  58. package/dist/types/module.d.ts.map +1 -1
  59. package/dist/types/module.js +33 -5
  60. package/package.json +115 -107
@@ -1,32 +1,71 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { useState } from "react";
2
+ import { useEffect, useState } from "react";
3
3
  import { Link, useLocation } from "@tanstack/react-router";
4
- import { Settings, LogOut, ChevronDown } from "lucide-react";
4
+ import { Settings, LogOut, ChevronDown, Menu } from "lucide-react";
5
+ import { Sheet, SheetContent, SheetTitle } from "../ui/sheet";
6
+ import { Skeleton } from "../ui/skeleton";
5
7
  import { cn } from "../../lib/utils";
6
- import { useModuleListTransform } from "../../extensions/ShellExtensions.js";
8
+ import { useFooterItems, useModuleListTransform } from "../../extensions/ShellExtensions.js";
9
+ import { usePermissionFilteredModules } from "../../permissions/useModulePermission.js";
10
+ import { useLogoSrc } from "../../hooks/useLogoSrc.js";
11
+ /**
12
+ * Sinds 1.0.23. Pipeline: eerst rechten, dan extensie-transforms (ordening).
13
+ * Zichtbaarheid, geen beveiliging.
14
+ */
15
+ export function useSidebarModules(modules, permissions) {
16
+ const filtered = usePermissionFilteredModules(modules, permissions);
17
+ const visible = useModuleListTransform(filtered.modules);
18
+ return { visibleModules: visible, loading: filtered.loading };
19
+ }
7
20
  // ─── Single flat nav link ─────────────────────────────────────────────────────
8
- function SidebarLink({ item, pathname, indent = false, }) {
21
+ function SidebarLink({ item, pathname, indent = false, onNavigate, }) {
9
22
  const isActive = pathname === item.href;
10
- return (_jsxs(Link, { to: item.href, className: cn("flex items-center gap-3 rounded-lg px-3 text-sm font-medium transition-colors", indent ? "py-2" : "py-2.5", isActive
23
+ return (_jsxs(Link, { to: item.href, onClick: onNavigate, className: cn("flex items-center gap-3 rounded-lg px-3 text-sm font-medium transition-colors", indent ? "py-2" : "py-2.5", isActive
11
24
  ? "bg-sidebar-accent text-sidebar-accent-foreground"
12
25
  : "text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), children: [_jsx(item.icon, { className: cn(indent ? "h-4 w-4" : "h-5 w-5") }), item.label] }));
13
26
  }
14
27
  // ─── Collapsible group (for modules with children) ────────────────────────────
15
- function SidebarGroup({ item, pathname, }) {
28
+ function SidebarGroup({ item, pathname, onNavigate, }) {
16
29
  const groupActive = item.children?.some((child) => pathname === child.href || pathname.startsWith(child.href + "/")) ?? false;
17
30
  const [open, setOpen] = useState(groupActive);
18
31
  return (_jsxs(_Fragment, { children: [_jsxs("button", { onClick: () => setOpen(!open), className: cn("flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors cursor-pointer", groupActive
19
32
  ? "text-sidebar-accent-foreground"
20
- : "text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), children: [_jsx(item.icon, { className: "h-5 w-5" }), _jsx("span", { className: "flex-1 text-left", children: item.label }), _jsx(ChevronDown, { className: cn("h-4 w-4 transition-transform", open && "rotate-180") })] }), open && (_jsx("div", { className: "ml-3 space-y-1", children: item.children.map((child) => (_jsx(SidebarLink, { item: child, pathname: pathname, indent: true }, child.href))) }))] }));
33
+ : "text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"), children: [_jsx(item.icon, { className: "h-5 w-5" }), _jsx("span", { className: "flex-1 text-left", children: item.label }), _jsx(ChevronDown, { className: cn("h-4 w-4 transition-transform", open && "rotate-180") })] }), open && (_jsx("div", { className: "ml-3 space-y-1", children: item.children.map((child) => (_jsx(SidebarLink, { item: child, pathname: pathname, indent: true, onNavigate: onNavigate }, child.href))) }))] }));
34
+ }
35
+ // ─── Shared content (desktop aside + mobile sheet) ────────────────────────────
36
+ function SidebarContent({ auth, visibleModules, loading, logoSrc, showSettings, pathname, onNavigate, }) {
37
+ const handleSignOut = async () => {
38
+ onNavigate?.();
39
+ await auth.signOut();
40
+ };
41
+ return (_jsxs("div", { className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "flex shrink-0 items-center justify-center px-12 py-0 border-b border-sidebar-border", children: _jsx("img", { src: logoSrc, alt: "Logo", className: "max-h-24 w-auto max-w-full object-contain" }) }), _jsx("nav", { className: "min-h-0 flex-1 space-y-1 px-3 py-4 overflow-y-auto sidebar-scroll", "aria-busy": loading || undefined, children: loading ? (_jsx("div", { "data-testid": "sidebar-skeleton", className: "space-y-2", children: [0, 1, 2, 3].map((i) => (_jsx(Skeleton, { className: "h-9 w-full bg-sidebar-accent" }, i))) })) : (visibleModules.map((mod) => mod.nav.children?.length ? (_jsx(SidebarGroup, { item: mod.nav, pathname: pathname, onNavigate: onNavigate }, mod.id)) : (_jsx(SidebarLink, { item: mod.nav, pathname: pathname, onNavigate: onNavigate }, mod.id)))) }), _jsxs("div", { className: "shrink-0 border-t border-sidebar-border px-3 py-4", children: [showSettings && (_jsx(SidebarLink, { item: { label: "Instellingen", href: "/instellingen", icon: Settings }, pathname: pathname, onNavigate: onNavigate })), _jsxs("button", { onClick: handleSignOut, className: "flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground mt-1 cursor-pointer", children: [_jsx(LogOut, { className: "h-5 w-5" }), "Uitloggen"] })] })] }));
21
42
  }
22
43
  // ─── Main Sidebar ─────────────────────────────────────────────────────────────
23
- export function Sidebar({ auth, modules }) {
44
+ /**
45
+ * Desktop (md+): vaste zijbalk links, w-64.
46
+ * Mobiel (<md): vaste topbalk (h-14) met menuknop die de zijbalk als Sheet opent.
47
+ */
48
+ export function Sidebar({ auth, modules, shellId, permissions = false, logoSrc, showSettings = true, visibleModules, loading, }) {
24
49
  const location = useLocation();
25
50
  const pathname = location.pathname;
26
- // Zonder ShellExtensionsProvider komt de lijst ongewijzigd terug (code-volgorde).
27
- const visibleModules = useModuleListTransform(modules);
28
- const handleSignOut = async () => {
29
- await auth.signOut();
51
+ const [mobileOpen, setMobileOpen] = useState(false);
52
+ const own = useSidebarModules(modules, permissions);
53
+ const list = visibleModules ?? own.visibleModules;
54
+ const isLoading = loading ?? (visibleModules ? false : own.loading);
55
+ const footer = useFooterItems({ settings: showSettings });
56
+ const logo = useLogoSrc(logoSrc);
57
+ // Sluit het mobiele menu bij elke routewijziging.
58
+ useEffect(() => {
59
+ setMobileOpen(false);
60
+ }, [pathname]);
61
+ const close = () => setMobileOpen(false);
62
+ const contentProps = {
63
+ auth,
64
+ visibleModules: list,
65
+ loading: isLoading,
66
+ logoSrc: logo,
67
+ showSettings: footer.settings,
68
+ pathname,
30
69
  };
31
- return (_jsx("aside", { className: "fixed left-0 top-0 z-40 h-screen w-64 bg-sidebar", children: _jsxs("div", { className: "flex h-full flex-col", children: [_jsx("div", { className: "px-12 py-0 border-b border-sidebar-border", children: _jsx("img", { src: "/logo.png", alt: "Logo", className: "w-full h-auto object-contain" }) }), _jsx("nav", { className: "flex-1 space-y-1 px-3 py-4 overflow-y-auto sidebar-scroll", children: visibleModules.map((mod) => mod.nav.children?.length ? (_jsx(SidebarGroup, { item: mod.nav, pathname: pathname }, mod.id)) : (_jsx(SidebarLink, { item: mod.nav, pathname: pathname }, mod.id))) }), _jsxs("div", { className: "border-t border-sidebar-border px-3 py-4", children: [_jsx(SidebarLink, { item: { label: "Instellingen", href: "/instellingen", icon: Settings }, pathname: pathname }), _jsxs("button", { onClick: handleSignOut, className: "flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition-colors text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground mt-1 cursor-pointer", children: [_jsx(LogOut, { className: "h-5 w-5" }), "Uitloggen"] })] })] }) }));
70
+ return (_jsxs(_Fragment, { children: [_jsx("aside", { "data-shell": shellId, className: "fixed left-0 top-0 z-40 hidden h-screen h-dvh w-64 bg-sidebar md:block", children: _jsx(SidebarContent, { ...contentProps }) }), _jsxs("div", { className: "fixed inset-x-0 top-0 z-40 flex h-14 items-center gap-3 border-b border-sidebar-border bg-sidebar px-3 md:hidden", children: [_jsx("button", { type: "button", onClick: () => setMobileOpen(true), "aria-label": "Menu openen", className: "flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-sidebar-muted hover:bg-sidebar-accent hover:text-sidebar-accent-foreground", children: _jsx(Menu, { className: "h-5 w-5" }) }), _jsx("img", { src: logo, alt: "Logo", className: "h-8 w-auto max-w-[10rem] object-contain" })] }), _jsx(Sheet, { open: mobileOpen, onOpenChange: setMobileOpen, children: _jsxs(SheetContent, { side: "left", "data-shell": shellId, className: "w-64 max-w-[85vw] border-sidebar-border bg-sidebar p-0 md:hidden", children: [_jsx(SheetTitle, { className: "sr-only", children: "Navigatie" }), _jsx(SidebarContent, { ...contentProps, onNavigate: close })] }) })] }));
32
71
  }
@@ -3,6 +3,17 @@ import type { FlowModule } from "../types/module.js";
3
3
  /** Transformeert de modulelijst (filteren en/of sorteren). Puur, geen side effects. */
4
4
  export type ModuleListTransform = (modules: FlowModule[]) => FlowModule[];
5
5
  export type UseModuleListTransform = () => ModuleListTransform;
6
+ /**
7
+ * Sinds 1.0.23. Footer-items van de zijbalk. Uitloggen staat bewust NIET in dit
8
+ * type: het is nooit te verbergen. Zichtbaarheid, geen beveiliging.
9
+ */
10
+ export interface FooterItems {
11
+ settings: boolean;
12
+ }
13
+ export type FooterTransform = (items: FooterItems) => FooterItems;
14
+ export type UseFooterTransform = () => FooterTransform;
15
+ /** Sinds 1.0.23. Past de footer-transform toe; fout → ongewijzigd. Zichtbaarheid, geen beveiliging. */
16
+ export declare function useFooterItems(base: FooterItems): FooterItems;
6
17
  export interface ShellExtensionsProviderProps {
7
18
  /**
8
19
  * Eén of meer hooks die elk een ModuleListTransform teruggeven.
@@ -16,9 +27,11 @@ export interface ShellExtensionsProviderProps {
16
27
  guard?: ComponentType<{
17
28
  children: ReactNode;
18
29
  }>;
30
+ /** Sinds 1.0.23. Optionele footer-transform (stabiele referentie). Uitloggen blijft altijd. */
31
+ useFooter?: UseFooterTransform;
19
32
  children: ReactNode;
20
33
  }
21
- export declare function ShellExtensionsProvider({ useModuleList, guard, children, }: ShellExtensionsProviderProps): import("react/jsx-runtime").JSX.Element;
34
+ export declare function ShellExtensionsProvider({ useModuleList, guard, useFooter, children, }: ShellExtensionsProviderProps): import("react/jsx-runtime").JSX.Element;
22
35
  export declare function useShellGuard(): ComponentType<{
23
36
  children: ReactNode;
24
37
  }> | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"ShellExtensions.d.ts","sourceRoot":"","sources":["../../src/core/extensions/ShellExtensions.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,uFAAuF;AACvF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,CAAC;AAE1E,MAAM,MAAM,sBAAsB,GAAG,MAAM,mBAAmB,CAAC;AAS/D,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;IAClE,kFAAkF;IAClF,KAAK,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IAC/C,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,EACtC,aAAa,EACb,KAAK,EACL,QAAQ,GACT,EAAE,4BAA4B,2CAY9B;AAED,wBAAgB,aAAa,IAAI,aAAa,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,SAAS,CAElF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAuB1E"}
1
+ {"version":3,"file":"ShellExtensions.d.ts","sourceRoot":"","sources":["../../src/core/extensions/ShellExtensions.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD,uFAAuF;AACvF,MAAM,MAAM,mBAAmB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,UAAU,EAAE,CAAC;AAE1E,MAAM,MAAM,sBAAsB,GAAG,MAAM,mBAAmB,CAAC;AAE/D;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB;AACD,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW,CAAC;AAClE,MAAM,MAAM,kBAAkB,GAAG,MAAM,eAAe,CAAC;AAYvD,uGAAuG;AACvG,wBAAgB,cAAc,CAAC,IAAI,EAAE,WAAW,GAAG,WAAW,CAc7D;AAED,MAAM,WAAW,4BAA4B;IAC3C;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,sBAAsB,GAAG,sBAAsB,EAAE,CAAC;IAClE,kFAAkF;IAClF,KAAK,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,SAAS,CAAA;KAAE,CAAC,CAAC;IAC/C,+FAA+F;IAC/F,SAAS,CAAC,EAAE,kBAAkB,CAAC;IAC/B,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED,wBAAgB,uBAAuB,CAAC,EACtC,aAAa,EACb,KAAK,EACL,SAAS,EACT,QAAQ,GACT,EAAE,4BAA4B,2CAY9B;AAED,wBAAgB,aAAa,IAAI,aAAa,CAAC;IAAE,QAAQ,EAAE,SAAS,CAAA;CAAE,CAAC,GAAG,SAAS,CAElF;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAuB1E"}
@@ -1,13 +1,32 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { createContext, useContext, useMemo } from "react";
3
3
  const ShellExtensionsContext = createContext({ transforms: [] });
4
- export function ShellExtensionsProvider({ useModuleList, guard, children, }) {
4
+ const identityFooter = () => (items) => items;
5
+ /** Sinds 1.0.23. Past de footer-transform toe; fout → ongewijzigd. Zichtbaarheid, geen beveiliging. */
6
+ export function useFooterItems(base) {
7
+ const useF = useContext(ShellExtensionsContext).useFooter ?? identityFooter;
8
+ let t = (i) => i;
9
+ try {
10
+ t = useF();
11
+ }
12
+ catch {
13
+ /* fail-open */
14
+ }
15
+ try {
16
+ const next = t(base);
17
+ return { settings: typeof next?.settings === "boolean" ? next.settings : base.settings };
18
+ }
19
+ catch {
20
+ return base;
21
+ }
22
+ }
23
+ export function ShellExtensionsProvider({ useModuleList, guard, useFooter, children, }) {
5
24
  const transforms = useMemo(() => (Array.isArray(useModuleList) ? useModuleList : useModuleList ? [useModuleList] : []),
6
25
  // Bewust op de referentie(s): de hooks moeten stabiel zijn, zodat de
7
26
  // hook-volgorde tussen renders nooit verandert.
8
27
  // eslint-disable-next-line react-hooks/exhaustive-deps
9
28
  [useModuleList]);
10
- const value = useMemo(() => ({ transforms, guard }), [transforms, guard]);
29
+ const value = useMemo(() => ({ transforms, guard, useFooter }), [transforms, guard, useFooter]);
11
30
  return _jsx(ShellExtensionsContext.Provider, { value: value, children: children });
12
31
  }
13
32
  export function useShellGuard() {
@@ -0,0 +1,7 @@
1
+ export declare const DEFAULT_LOGO_SRC = "/logo.png";
2
+ /**
3
+ * Sinds 1.0.23. Logopad: prop wint, dan `logo` uit `/huisstijl.json`, dan
4
+ * `/logo.png`.
5
+ */
6
+ export declare function useLogoSrc(prop?: string): string;
7
+ //# sourceMappingURL=useLogoSrc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useLogoSrc.d.ts","sourceRoot":"","sources":["../../src/core/hooks/useLogoSrc.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,gBAAgB,cAAc,CAAC;AAa5C;;;GAGG;AACH,wBAAgB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAWhD"}
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from "react";
2
+ export const DEFAULT_LOGO_SRC = "/logo.png";
3
+ let huisstijlLogo = null;
4
+ function loadHuisstijlLogo() {
5
+ if (!huisstijlLogo) {
6
+ huisstijlLogo = fetch("/huisstijl.json")
7
+ .then((r) => (r.ok ? r.json() : null))
8
+ .then((j) => (j && typeof j.logo === "string" && j.logo ? j.logo : null))
9
+ .catch(() => null);
10
+ }
11
+ return huisstijlLogo;
12
+ }
13
+ /**
14
+ * Sinds 1.0.23. Logopad: prop wint, dan `logo` uit `/huisstijl.json`, dan
15
+ * `/logo.png`.
16
+ */
17
+ export function useLogoSrc(prop) {
18
+ const [fromFile, setFromFile] = useState(null);
19
+ useEffect(() => {
20
+ if (prop || typeof window === "undefined")
21
+ return;
22
+ let alive = true;
23
+ loadHuisstijlLogo().then((v) => alive && setFromFile(v));
24
+ return () => {
25
+ alive = false;
26
+ };
27
+ }, [prop]);
28
+ return prop || fromFile || DEFAULT_LOGO_SRC;
29
+ }
package/dist/index.d.ts CHANGED
@@ -3,12 +3,23 @@ export type { AuthContext, AuthState, AuthActions, AppRole } from "./auth/useAut
3
3
  export { supabase } from "./supabase/client";
4
4
  export type { Database } from "./supabase/types";
5
5
  export { LoginPage } from "./components/layout/LoginPage";
6
+ export { requireAuth, safeRedirectPath, readRedirectParam } from "./auth/redirect";
7
+ export { resolveModuleAccess, canAccessModule } from "./auth/redirect";
8
+ export type { RequireAuthArgs, ModuleAccessContext } from "./auth/redirect";
9
+ export { useIsSuperadmin, roleFlags } from "./auth/useIsSuperadmin.js";
6
10
  export { HandoverPage } from "./components/layout/HandoverPage";
7
11
  export type { HandoverPageProps } from "./components/layout/HandoverPage";
8
12
  export { ResetWachtwoordPage } from "./components/layout/ResetWachtwoordPage";
9
13
  export { Header } from "./components/layout/Header";
10
14
  export { InstellingenPage } from "./components/layout/InstellingenPage";
11
15
  export { Sidebar } from "./components/layout/Sidebar";
16
+ export type { SidebarProps } from "./components/layout/Sidebar";
17
+ export { AuthenticatedLayout, NoAccess } from "./components/layout/AuthenticatedLayout";
18
+ export { useSidebarModules } from "./components/layout/Sidebar";
19
+ export { useShell, useShellModules } from "./shell/ShellContext.js";
20
+ export type { ShellContextValue } from "./shell/ShellContext.js";
21
+ export { useLogoSrc } from "./hooks/useLogoSrc.js";
22
+ export type { AuthenticatedLayoutProps } from "./components/layout/AuthenticatedLayout";
12
23
  export { Toaster } from "./components/ui/sonner";
13
24
  export { Button } from "./components/ui/button";
14
25
  export { Input } from "./components/ui/input";
@@ -32,13 +43,16 @@ export { cn } from "./lib/utils";
32
43
  export { validateFileUpload, ALLOWED_FILE_TYPES, loginSchema } from "./lib/validationSchemas";
33
44
  export { useIsMobile } from "./hooks/use-mobile";
34
45
  export { useHuisstijl } from "./hooks/useHuisstijl";
35
- export type { FlowModule, NavItem, SettingsCard } from "./types/module";
36
- export { moduleKey, navKey } from "./types/module.js";
37
- export { ShellExtensionsProvider, useModuleListTransform } from "./extensions/ShellExtensions.js";
38
- export type { ModuleListTransform, UseModuleListTransform, ShellExtensionsProviderProps, } from "./extensions/ShellExtensions.js";
46
+ export type { FlowModule, NavItem, SettingsCard, FeatureDefinition } from "./types/module";
47
+ export { moduleKey, navKey, featureKey, moduleFeatures } from "./types/module.js";
48
+ export { ShellExtensionsProvider, useModuleListTransform, useFooterItems } from "./extensions/ShellExtensions.js";
49
+ export type { ModuleListTransform, UseModuleListTransform, ShellExtensionsProviderProps, FooterItems, FooterTransform, UseFooterTransform, } from "./extensions/ShellExtensions.js";
39
50
  export { ModuleGuardSlot } from "./extensions/ModuleGuardSlot.js";
40
- export { useMyModulePermissions, useModulePermission, useModulePermissionTransform, } from "./permissions/useModulePermission.js";
41
- export type { PermissionLevel, ModulePermissionRow } from "./permissions/useModulePermission.js";
51
+ export { useMyModulePermissions, useModulePermission, useModulePermissionTransform, useCan, Can, usePermissionFilteredModules, } from "./permissions/useModulePermission.js";
52
+ export type { PermissionLevel, ModulePermissionRow, CanProps } from "./permissions/useModulePermission.js";
53
+ export { filterModules, resolveLevel } from "./permissions/resolve.js";
54
+ export { usePermissionSnapshot } from "./permissions/store.js";
55
+ export type { PermissionSnapshot, PermissionStatus } from "./permissions/store.js";
42
56
  export { ModuleAccessGuard } from "./permissions/ModuleAccessGuard.js";
43
57
  export type { ModuleAccessGuardProps } from "./permissions/ModuleAccessGuard.js";
44
58
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/core/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/core/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -4,11 +4,18 @@ export { useAuth, useAuthState } from "./auth/useAuth";
4
4
  export { supabase } from "./supabase/client";
5
5
  // Layout
6
6
  export { LoginPage } from "./components/layout/LoginPage";
7
+ export { requireAuth, safeRedirectPath, readRedirectParam } from "./auth/redirect";
8
+ export { resolveModuleAccess, canAccessModule } from "./auth/redirect";
9
+ export { useIsSuperadmin, roleFlags } from "./auth/useIsSuperadmin.js";
7
10
  export { HandoverPage } from "./components/layout/HandoverPage";
8
11
  export { ResetWachtwoordPage } from "./components/layout/ResetWachtwoordPage";
9
12
  export { Header } from "./components/layout/Header";
10
13
  export { InstellingenPage } from "./components/layout/InstellingenPage";
11
14
  export { Sidebar } from "./components/layout/Sidebar";
15
+ export { AuthenticatedLayout, NoAccess } from "./components/layout/AuthenticatedLayout";
16
+ export { useSidebarModules } from "./components/layout/Sidebar";
17
+ export { useShell, useShellModules } from "./shell/ShellContext.js";
18
+ export { useLogoSrc } from "./hooks/useLogoSrc.js";
12
19
  export { Toaster } from "./components/ui/sonner";
13
20
  // UI components
14
21
  export { Button } from "./components/ui/button";
@@ -34,10 +41,12 @@ export { cn } from "./lib/utils";
34
41
  export { validateFileUpload, ALLOWED_FILE_TYPES, loginSchema } from "./lib/validationSchemas";
35
42
  export { useIsMobile } from "./hooks/use-mobile";
36
43
  export { useHuisstijl } from "./hooks/useHuisstijl";
37
- export { moduleKey, navKey } from "./types/module.js";
44
+ export { moduleKey, navKey, featureKey, moduleFeatures } from "./types/module.js";
38
45
  // Shell-extensiepunten (allemaal optioneel; zonder provider ongewijzigd gedrag)
39
- export { ShellExtensionsProvider, useModuleListTransform } from "./extensions/ShellExtensions.js";
46
+ export { ShellExtensionsProvider, useModuleListTransform, useFooterItems } from "./extensions/ShellExtensions.js";
40
47
  export { ModuleGuardSlot } from "./extensions/ModuleGuardSlot.js";
41
48
  // Rechten (leeskant; fail-open, zie README)
42
- export { useMyModulePermissions, useModulePermission, useModulePermissionTransform, } from "./permissions/useModulePermission.js";
49
+ export { useMyModulePermissions, useModulePermission, useModulePermissionTransform, useCan, Can, usePermissionFilteredModules, } from "./permissions/useModulePermission.js";
50
+ export { filterModules, resolveLevel } from "./permissions/resolve.js";
51
+ export { usePermissionSnapshot } from "./permissions/store.js";
43
52
  export { ModuleAccessGuard } from "./permissions/ModuleAccessGuard.js";
@@ -0,0 +1,22 @@
1
+ import type { FlowModule } from "../types/module.js";
2
+ export type PermissionLevel = "none" | "view" | "edit";
3
+ export interface ModulePermissionRow {
4
+ moduleId: string;
5
+ featureId: string | null;
6
+ permission: PermissionLevel;
7
+ }
8
+ export declare const RANK: Record<PermissionLevel, number>;
9
+ export declare function normalizeLevel(value: unknown): PermissionLevel;
10
+ /** RPC-rijen → genormaliseerd. Moduleniveau is `feature_id = ''` → `null`. */
11
+ export declare function normalizeRows(data: unknown): ModulePermissionRow[];
12
+ /** Feature-rij, anders modulerij, anders default-allow. */
13
+ export declare function resolveLevel(rows: ModulePermissionRow[], moduleId: string, featureId?: string | null): PermissionLevel;
14
+ /**
15
+ * Pure zijbalkfilter. `none` weg; groep zonder zichtbare kinderen verdwijnt,
16
+ * tenzij de module een eigen link heeft (nav.href niet tussen de kinderen) en
17
+ * zelf toegankelijk is — dan blijft hij als gewone link staan.
18
+ */
19
+ export declare function filterModules(modules: FlowModule[], rows: ModulePermissionRow[]): FlowModule[];
20
+ /** Paden die nooit door de route-guard worden beperkt. */
21
+ export declare const EXEMPT_KEYS: Set<string>;
22
+ //# sourceMappingURL=resolve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/core/permissions/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAGrD,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAEvD,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,UAAU,EAAE,eAAe,CAAC;CAC7B;AAED,eAAO,MAAM,IAAI,EAAE,MAAM,CAAC,eAAe,EAAE,MAAM,CAAiC,CAAC;AAEnF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,eAAe,CAI9D;AAED,8EAA8E;AAC9E,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,mBAAmB,EAAE,CAalE;AAED,2DAA2D;AAC3D,wBAAgB,YAAY,CAC1B,IAAI,EAAE,mBAAmB,EAAE,EAC3B,QAAQ,EAAE,MAAM,EAChB,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,GACxB,eAAe,CAajB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,mBAAmB,EAAE,GAAG,UAAU,EAAE,CAyB9F;AAED,0DAA0D;AAC1D,eAAO,MAAM,WAAW,aAAsF,CAAC"}
@@ -0,0 +1,76 @@
1
+ import { featureKey, moduleKey, navKey } from "../types/module.js";
2
+ export const RANK = { none: 0, view: 1, edit: 2 };
3
+ export function normalizeLevel(value) {
4
+ const v = typeof value === "string" ? value.toLowerCase() : "";
5
+ if (v === "none" || v === "view" || v === "edit")
6
+ return v;
7
+ return "edit"; // onbekend → fail-open
8
+ }
9
+ /** RPC-rijen → genormaliseerd. Moduleniveau is `feature_id = ''` → `null`. */
10
+ export function normalizeRows(data) {
11
+ if (!Array.isArray(data))
12
+ return [];
13
+ return data.map((raw) => {
14
+ const row = (raw ?? {});
15
+ const rawModule = String(row["module_id"] ?? row["moduleId"] ?? "");
16
+ const rawFeature = row["feature_id"] ?? row["featureId"] ?? null;
17
+ const feature = typeof rawFeature === "string" && rawFeature !== "" ? rawFeature : null;
18
+ return {
19
+ moduleId: navKey(rawModule),
20
+ featureId: feature,
21
+ permission: normalizeLevel(row["permission"] ?? row["level"] ?? row["permission_level"]),
22
+ };
23
+ });
24
+ }
25
+ /** Feature-rij, anders modulerij, anders default-allow. */
26
+ export function resolveLevel(rows, moduleId, featureId) {
27
+ const key = navKey(moduleId) || moduleId;
28
+ const forModule = rows.filter((r) => r.moduleId === key);
29
+ if (forModule.length === 0)
30
+ return "edit";
31
+ if (featureId) {
32
+ const feature = forModule.find((r) => r.featureId === featureId);
33
+ if (feature)
34
+ return feature.permission;
35
+ }
36
+ const moduleLevel = forModule.find((r) => !r.featureId);
37
+ if (moduleLevel)
38
+ return moduleLevel.permission;
39
+ if (forModule.some((r) => r.permission === "edit"))
40
+ return "edit";
41
+ if (forModule.some((r) => r.permission === "view"))
42
+ return "view";
43
+ return "none";
44
+ }
45
+ /**
46
+ * Pure zijbalkfilter. `none` weg; groep zonder zichtbare kinderen verdwijnt,
47
+ * tenzij de module een eigen link heeft (nav.href niet tussen de kinderen) en
48
+ * zelf toegankelijk is — dan blijft hij als gewone link staan.
49
+ */
50
+ export function filterModules(modules, rows) {
51
+ if (rows.length === 0)
52
+ return modules;
53
+ const result = [];
54
+ for (const mod of modules) {
55
+ const children = mod.nav.children;
56
+ if (children?.length) {
57
+ const allowed = children.filter((child) => resolveLevel(rows, navKey(child.href), child.featureId ?? (featureKey(child.href) || null)) !==
58
+ "none");
59
+ if (allowed.length > 0) {
60
+ result.push({ ...mod, nav: { ...mod.nav, children: allowed } });
61
+ continue;
62
+ }
63
+ const ownLink = !children.some((c) => c.href === mod.nav.href);
64
+ if (ownLink && resolveLevel(rows, moduleKey(mod)) !== "none") {
65
+ result.push({ ...mod, nav: { ...mod.nav, children: undefined } });
66
+ }
67
+ continue;
68
+ }
69
+ if (resolveLevel(rows, moduleKey(mod), mod.nav.featureId ?? null) === "none")
70
+ continue;
71
+ result.push(mod);
72
+ }
73
+ return result;
74
+ }
75
+ /** Paden die nooit door de route-guard worden beperkt. */
76
+ export const EXEMPT_KEYS = new Set(["", "instellingen", "login", "logout", "auth", "reset-wachtwoord", "api"]);
@@ -0,0 +1,38 @@
1
+ import { type ModulePermissionRow } from "./resolve.js";
2
+ export type PermissionStatus = "loading" | "ready" | "error" | "timeout";
3
+ export interface PermissionSnapshot {
4
+ userId: string | null;
5
+ status: PermissionStatus;
6
+ rows: ModulePermissionRow[];
7
+ /** Rollen uit de JWT-claim `user_roles`, terugval tabel `user_roles`. */
8
+ roles: string[];
9
+ rolesLoaded: boolean;
10
+ }
11
+ export declare const PERMISSION_TIMEOUT_MS = 3000;
12
+ type Fetcher = () => Promise<unknown>;
13
+ type RolesFetcher = (userId: string) => Promise<string[]>;
14
+ export declare function subscribePermissions(listener: () => void): () => boolean;
15
+ export declare function getPermissionSnapshot(): PermissionSnapshot;
16
+ /** Rollen uit een JWT (claim `user_roles`). Geen verificatie: alleen UI. */
17
+ export declare function rolesFromAccessToken(token: string | undefined | null): string[] | null;
18
+ /**
19
+ * Laadt rechten (en rollen) voor `userId`. Dedupliceert en cachet 5 min.
20
+ * Na 3 s zonder antwoord gaat de status op `timeout` (fail-open); komt het
21
+ * antwoord later alsnog, dan wordt het `ready`.
22
+ */
23
+ export declare function loadPermissions(userId: string | null, accessToken?: string | null, force?: boolean): Promise<void>;
24
+ /** Wacht maximaal `ms` op rechten. Resolvet altijd (fail-open). */
25
+ export declare function waitForPermissions(userId: string | null, accessToken?: string | null, ms?: number): Promise<PermissionSnapshot>;
26
+ /** Koppelt de store eenmalig aan de Supabase-sessie (alleen in de browser). */
27
+ export declare function ensureSessionWiring(): void;
28
+ /** React-hook op de store. Werkt zonder QueryClientProvider. */
29
+ export declare function usePermissionSnapshot(): PermissionSnapshot;
30
+ export declare function __setPermissionFetchers(next: {
31
+ rows?: Fetcher;
32
+ roles?: RolesFetcher;
33
+ }): void;
34
+ export declare function __resetPermissionStore(): void;
35
+ /** Markeert de wiring als gedaan (tests/preview met een mock). */
36
+ export declare function __disableSessionWiring(): void;
37
+ export {};
38
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../../src/core/permissions/store.ts"],"names":[],"mappings":"AASA,OAAO,EAAiB,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAEvE,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,SAAS,CAAC;AAEzE,MAAM,WAAW,kBAAkB;IACjC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,MAAM,EAAE,gBAAgB,CAAC;IACzB,IAAI,EAAE,mBAAmB,EAAE,CAAC;IAC5B,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,eAAO,MAAM,qBAAqB,OAAO,CAAC;AAG1C,KAAK,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;AACtC,KAAK,YAAY,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AA8B1D,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,MAAM,IAAI,iBAGxD;AACD,wBAAgB,qBAAqB,uBAEpC;AAED,4EAA4E;AAC5E,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI,GAAG,MAAM,EAAE,GAAG,IAAI,CAetF;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,MAAM,GAAG,IAAI,EACrB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAC3B,KAAK,UAAQ,GACZ,OAAO,CAAC,IAAI,CAAC,CA0Cf;AAED,mEAAmE;AACnE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,EAAE,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,EAAE,SAAwB,+BAchH;AAGD,+EAA+E;AAC/E,wBAAgB,mBAAmB,SAmBlC;AAED,gEAAgE;AAChE,wBAAgB,qBAAqB,IAAI,kBAAkB,CAM1D;AAID,wBAAgB,uBAAuB,CAAC,IAAI,EAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,YAAY,CAAA;CAAE,QAGrF;AACD,wBAAgB,sBAAsB,SAQrC;AACD,kEAAkE;AAClE,wBAAgB,sBAAsB,SAErC"}