@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.
- package/README.md +61 -0
- package/dist/auth/redirect.d.ts +52 -0
- package/dist/auth/redirect.d.ts.map +1 -0
- package/dist/auth/redirect.js +98 -0
- package/dist/auth/useAuth.d.ts +1 -1
- package/dist/auth/useAuth.d.ts.map +1 -1
- package/dist/auth/useIsSuperadmin.d.ts +19 -0
- package/dist/auth/useIsSuperadmin.d.ts.map +1 -0
- package/dist/auth/useIsSuperadmin.js +16 -0
- package/dist/components/layout/AuthenticatedLayout.d.ts +29 -0
- package/dist/components/layout/AuthenticatedLayout.d.ts.map +1 -0
- package/dist/components/layout/AuthenticatedLayout.js +49 -0
- package/dist/components/layout/HandoverPage.d.ts.map +1 -1
- package/dist/components/layout/HandoverPage.js +4 -9
- package/dist/components/layout/Header.js +1 -1
- package/dist/components/layout/InstellingenPage.d.ts.map +1 -1
- package/dist/components/layout/InstellingenPage.js +10 -2
- package/dist/components/layout/LoginPage.d.ts.map +1 -1
- package/dist/components/layout/LoginPage.js +6 -6
- package/dist/components/layout/ResetWachtwoordPage.d.ts +3 -1
- package/dist/components/layout/ResetWachtwoordPage.d.ts.map +1 -1
- package/dist/components/layout/ResetWachtwoordPage.js +5 -3
- package/dist/components/layout/Sidebar.d.ts +37 -3
- package/dist/components/layout/Sidebar.d.ts.map +1 -1
- package/dist/components/layout/Sidebar.js +52 -13
- package/dist/extensions/ShellExtensions.d.ts +14 -1
- package/dist/extensions/ShellExtensions.d.ts.map +1 -1
- package/dist/extensions/ShellExtensions.js +21 -2
- package/dist/hooks/useLogoSrc.d.ts +7 -0
- package/dist/hooks/useLogoSrc.d.ts.map +1 -0
- package/dist/hooks/useLogoSrc.js +29 -0
- package/dist/index.d.ts +20 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/permissions/resolve.d.ts +22 -0
- package/dist/permissions/resolve.d.ts.map +1 -0
- package/dist/permissions/resolve.js +76 -0
- package/dist/permissions/store.d.ts +38 -0
- package/dist/permissions/store.d.ts.map +1 -0
- package/dist/permissions/store.js +180 -0
- package/dist/permissions/useModulePermission.d.ts +52 -32
- package/dist/permissions/useModulePermission.d.ts.map +1 -1
- package/dist/permissions/useModulePermission.js +72 -104
- package/dist/routes/index.d.ts +48 -0
- package/dist/routes/index.d.ts.map +1 -0
- package/dist/routes/index.js +63 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/permissions.server.d.ts +21 -8
- package/dist/server/permissions.server.d.ts.map +1 -1
- package/dist/server/permissions.server.js +54 -9
- package/dist/shell/ShellContext.d.ts +26 -0
- package/dist/shell/ShellContext.d.ts.map +1 -0
- package/dist/shell/ShellContext.js +14 -0
- package/dist/styles.css +127 -0
- package/dist/types/module.d.ts +39 -4
- package/dist/types/module.d.ts.map +1 -1
- package/dist/types/module.js +33 -5
- package/package.json +115 -107
package/README.md
CHANGED
|
@@ -111,3 +111,64 @@ retry-storm veroorzaken.
|
|
|
111
111
|
De browser-client is env-gedreven en lazy (`VITE_SUPABASE_URL` /
|
|
112
112
|
`VITE_SUPABASE_PUBLISHABLE_KEY`, met `SUPABASE_*` fallback voor SSR). Er staat
|
|
113
113
|
geen projectref of key in de code, ook niet als fallback.
|
|
114
|
+
|
|
115
|
+
## Rechten in 1.0.23
|
|
116
|
+
|
|
117
|
+
> **Zichtbaarheid, geen beveiliging.** Alles hieronder verbergt of toont UI. De
|
|
118
|
+
> data wordt afgeschermd door RLS en serverfuncties (`requireModulePermission`).
|
|
119
|
+
|
|
120
|
+
- **Automatisch via `AuthenticatedLayout` + `requireAuth`** — het gegenereerde
|
|
121
|
+
`_authenticated.tsx` hoeft niet te veranderen.
|
|
122
|
+
- Zijbalk: eerst het rechtenfilter, dan extensie-transforms (ordening). Uit met
|
|
123
|
+
`permissions={false}`. Tijdens het eerste laden een skeleton; na een fout of
|
|
124
|
+
3 s time-out de volledige lijst (fail-open).
|
|
125
|
+
- `requireAuth` geeft `{ moduleAccess }` terug uit `beforeLoad` (context wordt
|
|
126
|
+
niet gemuteerd). Bij `none` toont de layout "Geen toegang". Wacht max. 3 s.
|
|
127
|
+
- Werkt zonder `QueryClientProvider` (gedeelde store, `useSyncExternalStore`).
|
|
128
|
+
- `useCan(module, feature?, level?)` / `<Can>` — niveaus `none < view < edit`.
|
|
129
|
+
- `NavItem.featureId` (anders `featureKey(href)`); groepen zonder zichtbare
|
|
130
|
+
kinderen verdwijnen, tenzij de module een eigen link heeft.
|
|
131
|
+
- `SettingsCard.moduleId/featureId`: kaarten erven het recht van hun module.
|
|
132
|
+
- Footer: `ShellExtensionsProvider useFooter` kan Instellingen verbergen;
|
|
133
|
+
Uitloggen is nooit te verbergen.
|
|
134
|
+
- `useIsSuperadmin()` (claim `user_roles`, terugval tabel); server:
|
|
135
|
+
`getRoleFlags(claims)`, `isSuperadminServer()`. Nooit voor moduletoegang.
|
|
136
|
+
- `useShell()` / `useShellModules()` — ruwe modulelijst + `shellId`.
|
|
137
|
+
- Admin en superadmin zien altijd alles.
|
|
138
|
+
|
|
139
|
+
### Gedragswijziging `requireModulePermission`
|
|
140
|
+
|
|
141
|
+
**Vóór 1.0.23 liet deze functie alles door**: de RPC werd zonder `_user_id`
|
|
142
|
+
aangeroepen, werd niet gevonden, en dat gold als "add-on ontbreekt" →
|
|
143
|
+
toegestaan. Sinds 1.0.23 roept hij de 3-argumentversie aan; `_user_id` komt
|
|
144
|
+
uitsluitend uit het geverifieerde token van het verzoek, moduleniveau is
|
|
145
|
+
`_feature_id ''`. Zonder geldige sessie: 401. Controleer serverfuncties die
|
|
146
|
+
erop leunen: ze kunnen nu echt 403 geven.
|
|
147
|
+
|
|
148
|
+
### Huisstijl
|
|
149
|
+
|
|
150
|
+
Tokens staan op één plek: `src/core/styles.css`, ook als
|
|
151
|
+
`@flowselections/core/styles.css`. Logo: `logoSrc`-prop, anders `logo` uit
|
|
152
|
+
`/huisstijl.json`, anders `/logo.png`.
|
|
153
|
+
|
|
154
|
+
## Systeemroutes (`@flowselections/core/routes`, vanaf 1.0.23)
|
|
155
|
+
|
|
156
|
+
Shells houden per systeemroute een dun omhulsel; core levert de opties:
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// src/routes/login.tsx
|
|
160
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
161
|
+
import { loginRoute } from "@flowselections/core/routes";
|
|
162
|
+
export const Route = createFileRoute("/login")(loginRoute({ landing: "/voorraad" }));
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Ook: `resetPasswordRoute({ landing })` (`/reset-wachtwoord`), `landingRoute({ landing })` (`/`),
|
|
166
|
+
`handoverRoute({ landing })` (`/auth/handover`, `ssr: false`). Bij `loginRoute` wint een veilig `?redirect`
|
|
167
|
+
(same-site pad) van `landing`, ook als je al ingelogd bent. Shells met eigen routebestanden blijven werken.
|
|
168
|
+
|
|
169
|
+
## Publiceren
|
|
170
|
+
|
|
171
|
+
`vite build` (preview/hosting) schrijft verplicht naar `dist/` en overschrijft dan het pakket. Daarom:
|
|
172
|
+
`prepublishOnly` draait `build:pkg`, dat `dist/` eerst leegmaakt, opnieuw bouwt en met
|
|
173
|
+
`scripts/check-pkg.mjs` controleert dat `dist/` precies het pakket bevat. `npm publish` publiceert dus nooit
|
|
174
|
+
een preview-build.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { type PermissionLevel } from "../permissions/resolve.js";
|
|
2
|
+
/**
|
|
3
|
+
* Accepteert alleen een same-site pad: begint met "/", niet met "//",
|
|
4
|
+
* geen backslash, geen schema en geen stuurtekens. Anders `fallback`.
|
|
5
|
+
*/
|
|
6
|
+
export declare function safeRedirectPath(value: unknown, fallback: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* Leest `?redirect=` direct uit de adresbalk, zodat het ook werkt als de
|
|
9
|
+
* login-route van de shell geen validateSearch heeft.
|
|
10
|
+
*/
|
|
11
|
+
export declare function readRedirectParam(fallback: string): string;
|
|
12
|
+
export interface RequireAuthArgs {
|
|
13
|
+
context: unknown;
|
|
14
|
+
location: {
|
|
15
|
+
href: string;
|
|
16
|
+
pathname?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/** Sinds 1.0.23: wat `requireAuth` aan de routecontext toevoegt. */
|
|
20
|
+
export interface ModuleAccessContext {
|
|
21
|
+
moduleAccess: PermissionLevel;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Sinds 1.0.23. Moduletoegang voor een pad. Wacht max. 3 s; fout, time-out,
|
|
25
|
+
* vrijgesteld pad of geen modulerij → `edit` (fail-open).
|
|
26
|
+
* Zichtbaarheid, geen beveiliging.
|
|
27
|
+
*/
|
|
28
|
+
export declare function resolveModuleAccess(path: string, opts?: {
|
|
29
|
+
userId?: string | null;
|
|
30
|
+
accessToken?: string | null;
|
|
31
|
+
roles?: string[];
|
|
32
|
+
}): Promise<PermissionLevel>;
|
|
33
|
+
/** Sinds 1.0.23. `true` tenzij het niveau `none` is. Zichtbaarheid, geen beveiliging. */
|
|
34
|
+
export declare function canAccessModule(path: string, opts?: {
|
|
35
|
+
userId?: string | null;
|
|
36
|
+
accessToken?: string | null;
|
|
37
|
+
roles?: string[];
|
|
38
|
+
}): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Guard voor de `_authenticated`-route (beforeLoad, met ssr: false).
|
|
41
|
+
*
|
|
42
|
+
* - Zolang auth laadt: niets (de root invalideert de router daarna).
|
|
43
|
+
* - Geen sessie: synchrone redirect naar `loginPath` met `?redirect=`.
|
|
44
|
+
* - Sinds 1.0.23: geeft `{ moduleAccess }` terug (TanStack voegt dat samen in
|
|
45
|
+
* de routecontext; de context wordt niet gemuteerd). Bij `none` rendert
|
|
46
|
+
* AuthenticatedLayout "Geen toegang". Fail-open na 3 s of bij een fout.
|
|
47
|
+
*
|
|
48
|
+
* Zichtbaarheid, geen beveiliging: data wordt afgeschermd door RLS en
|
|
49
|
+
* serverfuncties.
|
|
50
|
+
*/
|
|
51
|
+
export declare function requireAuth({ context, location }: RequireAuthArgs, loginPath?: string): void | ModuleAccessContext | Promise<ModuleAccessContext>;
|
|
52
|
+
//# sourceMappingURL=redirect.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redirect.d.ts","sourceRoot":"","sources":["../../src/core/auth/redirect.ts"],"names":[],"mappings":"AAEA,OAAO,EAA6B,KAAK,eAAe,EAAE,MAAM,2BAA2B,CAAC;AAG5F;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAazE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAG1D;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAC/C;AAED,oEAAoE;AACpE,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,eAAe,CAAC;CAC/B;AAUD;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,MAAM,EACZ,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAO,GACnF,OAAO,CAAC,eAAe,CAAC,CAM1B;AAED,yFAAyF;AACzF,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC/E,OAAO,CAAC,OAAO,CAAC,CAElB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CACzB,EAAE,OAAO,EAAE,QAAQ,EAAE,EAAE,eAAe,EACtC,SAAS,SAAW,GACnB,IAAI,GAAG,mBAAmB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAuB3D"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { redirect } from "@tanstack/react-router";
|
|
2
|
+
import { navKey } from "../types/module.js";
|
|
3
|
+
import { EXEMPT_KEYS, resolveLevel } from "../permissions/resolve.js";
|
|
4
|
+
import { getPermissionSnapshot, waitForPermissions } from "../permissions/store.js";
|
|
5
|
+
/**
|
|
6
|
+
* Accepteert alleen een same-site pad: begint met "/", niet met "//",
|
|
7
|
+
* geen backslash, geen schema en geen stuurtekens. Anders `fallback`.
|
|
8
|
+
*/
|
|
9
|
+
export function safeRedirectPath(value, fallback) {
|
|
10
|
+
if (typeof value !== "string" || value.length === 0)
|
|
11
|
+
return fallback;
|
|
12
|
+
if (!value.startsWith("/") || value.startsWith("//"))
|
|
13
|
+
return fallback;
|
|
14
|
+
if (value.includes("\\"))
|
|
15
|
+
return fallback;
|
|
16
|
+
// eslint-disable-next-line no-control-regex
|
|
17
|
+
if (/[\u0000-\u001f\u007f]/.test(value))
|
|
18
|
+
return fallback;
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(value, "http://same.invalid");
|
|
21
|
+
if (url.origin !== "http://same.invalid")
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return fallback;
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Leest `?redirect=` direct uit de adresbalk, zodat het ook werkt als de
|
|
31
|
+
* login-route van de shell geen validateSearch heeft.
|
|
32
|
+
*/
|
|
33
|
+
export function readRedirectParam(fallback) {
|
|
34
|
+
if (typeof window === "undefined")
|
|
35
|
+
return fallback;
|
|
36
|
+
return safeRedirectPath(new URLSearchParams(window.location.search).get("redirect"), fallback);
|
|
37
|
+
}
|
|
38
|
+
function pathOf(location) {
|
|
39
|
+
return location.pathname ?? location.href.split("?")[0].split("#")[0];
|
|
40
|
+
}
|
|
41
|
+
function isAdminRoles(roles) {
|
|
42
|
+
return Array.isArray(roles) && (roles.includes("admin") || roles.includes("superadmin"));
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Sinds 1.0.23. Moduletoegang voor een pad. Wacht max. 3 s; fout, time-out,
|
|
46
|
+
* vrijgesteld pad of geen modulerij → `edit` (fail-open).
|
|
47
|
+
* Zichtbaarheid, geen beveiliging.
|
|
48
|
+
*/
|
|
49
|
+
export async function resolveModuleAccess(path, opts = {}) {
|
|
50
|
+
const key = navKey(path);
|
|
51
|
+
if (EXEMPT_KEYS.has(key) || isAdminRoles(opts.roles))
|
|
52
|
+
return "edit";
|
|
53
|
+
const snap = await waitForPermissions(opts.userId ?? null, opts.accessToken);
|
|
54
|
+
if (snap.status !== "ready" || isAdminRoles(snap.roles))
|
|
55
|
+
return "edit";
|
|
56
|
+
return resolveLevel(snap.rows, key);
|
|
57
|
+
}
|
|
58
|
+
/** Sinds 1.0.23. `true` tenzij het niveau `none` is. Zichtbaarheid, geen beveiliging. */
|
|
59
|
+
export async function canAccessModule(path, opts) {
|
|
60
|
+
return (await resolveModuleAccess(path, opts)) !== "none";
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Guard voor de `_authenticated`-route (beforeLoad, met ssr: false).
|
|
64
|
+
*
|
|
65
|
+
* - Zolang auth laadt: niets (de root invalideert de router daarna).
|
|
66
|
+
* - Geen sessie: synchrone redirect naar `loginPath` met `?redirect=`.
|
|
67
|
+
* - Sinds 1.0.23: geeft `{ moduleAccess }` terug (TanStack voegt dat samen in
|
|
68
|
+
* de routecontext; de context wordt niet gemuteerd). Bij `none` rendert
|
|
69
|
+
* AuthenticatedLayout "Geen toegang". Fail-open na 3 s of bij een fout.
|
|
70
|
+
*
|
|
71
|
+
* Zichtbaarheid, geen beveiliging: data wordt afgeschermd door RLS en
|
|
72
|
+
* serverfuncties.
|
|
73
|
+
*/
|
|
74
|
+
export function requireAuth({ context, location }, loginPath = "/login") {
|
|
75
|
+
const auth = context?.auth;
|
|
76
|
+
if (!auth || auth.loading)
|
|
77
|
+
return;
|
|
78
|
+
if (!auth.isAuthenticated) {
|
|
79
|
+
throw redirect({ to: loginPath, search: { redirect: location.href } });
|
|
80
|
+
}
|
|
81
|
+
const path = pathOf(location);
|
|
82
|
+
const key = navKey(path);
|
|
83
|
+
const roles = Array.isArray(auth.roles) ? auth.roles : [];
|
|
84
|
+
if (EXEMPT_KEYS.has(key) || isAdminRoles(roles))
|
|
85
|
+
return { moduleAccess: "edit" };
|
|
86
|
+
const userId = auth.user?.id ?? null;
|
|
87
|
+
const snap = getPermissionSnapshot();
|
|
88
|
+
if (snap.userId === userId && snap.status !== "loading") {
|
|
89
|
+
// Synchroon antwoord als de cache al bekend is (geen wachttijd bij navigatie).
|
|
90
|
+
const level = snap.status === "ready" && !isAdminRoles(snap.roles) ? resolveLevel(snap.rows, key) : "edit";
|
|
91
|
+
return { moduleAccess: level };
|
|
92
|
+
}
|
|
93
|
+
return resolveModuleAccess(path, {
|
|
94
|
+
userId,
|
|
95
|
+
accessToken: auth.session?.access_token ?? null,
|
|
96
|
+
roles,
|
|
97
|
+
}).then((moduleAccess) => ({ moduleAccess }));
|
|
98
|
+
}
|
package/dist/auth/useAuth.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { User, Session } from "@supabase/supabase-js";
|
|
2
|
-
export type AppRole = "admin" | "manager" | "warehouse" | "sales" | "developer";
|
|
2
|
+
export type AppRole = "admin" | "manager" | "warehouse" | "sales" | "developer" | "superadmin" | "administratie" | "ai_admin" | "monteur" | "planner" | "receptie";
|
|
3
3
|
export interface AuthState {
|
|
4
4
|
user: User | null;
|
|
5
5
|
session: Session | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/core/auth/useAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGtD,MAAM,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../src/core/auth/useAuth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAGtD,MAAM,MAAM,OAAO,GACf,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,GACzD,YAAY,GAAG,eAAe,GAAG,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;AAErF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,OAAO,EAAE,OAAO,GAAG,IAAI,CAAC;IACxB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,EAAE,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;CACvE;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAC9E,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7B,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;QAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IACnE,YAAY,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CACnC;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,WAAW,CAAC;AAElD,wBAAgB,YAAY,IAAI,WAAW,CAsE1C;AACD,wBAAgB,OAAO,IAAI,WAAW,CAErC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Pure: superadmin ⇒ admin (contract §4). */
|
|
2
|
+
export declare function roleFlags(roles: string[]): {
|
|
3
|
+
isSuperadmin: boolean;
|
|
4
|
+
isAdmin: boolean;
|
|
5
|
+
roles: string[];
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Sinds 1.0.23. Rollen uit de JWT-claim `user_roles`, terugval op tabel
|
|
9
|
+
* `user_roles`. Nooit gebruiken voor moduletoegang (daarvoor
|
|
10
|
+
* `my_module_permissions`). Werkt zonder QueryClientProvider.
|
|
11
|
+
* Zichtbaarheid, geen beveiliging.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useIsSuperadmin(): {
|
|
14
|
+
isLoading: boolean;
|
|
15
|
+
isSuperadmin: boolean;
|
|
16
|
+
isAdmin: boolean;
|
|
17
|
+
roles: string[];
|
|
18
|
+
};
|
|
19
|
+
//# sourceMappingURL=useIsSuperadmin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useIsSuperadmin.d.ts","sourceRoot":"","sources":["../../src/core/auth/useIsSuperadmin.ts"],"names":[],"mappings":"AAEA,8CAA8C;AAC9C,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE;;;;EAGxC;AAED;;;;;GAKG;AACH,wBAAgB,eAAe;;;;;EAG9B"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { usePermissionSnapshot } from "../permissions/store.js";
|
|
2
|
+
/** Pure: superadmin ⇒ admin (contract §4). */
|
|
3
|
+
export function roleFlags(roles) {
|
|
4
|
+
const isSuperadmin = roles.includes("superadmin");
|
|
5
|
+
return { isSuperadmin, isAdmin: isSuperadmin || roles.includes("admin"), roles };
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Sinds 1.0.23. Rollen uit de JWT-claim `user_roles`, terugval op tabel
|
|
9
|
+
* `user_roles`. Nooit gebruiken voor moduletoegang (daarvoor
|
|
10
|
+
* `my_module_permissions`). Werkt zonder QueryClientProvider.
|
|
11
|
+
* Zichtbaarheid, geen beveiliging.
|
|
12
|
+
*/
|
|
13
|
+
export function useIsSuperadmin() {
|
|
14
|
+
const snap = usePermissionSnapshot();
|
|
15
|
+
return { ...roleFlags(snap.roles), isLoading: !snap.rolesLoaded };
|
|
16
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { SidebarProps } from "./Sidebar";
|
|
3
|
+
export type AuthenticatedLayoutProps = SidebarProps & {
|
|
4
|
+
/** Optioneel; standaard rendert de layout <Outlet />. */
|
|
5
|
+
children?: ReactNode;
|
|
6
|
+
/** Toon de gedeelde paginakop. Standaard ingeschakeld. */
|
|
7
|
+
header?: boolean;
|
|
8
|
+
};
|
|
9
|
+
type MatchLike = {
|
|
10
|
+
staticData?: unknown;
|
|
11
|
+
context?: unknown;
|
|
12
|
+
};
|
|
13
|
+
/** Sinds 1.0.23: diepste match met een niet-lege titel. */
|
|
14
|
+
export declare function pickTitle(matches: MatchLike[]): string;
|
|
15
|
+
/** Sinds 1.0.23: `moduleAccess` uit de diepste routecontext die hem heeft. */
|
|
16
|
+
export declare function pickModuleAccess(matches: MatchLike[]): string | undefined;
|
|
17
|
+
export declare function NoAccess(): import("react/jsx-runtime").JSX.Element;
|
|
18
|
+
/**
|
|
19
|
+
* Zijbalk + content-offset + mobiele topbalk + paginakop.
|
|
20
|
+
*
|
|
21
|
+
* Sinds 1.0.23: filtert de zijbalk standaard op rechten (eerst rechten, dan
|
|
22
|
+
* extensie-transforms; uit met `permissions={false}`), toont "Geen toegang"
|
|
23
|
+
* als `requireAuth` `moduleAccess: "none"` teruggaf, en levert `useShell()`.
|
|
24
|
+
* Zichtbaarheid, geen beveiliging: data wordt afgeschermd door RLS en
|
|
25
|
+
* serverfuncties.
|
|
26
|
+
*/
|
|
27
|
+
export declare function AuthenticatedLayout({ children, header, permissions, ...sidebarProps }: AuthenticatedLayoutProps): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
export {};
|
|
29
|
+
//# sourceMappingURL=AuthenticatedLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthenticatedLayout.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/AuthenticatedLayout.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGvC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAI9C,MAAM,MAAM,wBAAwB,GAAG,YAAY,GAAG;IACpD,yDAAyD;IACzD,QAAQ,CAAC,EAAE,SAAS,CAAC;IACrB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF,KAAK,SAAS,GAAG;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC;AAE7D,2DAA2D;AAC3D,wBAAgB,SAAS,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,CAMtD;AAED,8EAA8E;AAC9E,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,MAAM,GAAG,SAAS,CAMzE;AAED,wBAAgB,QAAQ,4CAWvB;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,MAAa,EACb,WAAkB,EAClB,GAAG,YAAY,EAChB,EAAE,wBAAwB,2CA6B1B"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo } from "react";
|
|
3
|
+
import { Outlet, useMatches } from "@tanstack/react-router";
|
|
4
|
+
import { Sidebar, useSidebarModules } from "./Sidebar";
|
|
5
|
+
import { Header } from "./Header";
|
|
6
|
+
import { ShellContext } from "../../shell/ShellContext.js";
|
|
7
|
+
/** Sinds 1.0.23: diepste match met een niet-lege titel. */
|
|
8
|
+
export function pickTitle(matches) {
|
|
9
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
10
|
+
const t = matches[i].staticData?.title;
|
|
11
|
+
if (typeof t === "string" && t.trim() !== "")
|
|
12
|
+
return t;
|
|
13
|
+
}
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
/** Sinds 1.0.23: `moduleAccess` uit de diepste routecontext die hem heeft. */
|
|
17
|
+
export function pickModuleAccess(matches) {
|
|
18
|
+
for (let i = matches.length - 1; i >= 0; i--) {
|
|
19
|
+
const v = matches[i].context?.moduleAccess;
|
|
20
|
+
if (typeof v === "string")
|
|
21
|
+
return v;
|
|
22
|
+
}
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
export function NoAccess() {
|
|
26
|
+
return (_jsx("div", { className: "flex min-h-[60vh] items-center justify-center p-8", children: _jsxs("div", { className: "max-w-md text-center", children: [_jsx("h1", { className: "text-lg font-semibold text-foreground", children: "Geen toegang" }), _jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: "Je hebt geen rechten voor deze module. Vraag een beheerder om toegang." })] }) }));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Zijbalk + content-offset + mobiele topbalk + paginakop.
|
|
30
|
+
*
|
|
31
|
+
* Sinds 1.0.23: filtert de zijbalk standaard op rechten (eerst rechten, dan
|
|
32
|
+
* extensie-transforms; uit met `permissions={false}`), toont "Geen toegang"
|
|
33
|
+
* als `requireAuth` `moduleAccess: "none"` teruggaf, en levert `useShell()`.
|
|
34
|
+
* Zichtbaarheid, geen beveiliging: data wordt afgeschermd door RLS en
|
|
35
|
+
* serverfuncties.
|
|
36
|
+
*/
|
|
37
|
+
export function AuthenticatedLayout({ children, header = true, permissions = true, ...sidebarProps }) {
|
|
38
|
+
const matches = useMatches();
|
|
39
|
+
const title = pickTitle(matches);
|
|
40
|
+
const access = pickModuleAccess(matches);
|
|
41
|
+
const { visibleModules, loading } = useSidebarModules(sidebarProps.modules, permissions);
|
|
42
|
+
const shell = useMemo(() => ({
|
|
43
|
+
shellId: sidebarProps.shellId,
|
|
44
|
+
modules: sidebarProps.modules,
|
|
45
|
+
visibleModules,
|
|
46
|
+
permissionsLoading: loading,
|
|
47
|
+
}), [sidebarProps.shellId, sidebarProps.modules, visibleModules, loading]);
|
|
48
|
+
return (_jsx(ShellContext.Provider, { value: shell, children: _jsxs("div", { className: "min-h-screen bg-background [--core-topbar-h:3.5rem] md:[--core-topbar-h:0px]", children: [_jsx(Sidebar, { ...sidebarProps, permissions: permissions, visibleModules: visibleModules, loading: loading }), _jsxs("div", { className: "flex min-h-dvh flex-col pt-14 md:ml-64 md:pt-0", children: [header && _jsx(Header, { title: title }), _jsx("main", { className: "flex-1", children: permissions && access === "none" ? _jsx(NoAccess, {}) : (children ?? _jsx(Outlet, {})) })] })] }) }));
|
|
49
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"HandoverPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/HandoverPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"HandoverPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/HandoverPage.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,iBAAiB;IAChC,gEAAgE;IAChE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAID,wBAAgB,YAAY,CAAC,EAAE,UAAgB,EAAE,SAAoB,EAAE,EAAE,iBAAiB,2CA0FzF"}
|
|
@@ -4,6 +4,7 @@ import { useRouter } from "@tanstack/react-router";
|
|
|
4
4
|
import { Button } from "../ui/button";
|
|
5
5
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card";
|
|
6
6
|
import { supabase } from "../../supabase/client";
|
|
7
|
+
import { readRedirectParam } from "../../auth/redirect";
|
|
7
8
|
import { exchangeHandover } from "../../auth/handover.functions.js";
|
|
8
9
|
const FOUTMELDING = "Inloggen is niet gelukt. Probeer het opnieuw vanaf de inlogpagina.";
|
|
9
10
|
export function HandoverPage({ redirectTo = "/", loginPath = "/login" }) {
|
|
@@ -27,6 +28,8 @@ export function HandoverPage({ redirectTo = "/", loginPath = "/login" }) {
|
|
|
27
28
|
started.current = true;
|
|
28
29
|
(async () => {
|
|
29
30
|
const code = new URLSearchParams(window.location.search).get("code");
|
|
31
|
+
// Doel vóór het opschonen van de URL lezen; alleen same-site paden.
|
|
32
|
+
const target = readRedirectParam(redirectTo);
|
|
30
33
|
if (!code) {
|
|
31
34
|
setFailed(true);
|
|
32
35
|
return;
|
|
@@ -36,14 +39,6 @@ export function HandoverPage({ redirectTo = "/", loginPath = "/login" }) {
|
|
|
36
39
|
const cleanUrl = window.location.pathname + window.location.hash;
|
|
37
40
|
window.history.replaceState(null, "", cleanUrl);
|
|
38
41
|
try {
|
|
39
|
-
// Eerst een eventuele bestaande sessie beëindigen, anders belandt
|
|
40
|
-
// iemand in de omgeving van de vorige gebruiker. Scope 'local':
|
|
41
|
-
// alleen de lokale opslag wissen, geen server-side sessies intrekken.
|
|
42
|
-
// De standaardscope ('global') zou óók de net aangemaakte
|
|
43
|
-
// handover-sessie server-side ongeldig maken (session_not_found).
|
|
44
|
-
const { data } = await supabase.auth.getSession();
|
|
45
|
-
if (data.session)
|
|
46
|
-
await supabase.auth.signOut({ scope: "local" });
|
|
47
42
|
const result = await exchangeHandover({ data: { code } });
|
|
48
43
|
const { error } = await supabase.auth.setSession({
|
|
49
44
|
access_token: result.accessToken,
|
|
@@ -56,7 +51,7 @@ export function HandoverPage({ redirectTo = "/", loginPath = "/login" }) {
|
|
|
56
51
|
await waitForAuthContext();
|
|
57
52
|
// Nooit window.location.href: een volledige pagina-load laat de
|
|
58
53
|
// server (zonder sessie) eerst naar /login redirecten.
|
|
59
|
-
await router.navigate({
|
|
54
|
+
await router.navigate({ href: target, replace: true });
|
|
60
55
|
}
|
|
61
56
|
catch {
|
|
62
57
|
setFailed(true);
|
|
@@ -7,5 +7,5 @@ import { BugsDialog } from "./BugsDialog";
|
|
|
7
7
|
import { NotificationBell } from "./NotificationBell";
|
|
8
8
|
export function Header({ title }) {
|
|
9
9
|
const [bugsDialogOpen, setBugsDialogOpen] = useState(false);
|
|
10
|
-
return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "sticky top-0 z-30 flex h-16 items-center justify-between border-b border-border bg-card px-6", children: [_jsx("h1", { className: "text-xl font-semibold text-foreground", children: title }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "relative hidden md:block", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoeken...", className: "w-64 pl-9" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setBugsDialogOpen(true), children: [_jsx(Bug, { className: "mr-2 h-4 w-4" }), "Bugs melden"] }), _jsx(NotificationBell, {})] })] }), _jsx(BugsDialog, { open: bugsDialogOpen, onOpenChange: setBugsDialogOpen })] }));
|
|
10
|
+
return (_jsxs(_Fragment, { children: [_jsxs("header", { className: "sticky top-[var(--core-topbar-h,0px)] md:top-0 z-30 flex h-16 items-center justify-between border-b border-border bg-card px-6", children: [_jsx("h1", { className: "text-xl font-semibold text-foreground", children: title }), _jsxs("div", { className: "flex items-center gap-4", children: [_jsxs("div", { className: "relative hidden md:block", children: [_jsx(Search, { className: "absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" }), _jsx(Input, { placeholder: "Zoeken...", className: "w-64 pl-9" })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: () => setBugsDialogOpen(true), children: [_jsx(Bug, { className: "mr-2 h-4 w-4" }), "Bugs melden"] }), _jsx(NotificationBell, {})] })] }), _jsx(BugsDialog, { open: bugsDialogOpen, onOpenChange: setBugsDialogOpen })] }));
|
|
11
11
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstellingenPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/InstellingenPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"InstellingenPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/InstellingenPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAgB,MAAM,oBAAoB,CAAC;AAwInE,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,UAAU,EAAE,CAAA;CAAE,2CAsBtE"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { moduleKey } from "../../types/module.js";
|
|
3
|
+
import { resolveLevel } from "../../permissions/resolve.js";
|
|
4
|
+
import { usePermissionSnapshot } from "../../permissions/store.js";
|
|
2
5
|
import { useState } from "react";
|
|
3
6
|
import { Button } from "../ui/button";
|
|
4
7
|
import { Input } from "../ui/input";
|
|
@@ -72,7 +75,12 @@ const coreCards = [
|
|
|
72
75
|
];
|
|
73
76
|
// ─── Main export ──────────────────────────────────────────────────────────────
|
|
74
77
|
export function InstellingenPage({ modules }) {
|
|
75
|
-
|
|
78
|
+
// Sinds 1.0.23: modulekaarten erven het recht van hun module (of featureId).
|
|
79
|
+
// Core-kaarten altijd. Laden/fout/admin → tonen. Zichtbaarheid, geen beveiliging.
|
|
80
|
+
const snap = usePermissionSnapshot();
|
|
81
|
+
const filterOn = snap.status === "ready" && !snap.roles.includes("admin") && !snap.roles.includes("superadmin");
|
|
82
|
+
const moduleCards = modules.flatMap((m) => m.settingsCards.filter((card) => !filterOn ||
|
|
83
|
+
resolveLevel(snap.rows, card.moduleId ?? moduleKey(m), card.featureId ?? null) !== "none"));
|
|
76
84
|
const allCards = [...coreCards, ...moduleCards].sort((a, b) => a.order - b.order);
|
|
77
|
-
return (_jsx("
|
|
85
|
+
return (_jsx("div", { className: "p-6", children: _jsx("div", { className: "grid gap-6 lg:grid-cols-2", children: allCards.map((card, i) => _jsx(card.component, {}, i)) }) }));
|
|
78
86
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"LoginPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/LoginPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"LoginPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/LoginPage.tsx"],"names":[],"mappings":"AAWA,UAAU,cAAc;IACtB,4DAA4D;IAC5D,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,wBAAgB,SAAS,CAAC,EAAE,UAA4B,EAAE,EAAE,cAAc,2CA+HzE"}
|
|
@@ -8,6 +8,7 @@ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle }
|
|
|
8
8
|
import { toast } from "sonner";
|
|
9
9
|
import { loginSchema } from "../../lib/validationSchemas";
|
|
10
10
|
import { supabase } from "../../supabase/client";
|
|
11
|
+
import { readRedirectParam } from "../../auth/redirect";
|
|
11
12
|
export function LoginPage({ redirectTo = "/instellingen" }) {
|
|
12
13
|
const router = useRouter();
|
|
13
14
|
const [email, setEmail] = useState("");
|
|
@@ -42,12 +43,11 @@ export function LoginPage({ redirectTo = "/instellingen" }) {
|
|
|
42
43
|
}
|
|
43
44
|
// Client-side navigatie zonder volledige herlaad: dat voorkomt de flits
|
|
44
45
|
// waarbij de server (zonder sessie) eerst naar /login terugstuurt.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
await router.navigate({ to: redirectTo, replace: true });
|
|
46
|
+
// Geen window.location.href-fallback: een volledige load laat de guard
|
|
47
|
+
// opnieuw beslissen; de root-invalidate pakt de sessie alsnog op.
|
|
48
|
+
await waitForAuthContext();
|
|
49
|
+
const target = readRedirectParam(redirectTo);
|
|
50
|
+
await router.navigate({ href: target, replace: true });
|
|
51
51
|
setIsLoading(false);
|
|
52
52
|
};
|
|
53
53
|
const handleForgotPassword = async (e) => {
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
interface ResetWachtwoordPageProps {
|
|
2
2
|
redirectTo: string;
|
|
3
|
+
/** Pad van de inlogpagina. Standaard "/login". */
|
|
4
|
+
loginPath?: string;
|
|
3
5
|
}
|
|
4
|
-
export declare function ResetWachtwoordPage({ redirectTo }: ResetWachtwoordPageProps): import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare function ResetWachtwoordPage({ redirectTo, loginPath }: ResetWachtwoordPageProps): import("react/jsx-runtime").JSX.Element;
|
|
5
7
|
export {};
|
|
6
8
|
//# sourceMappingURL=ResetWachtwoordPage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResetWachtwoordPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/ResetWachtwoordPage.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ResetWachtwoordPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/ResetWachtwoordPage.tsx"],"names":[],"mappings":"AASA,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,SAAoB,EAAE,EAAE,wBAAwB,2CAwFjG"}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect } from "react";
|
|
3
|
+
import { useRouter } from "@tanstack/react-router";
|
|
3
4
|
import { Button } from "../ui/button";
|
|
4
5
|
import { Input } from "../ui/input";
|
|
5
6
|
import { Label } from "../ui/label";
|
|
6
7
|
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card";
|
|
7
8
|
import { toast } from "sonner";
|
|
8
9
|
import { supabase } from "../../supabase/client";
|
|
9
|
-
export function ResetWachtwoordPage({ redirectTo }) {
|
|
10
|
+
export function ResetWachtwoordPage({ redirectTo, loginPath = "/login" }) {
|
|
11
|
+
const router = useRouter();
|
|
10
12
|
const [password, setPassword] = useState("");
|
|
11
13
|
const [confirmPassword, setConfirmPassword] = useState("");
|
|
12
14
|
const [isLoading, setIsLoading] = useState(false);
|
|
@@ -41,11 +43,11 @@ export function ResetWachtwoordPage({ redirectTo }) {
|
|
|
41
43
|
}
|
|
42
44
|
else {
|
|
43
45
|
toast.success("Wachtwoord succesvol gewijzigd!");
|
|
44
|
-
|
|
46
|
+
await router.navigate({ href: redirectTo });
|
|
45
47
|
}
|
|
46
48
|
};
|
|
47
49
|
if (!isReady) {
|
|
48
|
-
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { className: "text-2xl", children: "Ongeldige link" }), _jsx(CardDescription, { children: "Deze reset-link is ongeldig of verlopen. Vraag een nieuwe aan via de inlogpagina." })] }), _jsx(CardFooter, { className: "justify-center", children: _jsx(Button, { onClick: () =>
|
|
50
|
+
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { className: "text-2xl", children: "Ongeldige link" }), _jsx(CardDescription, { children: "Deze reset-link is ongeldig of verlopen. Vraag een nieuwe aan via de inlogpagina." })] }), _jsx(CardFooter, { className: "justify-center", children: _jsx(Button, { onClick: () => router.navigate({ href: loginPath }), children: "Naar inlogpagina" }) })] }) }));
|
|
49
51
|
}
|
|
50
52
|
return (_jsx("div", { className: "min-h-screen flex items-center justify-center bg-background p-4", children: _jsxs(Card, { className: "w-full max-w-md", children: [_jsxs(CardHeader, { className: "text-center", children: [_jsx(CardTitle, { className: "text-2xl", children: "Nieuw wachtwoord instellen" }), _jsx(CardDescription, { children: "Kies een nieuw wachtwoord voor je account" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "password", children: "Nieuw wachtwoord" }), _jsx(Input, { id: "password", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "confirmPassword", children: "Bevestig wachtwoord" }), _jsx(Input, { id: "confirmPassword", type: "password", value: confirmPassword, onChange: (e) => setConfirmPassword(e.target.value), required: true, minLength: 6, maxLength: 128 })] })] }), _jsx(CardFooter, { children: _jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Opslaan..." : "Wachtwoord opslaan" }) })] })] }) }));
|
|
51
53
|
}
|
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
import type { AuthContext } from "../../auth/useAuth";
|
|
2
2
|
import type { FlowModule } from "../../types/module.js";
|
|
3
|
-
interface SidebarProps {
|
|
3
|
+
export interface SidebarProps {
|
|
4
4
|
auth: AuthContext;
|
|
5
5
|
modules: FlowModule[];
|
|
6
|
+
/**
|
|
7
|
+
* Identificeert de shell (bijv. "floriday-voorraad"). Komt als data-attribuut
|
|
8
|
+
* op de zijbalk, zodat extensies en shells per-shell volgorde of styling
|
|
9
|
+
* kunnen koppelen.
|
|
10
|
+
*/
|
|
11
|
+
shellId?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Sinds 1.0.23. Rechtenfilter vóór de extensie-transforms. Standaard `false`
|
|
14
|
+
* bij losse `<Sidebar>` (gedrag 1.0.22); AuthenticatedLayout zet hem aan.
|
|
15
|
+
* Zichtbaarheid, geen beveiliging.
|
|
16
|
+
*/
|
|
17
|
+
permissions?: boolean;
|
|
18
|
+
/** Sinds 1.0.23. Logopad; anders `logo` uit /huisstijl.json, anders /logo.png. */
|
|
19
|
+
logoSrc?: string;
|
|
20
|
+
/** Sinds 1.0.23. Instellingen tonen (standaard true). Uitloggen is nooit te verbergen. */
|
|
21
|
+
showSettings?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Sinds 1.0.23. Al berekende lijst (AuthenticatedLayout geeft die door);
|
|
24
|
+
* de Sidebar slaat dan zijn eigen pipeline over.
|
|
25
|
+
*/
|
|
26
|
+
visibleModules?: FlowModule[];
|
|
27
|
+
/** Sinds 1.0.23. Toon een skeleton i.p.v. de lijst (eerste laden rechten). */
|
|
28
|
+
loading?: boolean;
|
|
6
29
|
}
|
|
7
|
-
|
|
8
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Sinds 1.0.23. Pipeline: eerst rechten, dan extensie-transforms (ordening).
|
|
32
|
+
* Zichtbaarheid, geen beveiliging.
|
|
33
|
+
*/
|
|
34
|
+
export declare function useSidebarModules(modules: FlowModule[], permissions: boolean): {
|
|
35
|
+
visibleModules: FlowModule[];
|
|
36
|
+
loading: boolean;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Desktop (md+): vaste zijbalk links, w-64.
|
|
40
|
+
* Mobiel (<md): vaste topbalk (h-14) met menuknop die de zijbalk als Sheet opent.
|
|
41
|
+
*/
|
|
42
|
+
export declare function Sidebar({ auth, modules, shellId, permissions, logoSrc, showSettings, visibleModules, loading, }: SidebarProps): import("react/jsx-runtime").JSX.Element;
|
|
9
43
|
//# sourceMappingURL=Sidebar.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/Sidebar.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"Sidebar.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/Sidebar.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,KAAK,EAAE,UAAU,EAAW,MAAM,uBAAuB,CAAC;AAOjE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;;OAIG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,kFAAkF;IAClF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,UAAU,EAAE,CAAC;IAC9B,8EAA8E;IAC9E,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,OAAO;;;EAI5E;AAmKD;;;GAGG;AACH,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,OAAO,EACP,OAAO,EACP,WAAmB,EACnB,OAAO,EACP,YAAmB,EACnB,cAAc,EACd,OAAO,GACR,EAAE,YAAY,2CA2Dd"}
|