@flowselections/admin-installatietechniek 1.0.4 → 1.0.5

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 (29) hide show
  1. package/dist-lib/components/AdminForInstallatieTechniek.d.ts.map +1 -1
  2. package/dist-lib/components/AdminForInstallatieTechniek.js +15 -4
  3. package/dist-lib/components/ModuleAccessGuard.d.ts +15 -0
  4. package/dist-lib/components/ModuleAccessGuard.d.ts.map +1 -0
  5. package/dist-lib/components/ModuleAccessGuard.js +40 -0
  6. package/dist-lib/hooks/useModulePermission.d.ts +8 -2
  7. package/dist-lib/hooks/useModulePermission.d.ts.map +1 -1
  8. package/dist-lib/hooks/useModulePermission.js +26 -23
  9. package/dist-lib/index.d.ts +2 -0
  10. package/dist-lib/index.d.ts.map +1 -1
  11. package/dist-lib/index.js +2 -0
  12. package/dist-lib/integrations/supabase/auth-attacher.d.ts.map +1 -1
  13. package/dist-lib/integrations/supabase/auth-attacher.js +5 -1
  14. package/dist-lib/integrations/supabase/auth-middleware.d.ts +555 -117
  15. package/dist-lib/integrations/supabase/auth-middleware.d.ts.map +1 -1
  16. package/dist-lib/integrations/supabase/auth-middleware.js +21 -7
  17. package/dist-lib/integrations/supabase/client.d.ts +555 -117
  18. package/dist-lib/integrations/supabase/client.d.ts.map +1 -1
  19. package/dist-lib/integrations/supabase/client.server.d.ts +555 -117
  20. package/dist-lib/integrations/supabase/client.server.d.ts.map +1 -1
  21. package/dist-lib/integrations/supabase/types.d.ts +567 -128
  22. package/dist-lib/integrations/supabase/types.d.ts.map +1 -1
  23. package/dist-lib/lib/admin.functions.d.ts +30013 -1785
  24. package/dist-lib/lib/admin.functions.d.ts.map +1 -1
  25. package/dist-lib/lib/admin.functions.js +19 -1
  26. package/dist-lib/lib/permissions.server.d.ts +14 -0
  27. package/dist-lib/lib/permissions.server.d.ts.map +1 -0
  28. package/dist-lib/lib/permissions.server.js +26 -0
  29. package/package.json +87 -87
@@ -1 +1 @@
1
- {"version":3,"file":"AdminForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/AdminForInstallatieTechniek.tsx"],"names":[],"mappings":"AAsFA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,gCAAgC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AA+DD,wBAAgB,2BAA2B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE,gCAAqC,+BA2oCtG"}
1
+ {"version":3,"file":"AdminForInstallatieTechniek.d.ts","sourceRoot":"","sources":["../../src/components/AdminForInstallatieTechniek.tsx"],"names":[],"mappings":"AAyFA,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CAC3C;AAED,MAAM,WAAW,gCAAgC;IAC/C,8EAA8E;IAC9E,OAAO,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC7B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAiED,wBAAgB,2BAA2B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,GAAE,gCAAqC,+BAssCtG"}
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useState } from "react";
3
- import { useAuth, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
3
+ import { useAuth, AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, Badge, Button, Card, CardContent, CardHeader, CardTitle, Checkbox, Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubContent, DropdownMenuSubTrigger, DropdownMenuTrigger, Input, Label, Select, SelectContent, SelectItem, SelectTrigger, SelectValue, Switch, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, Tabs, TabsContent, TabsList, TabsTrigger, toast, } from "@flowselections/core";
4
4
  import { ChevronDown, ChevronRight, Copy, FlaskConical, MoreVertical, Plus, Pencil, Shield, UserPlus, UserX, } from "lucide-react";
5
- import { assignRole, createRole, createTestUser, deleteRole, deleteUser, duplicateRole, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, } from "../lib/admin.functions";
5
+ import { assignRole, createRole, createTestUser, deleteRole, deleteUser, duplicateRole, getRoles, getUserModulePermissions, getUsersWithRoles, inviteUser, removeRole, setModuleEnforce, setUserModulePermission, syncModuleRegistry, updateRole, updateUserName, } from "../lib/admin.functions";
6
6
  import { useDiscoveredModules } from "../lib/moduleDiscovery";
7
7
  import { supabase } from "../integrations/supabase/client";
8
8
  const USER_PERMISSION_LABELS = {
@@ -188,6 +188,17 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
188
188
  toast.error(getErrorMessage(error, "Opslaan mislukt"));
189
189
  }
190
190
  };
191
+ const handleEnforceChange = async (moduleId, enforce) => {
192
+ setRoleModules((prev) => prev.map((m) => (m.id === moduleId ? { ...m, enforce } : m)));
193
+ try {
194
+ await setModuleEnforce({ data: { moduleId, enforce } });
195
+ toast.success(`Module ${enforce ? "afgedwongen" : "niet langer afgedwongen"}`);
196
+ }
197
+ catch (error) {
198
+ setRoleModules((prev) => prev.map((m) => (m.id === moduleId ? { ...m, enforce: !enforce } : m)));
199
+ toast.error(getErrorMessage(error, "Afdwingen wijzigen mislukt"));
200
+ }
201
+ };
191
202
  const handleAssign = async (userId, roleId) => {
192
203
  setSubmitting(true);
193
204
  try {
@@ -422,7 +433,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
422
433
  if (!canAccess) {
423
434
  return (_jsx("main", { className: "p-6", children: _jsx(Card, { children: _jsx(CardContent, { className: "pt-6", children: _jsx("p", { className: "text-muted-foreground", children: "Je hebt geen toegang tot deze pagina." }) }) }) }));
424
435
  }
425
- return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "space-y-4", children: _jsxs("div", { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Gebruikers & rollen" })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Hier vind je alle gebruikers die toegang hebben en zie je welke rol zij hebben. Ook beheer je hier alle beschikbare rollen." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [isAdmin && activeTab === "roles" && (_jsxs(Button, { size: "sm", onClick: () => openRoleEditor(), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Nieuwe rol"] })), activeTab === "users" && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setTestOpen(true), children: [_jsx(FlaskConical, { className: "h-4 w-4 mr-2" }), "Testaccount"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setInviteOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Account aanmaken"] })] }))] })] }) }), _jsx(CardContent, { children: _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "users", children: "Gebruikers" }), _jsx(TabsTrigger, { value: "roles", children: "Rollen" }), _jsx(TabsTrigger, { value: "user-permissions", children: "Rechten per gebruiker" })] }), _jsxs(TabsContent, { value: "users", className: "mt-4 space-y-6", children: [_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Zoeken" }), _jsx(Input, { placeholder: "Zoek op naam of e-mail", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value) })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Filter op rol" }), _jsxs(Select, { value: roleFilter, onValueChange: setRoleFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle rollen" }), availableRoles.map((role) => (_jsx(SelectItem, { value: role.id, children: role.name }, role.id))), _jsx(SelectItem, { value: "none", children: "Geen rollen" })] })] })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Sorteren" }), _jsxs(Select, { value: sortBy, onValueChange: (value) => setSortBy(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "name-asc", children: "Naam A-Z" }), _jsx(SelectItem, { value: "name-desc", children: "Naam Z-A" }), _jsx(SelectItem, { value: "role-asc", children: "Rol A-Z" }), _jsx(SelectItem, { value: "role-desc", children: "Rol Z-A" })] })] })] })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters
436
+ return (_jsxs("main", { className: "p-6 space-y-6", children: [_jsxs(Card, { children: [_jsx(CardHeader, { className: "space-y-4", children: _jsxs("div", { className: "flex flex-row items-start justify-between gap-4", children: [_jsxs("div", { className: "space-y-1", children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Shield, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Gebruikers & rollen" })] }), _jsx("p", { className: "text-sm text-muted-foreground", children: "Hier vind je alle gebruikers die toegang hebben en zie je welke rol zij hebben. Ook beheer je hier alle beschikbare rollen." })] }), _jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [isAdmin && activeTab === "roles" && (_jsxs(Button, { size: "sm", onClick: () => openRoleEditor(), children: [_jsx(Plus, { className: "h-4 w-4 mr-2" }), "Nieuwe rol"] })), activeTab === "users" && (_jsxs(_Fragment, { children: [_jsxs(Button, { variant: "outline", size: "sm", onClick: () => setTestOpen(true), children: [_jsx(FlaskConical, { className: "h-4 w-4 mr-2" }), "Testaccount"] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setInviteOpen(true), children: [_jsx(UserPlus, { className: "h-4 w-4 mr-2" }), "Account aanmaken"] })] }))] })] }) }), _jsx(CardContent, { children: _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, children: [_jsxs(TabsList, { children: [_jsx(TabsTrigger, { value: "users", children: "Gebruikers" }), _jsx(TabsTrigger, { value: "roles", children: "Rollen" }), _jsx(TabsTrigger, { value: "user-permissions", children: "Rechten per gebruiker" }), _jsx(TabsTrigger, { value: "modules", children: "Modules" })] }), _jsxs(TabsContent, { value: "users", className: "mt-4 space-y-6", children: [_jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-end", children: [_jsxs("div", { className: "flex-1 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Zoeken" }), _jsx(Input, { placeholder: "Zoek op naam of e-mail", value: searchQuery, onChange: (e) => setSearchQuery(e.target.value) })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Filter op rol" }), _jsxs(Select, { value: roleFilter, onValueChange: setRoleFilter, children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "all", children: "Alle rollen" }), availableRoles.map((role) => (_jsx(SelectItem, { value: role.id, children: role.name }, role.id))), _jsx(SelectItem, { value: "none", children: "Geen rollen" })] })] })] }), _jsxs("div", { className: "w-full sm:w-48 space-y-2", children: [_jsx(Label, { className: "text-sm font-medium", children: "Sorteren" }), _jsxs(Select, { value: sortBy, onValueChange: (value) => setSortBy(value), children: [_jsx(SelectTrigger, { children: _jsx(SelectValue, {}) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "name-asc", children: "Naam A-Z" }), _jsx(SelectItem, { value: "name-desc", children: "Naam Z-A" }), _jsx(SelectItem, { value: "role-asc", children: "Rol A-Z" }), _jsx(SelectItem, { value: "role-desc", children: "Rol Z-A" })] })] })] })] }), loading ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Laden..." })) : filteredUsers.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: hasActiveFilters
426
437
  ? "Geen gebruikers gevonden met de huidige filters."
427
438
  : "Geen gebruikers gevonden." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Gebruiker" }), _jsx(TableHead, { children: "Rollen" }), _jsx(TableHead, { className: "w-16" })] }) }), _jsx(TableBody, { children: filteredUsers.map((u) => {
428
439
  const manageable = canManageUser(u);
@@ -439,7 +450,7 @@ export function AdminForInstallatieTechniek({ modules, shellId } = {}) {
439
450
  rows.push(_jsxs(TableRow, { children: [_jsx(TableCell, { className: "pl-8 text-sm text-muted-foreground", children: f.name }), _jsx(TableCell, { children: _jsxs(Select, { value: userPermMap[permCellKey(mod.id, f.id)] ?? "inherit", onValueChange: (v) => handleUserPermChange(mod.id, f.id, v), children: [_jsx(SelectTrigger, { className: "w-48", children: _jsx(SelectValue, {}) }), _jsx(SelectContent, { children: Object.keys(USER_PERMISSION_LABELS).map((p) => (_jsx(SelectItem, { value: p, children: USER_PERMISSION_LABELS[p] }, p))) })] }) })] }, `${mod.id}-${f.id}`));
440
451
  }
441
452
  return rows;
442
- }) })] }))] })] }) })] }), _jsx(Dialog, { open: editor !== null, onOpenChange: (open) => !open && !editorSaving && setEditor(null), children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: editor?.roleId ? "Rol bewerken" : "Rol toevoegen" }), _jsx(DialogDescription, { children: "Maak hier een rol aan en wijs de gewenste rechten toe. Vink je een onderdeel niet aan? Dan heeft de gebruiker geen toegang tot (een deel van) die pagina." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "role-name", children: "Naam rol" }), _jsx(Input, { id: "role-name", value: editor?.name ?? "", placeholder: "Vul een naam voor de rol in.", onChange: (e) => setEditor((state) => (state ? { ...state, name: e.target.value } : state)), disabled: editorSaving })] }), _jsxs("div", { className: "max-h-[45vh] overflow-y-auto rounded-md border", children: [_jsxs("div", { className: "flex items-center justify-end gap-8 border-b px-4 py-2 text-sm font-medium", children: [_jsx("span", { className: "w-16 text-center", children: "Lezen" }), _jsx("span", { className: "w-16 text-center", children: "Bewerken" })] }), roleModules.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (roleModules.map((mod) => {
453
+ }) })] }))] }), _jsxs(TabsContent, { value: "modules", className: "mt-4 space-y-6", children: [_jsx("p", { className: "text-sm text-muted-foreground", children: "Schakel \"Toegang afdwingen\" in voor een module om de rollen en gebruikersrechten actief te laten controleren. Modules zonder deze schakelaar blijven voor iedereen toegankelijk (default-allow)." }), roleModules.length === 0 ? (_jsx("p", { className: "text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd." })) : (_jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: "Module" }), _jsx(TableHead, { className: "w-48", children: "Toegang afdwingen" })] }) }), _jsx(TableBody, { children: roleModules.map((mod) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "font-medium", children: mod.name }), _jsx(TableCell, { children: _jsxs("div", { className: "flex items-center gap-3", children: [_jsx(Switch, { id: `enforce-${mod.id}`, checked: mod.enforce ?? false, onCheckedChange: (checked) => handleEnforceChange(mod.id, checked), disabled: submitting || !isAdmin }), _jsx(Label, { htmlFor: `enforce-${mod.id}`, className: "text-sm font-normal", children: (mod.enforce ?? false) ? "Ingeschakeld" : "Uitgeschakeld" })] }) })] }, mod.id))) })] }))] })] }) })] }), _jsx(Dialog, { open: editor !== null, onOpenChange: (open) => !open && !editorSaving && setEditor(null), children: _jsxs(DialogContent, { className: "max-w-3xl", children: [_jsxs(DialogHeader, { children: [_jsx(DialogTitle, { children: editor?.roleId ? "Rol bewerken" : "Rol toevoegen" }), _jsx(DialogDescription, { children: "Maak hier een rol aan en wijs de gewenste rechten toe. Vink je een onderdeel niet aan? Dan heeft de gebruiker geen toegang tot (een deel van) die pagina." })] }), _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "role-name", children: "Naam rol" }), _jsx(Input, { id: "role-name", value: editor?.name ?? "", placeholder: "Vul een naam voor de rol in.", onChange: (e) => setEditor((state) => (state ? { ...state, name: e.target.value } : state)), disabled: editorSaving })] }), _jsxs("div", { className: "max-h-[45vh] overflow-y-auto rounded-md border", children: [_jsxs("div", { className: "flex items-center justify-end gap-8 border-b px-4 py-2 text-sm font-medium", children: [_jsx("span", { className: "w-16 text-center", children: "Lezen" }), _jsx("span", { className: "w-16 text-center", children: "Bewerken" })] }), roleModules.length === 0 ? (_jsx("p", { className: "p-4 text-sm text-muted-foreground", children: "Er zijn nog geen modules geregistreerd voor deze shell." })) : (roleModules.map((mod) => {
443
454
  const cell = editor?.cells[permCellKey(mod.id, "")] ?? {
444
455
  canView: false,
445
456
  canEdit: false,
@@ -0,0 +1,15 @@
1
+ interface ModuleAccessGuardProps {
2
+ children: React.ReactNode;
3
+ featureId?: string;
4
+ fallback?: React.ReactNode;
5
+ }
6
+ /**
7
+ * Rendert alleen kinderen als de ingelogde gebruiker toegang heeft tot de
8
+ * huidige module. Tijdens laden een skeleton; bij onvoldoende recht een nette
9
+ * "geen toegang"-kaart. Modules die niet geregistreerd zijn worden altijd
10
+ * toegestaan (default-allow), zodat bestaande shells zonder aanpassingen
11
+ * blijven werken.
12
+ */
13
+ export declare function ModuleAccessGuard({ children, featureId, fallback }: ModuleAccessGuardProps): import("react").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=ModuleAccessGuard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModuleAccessGuard.d.ts","sourceRoot":"","sources":["../../src/components/ModuleAccessGuard.tsx"],"names":[],"mappings":"AAKA,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;CAC5B;AAYD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,QAAQ,EAAE,SAAc,EAAE,QAAQ,EAAE,EAAE,sBAAsB,+BAwC/F"}
@@ -0,0 +1,40 @@
1
+ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useRouterState } from "@tanstack/react-router";
3
+ import { Card, CardContent, CardHeader, CardTitle } from "@flowselections/core";
4
+ import { ShieldAlert } from "lucide-react";
5
+ import { useModulePermission } from "../hooks/useModulePermission";
6
+ const EXCLUDED_SEGMENTS = new Set(["admin-installatietechniek", "instellingen"]);
7
+ function deriveModuleId(pathname) {
8
+ const segments = pathname.split("/").filter(Boolean);
9
+ const first = segments[0];
10
+ if (!first || first.startsWith("$") || first.startsWith("_"))
11
+ return null;
12
+ if (EXCLUDED_SEGMENTS.has(first))
13
+ return null;
14
+ return first;
15
+ }
16
+ /**
17
+ * Rendert alleen kinderen als de ingelogde gebruiker toegang heeft tot de
18
+ * huidige module. Tijdens laden een skeleton; bij onvoldoende recht een nette
19
+ * "geen toegang"-kaart. Modules die niet geregistreerd zijn worden altijd
20
+ * toegestaan (default-allow), zodat bestaande shells zonder aanpassingen
21
+ * blijven werken.
22
+ */
23
+ export function ModuleAccessGuard({ children, featureId = "", fallback }) {
24
+ const pathname = useRouterState({ select: (s) => s.location.pathname });
25
+ const moduleId = deriveModuleId(pathname);
26
+ // Als we geen module kunnen afleiden (bijv. admin of instellingen), toon
27
+ // altijd de inhoud.
28
+ if (!moduleId)
29
+ return _jsx(_Fragment, { children: children });
30
+ const { permission, loading, canView } = useModulePermission(moduleId, featureId);
31
+ if (loading) {
32
+ return (_jsxs("div", { className: "flex-1 p-6", children: [_jsx("div", { className: "h-6 w-48 animate-pulse rounded bg-muted mb-4" }), _jsx("div", { className: "h-32 animate-pulse rounded bg-muted" })] }));
33
+ }
34
+ if (!canView) {
35
+ if (fallback)
36
+ return _jsx(_Fragment, { children: fallback });
37
+ return (_jsx("div", { className: "flex-1 flex items-center justify-center p-6", children: _jsxs(Card, { className: "max-w-md w-full", children: [_jsxs(CardHeader, { className: "flex flex-row items-center gap-3", children: [_jsx(ShieldAlert, { className: "h-6 w-6 text-destructive" }), _jsx(CardTitle, { children: "Geen toegang" })] }), _jsx(CardContent, { children: _jsx("p", { className: "text-sm text-muted-foreground", children: "Je hebt geen toegang tot deze module. Neem contact op met een beheerder als je denkt dat dit niet klopt." }) })] }) }));
38
+ }
39
+ return _jsx(_Fragment, { children: children });
40
+ }
@@ -2,10 +2,10 @@ export type ModulePermission = "none" | "view" | "edit";
2
2
  /**
3
3
  * Haalt de effectieve rechten van de ingelogde gebruiker op voor alle
4
4
  * geregistreerde modules en onderdelen. Persoonlijke uitzonderingen winnen
5
- * van rolrechten.
5
+ * van rolrechten. Gedeelde cache via React Query; één keer per sessie.
6
6
  */
7
7
  export declare function useMyModulePermissions(): {
8
- permissions: Record<string, ModulePermission>;
8
+ permissions: NoInfer<Record<string, ModulePermission>>;
9
9
  loading: boolean;
10
10
  };
11
11
  /** Effectief recht voor één module of onderdeel: "none" | "view" | "edit". */
@@ -15,4 +15,10 @@ export declare function useModulePermission(moduleId: string, featureId?: string
15
15
  canEdit: boolean;
16
16
  loading: boolean;
17
17
  };
18
+ /** Invalidatie-aanhaker voor auth-wijzigingen. */
19
+ export declare function invalidateModulePermissions(queryClient: {
20
+ invalidateQueries: (options: {
21
+ queryKey: string[];
22
+ }) => Promise<void>;
23
+ }): Promise<void>;
18
24
  //# sourceMappingURL=useModulePermission.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAIxD;;;;GAIG;AACH,wBAAgB,sBAAsB;;;EA2BrC;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAK;;;;;EAUnE"}
1
+ {"version":3,"file":"useModulePermission.d.ts","sourceRoot":"","sources":["../../src/hooks/useModulePermission.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAMxD;;;;GAIG;AACH,wBAAgB,sBAAsB;;;EAmBrC;AAED,8EAA8E;AAC9E,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,SAAK;;;;;EAgBnE;AAED,kDAAkD;AAClD,wBAAgB,2BAA2B,CAAC,WAAW,EAAE;IACvD,iBAAiB,EAAE,CAAC,OAAO,EAAE;QAAE,QAAQ,EAAE,MAAM,EAAE,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvE,iBAEA"}
@@ -1,42 +1,41 @@
1
- import { useEffect, useState } from "react";
1
+ import { useQuery } from "@tanstack/react-query";
2
2
  import { supabase } from "../integrations/supabase/client";
3
3
  const cellKey = (moduleId, featureId) => `${moduleId}::${featureId}`;
4
+ const permissionQueryKey = ["my-module-permissions"];
4
5
  /**
5
6
  * Haalt de effectieve rechten van de ingelogde gebruiker op voor alle
6
7
  * geregistreerde modules en onderdelen. Persoonlijke uitzonderingen winnen
7
- * van rolrechten.
8
+ * van rolrechten. Gedeelde cache via React Query; één keer per sessie.
8
9
  */
9
10
  export function useMyModulePermissions() {
10
- const [permissions, setPermissions] = useState({});
11
- const [loading, setLoading] = useState(true);
12
- useEffect(() => {
13
- let cancelled = false;
14
- (async () => {
11
+ const { data, isLoading } = useQuery({
12
+ queryKey: permissionQueryKey,
13
+ queryFn: async () => {
15
14
  const { data, error } = await supabase.rpc("my_module_permissions");
16
- if (cancelled)
17
- return;
15
+ if (error)
16
+ throw error;
18
17
  const rows = Array.isArray(data)
19
18
  ? data
20
19
  : [];
21
- if (!error) {
22
- const map = {};
23
- for (const row of rows) {
24
- map[cellKey(row.module_id, row.feature_id ?? "")] = row.permission;
25
- }
26
- setPermissions(map);
20
+ const map = {};
21
+ for (const row of rows) {
22
+ map[cellKey(row.module_id, row.feature_id ?? "")] = row.permission;
27
23
  }
28
- setLoading(false);
29
- })();
30
- return () => {
31
- cancelled = true;
32
- };
33
- }, []);
34
- return { permissions, loading };
24
+ return map;
25
+ },
26
+ staleTime: 5 * 60 * 1000,
27
+ });
28
+ return { permissions: data ?? {}, loading: isLoading };
35
29
  }
36
30
  /** Effectief recht voor één module of onderdeel: "none" | "view" | "edit". */
37
31
  export function useModulePermission(moduleId, featureId = "") {
38
32
  const { permissions, loading } = useMyModulePermissions();
39
- const permission = permissions[cellKey(moduleId, featureId)] ?? permissions[cellKey(moduleId, "")] ?? "none";
33
+ const exact = permissions[cellKey(moduleId, featureId)];
34
+ const moduleFallback = permissions[cellKey(moduleId, "")];
35
+ // Niet geregistreerde modules zijn default toegestaan (edit) zodat
36
+ // bestaande shells blijven werken totdat een beheerder expliciet rechten
37
+ // instelt en enforce aanzet.
38
+ const permission = exact ?? moduleFallback ?? "edit";
40
39
  return {
41
40
  permission,
42
41
  canView: permission === "view" || permission === "edit",
@@ -44,3 +43,7 @@ export function useModulePermission(moduleId, featureId = "") {
44
43
  loading,
45
44
  };
46
45
  }
46
+ /** Invalidatie-aanhaker voor auth-wijzigingen. */
47
+ export function invalidateModulePermissions(queryClient) {
48
+ return queryClient.invalidateQueries({ queryKey: permissionQueryKey });
49
+ }
@@ -6,5 +6,7 @@ export { useModulePermission, useMyModulePermissions } from './hooks/useModulePe
6
6
  export type { ModulePermission } from './hooks/useModulePermission';
7
7
  export { useDiscoveredModules } from './lib/moduleDiscovery';
8
8
  export type { DiscoveredModule, DiscoveredFeature } from './lib/moduleDiscovery';
9
+ export { ModuleAccessGuard } from './components/ModuleAccessGuard';
10
+ export { requireModulePermission } from './lib/permissions.server';
9
11
  export declare const adminInstallatietechniekModule: FlowModule;
10
12
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACnH,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGjF,eAAO,MAAM,8BAA8B,EAAE,UAY5C,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAEvD,cAAc,kBAAkB,CAAC;AACjC,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,YAAY,EAAE,gCAAgC,EAAE,gBAAgB,EAAE,MAAM,0CAA0C,CAAC;AACnH,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAC1F,YAAY,EAAE,gBAAgB,EAAE,MAAM,6BAA6B,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAC7D,YAAY,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACjF,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AACnE,OAAO,EAAE,uBAAuB,EAAE,MAAM,0BAA0B,CAAC;AAInE,eAAO,MAAM,8BAA8B,EAAE,UAY5C,CAAC"}
package/dist-lib/index.js CHANGED
@@ -6,6 +6,8 @@ export * from './_core-safelist';
6
6
  export { AdminForInstallatieTechniek } from './components/AdminForInstallatieTechniek';
7
7
  export { useModulePermission, useMyModulePermissions } from './hooks/useModulePermission';
8
8
  export { useDiscoveredModules } from './lib/moduleDiscovery';
9
+ export { ModuleAccessGuard } from './components/ModuleAccessGuard';
10
+ export { requireModulePermission } from './lib/permissions.server';
9
11
  export const adminInstallatietechniekModule = {
10
12
  id: "admin-installatietechniek",
11
13
  name: "Admin Installatietechniek",
@@ -1 +1 @@
1
- {"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,mHAQ9B,CAAA"}
1
+ {"version":3,"file":"auth-attacher.d.ts","sourceRoot":"","sources":["../../../src/integrations/supabase/auth-attacher.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,kBAAkB,mHAY9B,CAAA"}
@@ -7,6 +7,10 @@ export const attachSupabaseAuth = createMiddleware({ type: 'function' }).client(
7
7
  const { data } = await supabase.auth.getSession();
8
8
  const token = data.session?.access_token;
9
9
  return next({
10
- headers: token ? { Authorization: `Bearer ${token}` } : {},
10
+ // `x-sb-auth` is een fallback: sommige preview-proxies overschrijven de
11
+ // Authorization-header, waardoor de server een ongeldig token zag.
12
+ headers: token
13
+ ? { Authorization: `Bearer ${token}`, 'x-sb-auth': token }
14
+ : {},
11
15
  });
12
16
  });