@flowselections/core 1.0.0

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 (196) hide show
  1. package/dist/auth/useAuth.d.ts +28 -0
  2. package/dist/auth/useAuth.d.ts.map +1 -0
  3. package/dist/auth/useAuth.js +72 -0
  4. package/dist/components/instellingen/NotificationSettingsCard.d.ts +2 -0
  5. package/dist/components/instellingen/NotificationSettingsCard.d.ts.map +1 -0
  6. package/dist/components/instellingen/NotificationSettingsCard.js +63 -0
  7. package/dist/components/layout/BugsDialog.d.ts +7 -0
  8. package/dist/components/layout/BugsDialog.d.ts.map +1 -0
  9. package/dist/components/layout/BugsDialog.js +148 -0
  10. package/dist/components/layout/Header.d.ts +6 -0
  11. package/dist/components/layout/Header.d.ts.map +1 -0
  12. package/dist/components/layout/Header.js +11 -0
  13. package/dist/components/layout/InstellingenPage.d.ts +5 -0
  14. package/dist/components/layout/InstellingenPage.d.ts.map +1 -0
  15. package/dist/components/layout/InstellingenPage.js +79 -0
  16. package/dist/components/layout/LoginPage.d.ts +6 -0
  17. package/dist/components/layout/LoginPage.d.ts.map +1 -0
  18. package/dist/components/layout/LoginPage.js +57 -0
  19. package/dist/components/layout/NotificationBell.d.ts +2 -0
  20. package/dist/components/layout/NotificationBell.d.ts.map +1 -0
  21. package/dist/components/layout/NotificationBell.js +45 -0
  22. package/dist/components/layout/ResetWachtwoordPage.d.ts +6 -0
  23. package/dist/components/layout/ResetWachtwoordPage.d.ts.map +1 -0
  24. package/dist/components/layout/ResetWachtwoordPage.js +51 -0
  25. package/dist/components/layout/Sidebar.d.ts +9 -0
  26. package/dist/components/layout/Sidebar.d.ts.map +1 -0
  27. package/dist/components/layout/Sidebar.js +32 -0
  28. package/dist/components/ui/accordion.d.ts +8 -0
  29. package/dist/components/ui/accordion.d.ts.map +1 -0
  30. package/dist/components/ui/accordion.js +13 -0
  31. package/dist/components/ui/alert-dialog.d.ts +21 -0
  32. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  33. package/dist/components/ui/alert-dialog.js +26 -0
  34. package/dist/components/ui/alert.d.ts +9 -0
  35. package/dist/components/ui/alert.d.ts.map +1 -0
  36. package/dist/components/ui/alert.js +22 -0
  37. package/dist/components/ui/aspect-ratio.d.ts +4 -0
  38. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  39. package/dist/components/ui/aspect-ratio.js +3 -0
  40. package/dist/components/ui/avatar.d.ts +7 -0
  41. package/dist/components/ui/avatar.d.ts.map +1 -0
  42. package/dist/components/ui/avatar.js +12 -0
  43. package/dist/components/ui/badge.d.ts +10 -0
  44. package/dist/components/ui/badge.d.ts.map +1 -0
  45. package/dist/components/ui/badge.js +20 -0
  46. package/dist/components/ui/breadcrumb.d.ts +20 -0
  47. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  48. package/dist/components/ui/breadcrumb.js +23 -0
  49. package/dist/components/ui/button.d.ts +12 -0
  50. package/dist/components/ui/button.d.ts.map +1 -0
  51. package/dist/components/ui/button.js +33 -0
  52. package/dist/components/ui/calendar.d.ts +9 -0
  53. package/dist/components/ui/calendar.d.ts.map +1 -0
  54. package/dist/components/ui/calendar.js +74 -0
  55. package/dist/components/ui/card.d.ts +9 -0
  56. package/dist/components/ui/card.d.ts.map +1 -0
  57. package/dist/components/ui/card.js +16 -0
  58. package/dist/components/ui/carousel.d.ts +19 -0
  59. package/dist/components/ui/carousel.d.ts.map +1 -0
  60. package/dist/components/ui/carousel.js +98 -0
  61. package/dist/components/ui/chart.d.ts +63 -0
  62. package/dist/components/ui/chart.d.ts.map +1 -0
  63. package/dist/components/ui/chart.js +134 -0
  64. package/dist/components/ui/checkbox.d.ts +5 -0
  65. package/dist/components/ui/checkbox.d.ts.map +1 -0
  66. package/dist/components/ui/checkbox.js +8 -0
  67. package/dist/components/ui/collapsible.d.ts +6 -0
  68. package/dist/components/ui/collapsible.d.ts.map +1 -0
  69. package/dist/components/ui/collapsible.js +6 -0
  70. package/dist/components/ui/command.d.ts +81 -0
  71. package/dist/components/ui/command.d.ts.map +1 -0
  72. package/dist/components/ui/command.js +29 -0
  73. package/dist/components/ui/context-menu.d.ts +28 -0
  74. package/dist/components/ui/context-menu.d.ts.map +1 -0
  75. package/dist/components/ui/context-menu.js +33 -0
  76. package/dist/components/ui/dialog.d.ts +20 -0
  77. package/dist/components/ui/dialog.d.ts.map +1 -0
  78. package/dist/components/ui/dialog.js +23 -0
  79. package/dist/components/ui/drawer.d.ts +23 -0
  80. package/dist/components/ui/drawer.d.ts.map +1 -0
  81. package/dist/components/ui/drawer.js +22 -0
  82. package/dist/components/ui/dropdown-menu.d.ts +28 -0
  83. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  84. package/dist/components/ui/dropdown-menu.js +36 -0
  85. package/dist/components/ui/form.d.ts +24 -0
  86. package/dist/components/ui/form.d.ts.map +1 -0
  87. package/dist/components/ui/form.js +65 -0
  88. package/dist/components/ui/hover-card.d.ts +7 -0
  89. package/dist/components/ui/hover-card.d.ts.map +1 -0
  90. package/dist/components/ui/hover-card.js +9 -0
  91. package/dist/components/ui/icon-picker.d.ts +15 -0
  92. package/dist/components/ui/icon-picker.d.ts.map +1 -0
  93. package/dist/components/ui/icon-picker.js +73 -0
  94. package/dist/components/ui/input-otp.d.ts +35 -0
  95. package/dist/components/ui/input-otp.d.ts.map +1 -0
  96. package/dist/components/ui/input-otp.js +18 -0
  97. package/dist/components/ui/input.d.ts +4 -0
  98. package/dist/components/ui/input.d.ts.map +1 -0
  99. package/dist/components/ui/input.js +8 -0
  100. package/dist/components/ui/label.d.ts +6 -0
  101. package/dist/components/ui/label.d.ts.map +1 -0
  102. package/dist/components/ui/label.js +10 -0
  103. package/dist/components/ui/menubar.d.ts +29 -0
  104. package/dist/components/ui/menubar.d.ts.map +1 -0
  105. package/dist/components/ui/menubar.js +45 -0
  106. package/dist/components/ui/navigation-menu.d.ts +13 -0
  107. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  108. package/dist/components/ui/navigation-menu.js +24 -0
  109. package/dist/components/ui/pagination.d.ts +29 -0
  110. package/dist/components/ui/pagination.d.ts.map +1 -0
  111. package/dist/components/ui/pagination.js +23 -0
  112. package/dist/components/ui/popover.d.ts +8 -0
  113. package/dist/components/ui/popover.d.ts.map +1 -0
  114. package/dist/components/ui/popover.js +10 -0
  115. package/dist/components/ui/progress.d.ts +5 -0
  116. package/dist/components/ui/progress.d.ts.map +1 -0
  117. package/dist/components/ui/progress.js +8 -0
  118. package/dist/components/ui/radio-group.d.ts +6 -0
  119. package/dist/components/ui/radio-group.d.ts.map +1 -0
  120. package/dist/components/ui/radio-group.js +14 -0
  121. package/dist/components/ui/resizable.d.ts +8 -0
  122. package/dist/components/ui/resizable.d.ts.map +1 -0
  123. package/dist/components/ui/resizable.js +8 -0
  124. package/dist/components/ui/scroll-area.d.ts +6 -0
  125. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  126. package/dist/components/ui/scroll-area.js +11 -0
  127. package/dist/components/ui/select.d.ts +14 -0
  128. package/dist/components/ui/select.d.ts.map +1 -0
  129. package/dist/components/ui/select.js +27 -0
  130. package/dist/components/ui/separator.d.ts +5 -0
  131. package/dist/components/ui/separator.d.ts.map +1 -0
  132. package/dist/components/ui/separator.js +7 -0
  133. package/dist/components/ui/sheet.d.ts +26 -0
  134. package/dist/components/ui/sheet.d.ts.map +1 -0
  135. package/dist/components/ui/sheet.js +37 -0
  136. package/dist/components/ui/sidebar.d.ts +66 -0
  137. package/dist/components/ui/sidebar.d.ts.map +1 -0
  138. package/dist/components/ui/sidebar.js +224 -0
  139. package/dist/components/ui/skeleton.d.ts +3 -0
  140. package/dist/components/ui/skeleton.d.ts.map +1 -0
  141. package/dist/components/ui/skeleton.js +6 -0
  142. package/dist/components/ui/slider.d.ts +5 -0
  143. package/dist/components/ui/slider.d.ts.map +1 -0
  144. package/dist/components/ui/slider.js +7 -0
  145. package/dist/components/ui/sonner.d.ts +5 -0
  146. package/dist/components/ui/sonner.d.ts.map +1 -0
  147. package/dist/components/ui/sonner.js +13 -0
  148. package/dist/components/ui/switch.d.ts +5 -0
  149. package/dist/components/ui/switch.d.ts.map +1 -0
  150. package/dist/components/ui/switch.js +7 -0
  151. package/dist/components/ui/table.d.ts +11 -0
  152. package/dist/components/ui/table.d.ts.map +1 -0
  153. package/dist/components/ui/table.js +20 -0
  154. package/dist/components/ui/tabs.d.ts +8 -0
  155. package/dist/components/ui/tabs.d.ts.map +1 -0
  156. package/dist/components/ui/tabs.js +12 -0
  157. package/dist/components/ui/textarea.d.ts +4 -0
  158. package/dist/components/ui/textarea.d.ts.map +1 -0
  159. package/dist/components/ui/textarea.js +8 -0
  160. package/dist/components/ui/toggle-group.d.ts +13 -0
  161. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  162. package/dist/components/ui/toggle-group.js +21 -0
  163. package/dist/components/ui/toggle.d.ts +13 -0
  164. package/dist/components/ui/toggle.d.ts.map +1 -0
  165. package/dist/components/ui/toggle.js +25 -0
  166. package/dist/components/ui/tooltip.d.ts +8 -0
  167. package/dist/components/ui/tooltip.d.ts.map +1 -0
  168. package/dist/components/ui/tooltip.js +11 -0
  169. package/dist/hooks/use-mobile.d.ts +2 -0
  170. package/dist/hooks/use-mobile.d.ts.map +1 -0
  171. package/dist/hooks/use-mobile.js +15 -0
  172. package/dist/index.d.ts +32 -0
  173. package/dist/index.d.ts.map +1 -0
  174. package/dist/index.js +33 -0
  175. package/dist/lib/utils.d.ts +3 -0
  176. package/dist/lib/utils.d.ts.map +1 -0
  177. package/dist/lib/utils.js +5 -0
  178. package/dist/lib/validationSchemas.d.ts +15 -0
  179. package/dist/lib/validationSchemas.d.ts.map +1 -0
  180. package/dist/lib/validationSchemas.js +25 -0
  181. package/dist/supabase/auth-middleware.d.ts +639 -0
  182. package/dist/supabase/auth-middleware.d.ts.map +1 -0
  183. package/dist/supabase/auth-middleware.js +52 -0
  184. package/dist/supabase/client.d.ts +635 -0
  185. package/dist/supabase/client.d.ts.map +1 -0
  186. package/dist/supabase/client.js +13 -0
  187. package/dist/supabase/client.server.d.ts +635 -0
  188. package/dist/supabase/client.server.d.ts.map +1 -0
  189. package/dist/supabase/client.server.js +30 -0
  190. package/dist/supabase/types.d.ts +717 -0
  191. package/dist/supabase/types.d.ts.map +1 -0
  192. package/dist/supabase/types.js +7 -0
  193. package/dist/types/module.d.ts +20 -0
  194. package/dist/types/module.d.ts.map +1 -0
  195. package/dist/types/module.js +1 -0
  196. package/package.json +100 -0
@@ -0,0 +1,28 @@
1
+ import { User, Session } from "@supabase/supabase-js";
2
+ export type AppRole = "admin" | "manager" | "warehouse" | "sales" | "developer";
3
+ export interface AuthState {
4
+ user: User | null;
5
+ session: Session | null;
6
+ loading: boolean;
7
+ roles: AppRole[];
8
+ isAdmin: boolean;
9
+ isAuthenticated: boolean;
10
+ profile: {
11
+ display_name: string | null;
12
+ email: string | null;
13
+ } | null;
14
+ }
15
+ export interface AuthActions {
16
+ signIn: (email: string, password: string) => Promise<{
17
+ error: Error | null;
18
+ }>;
19
+ signOut: () => Promise<void>;
20
+ resetPassword: (email: string) => Promise<{
21
+ error: Error | null;
22
+ }>;
23
+ refreshRoles: () => Promise<void>;
24
+ }
25
+ export type AuthContext = AuthState & AuthActions;
26
+ export declare function useAuthState(): AuthContext;
27
+ export declare function useAuth(): AuthContext;
28
+ //# sourceMappingURL=useAuth.d.ts.map
@@ -0,0 +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,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,GAAG,OAAO,GAAG,WAAW,CAAC;AAEhF,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,72 @@
1
+ import { useState, useEffect, useCallback } from "react";
2
+ import { supabase } from "../supabase/client";
3
+ export function useAuthState() {
4
+ const [user, setUser] = useState(null);
5
+ const [session, setSession] = useState(null);
6
+ const [loading, setLoading] = useState(true);
7
+ const [roles, setRoles] = useState([]);
8
+ const [profile, setProfile] = useState(null);
9
+ const isAdmin = roles.includes("admin");
10
+ const isAuthenticated = !!user;
11
+ const fetchUserData = useCallback(async (userId) => {
12
+ try {
13
+ const [rolesRes, profileRes] = await Promise.all([
14
+ supabase.from("user_roles").select("role").eq("user_id", userId),
15
+ supabase.from("profiles").select("display_name, email").eq("id", userId).single(),
16
+ ]);
17
+ if (rolesRes.data)
18
+ setRoles(rolesRes.data.map((r) => r.role));
19
+ if (profileRes.data)
20
+ setProfile(profileRes.data);
21
+ }
22
+ catch (error) {
23
+ console.error("Error fetching user data:", error);
24
+ }
25
+ }, []);
26
+ const refreshRoles = useCallback(async () => {
27
+ if (user)
28
+ await fetchUserData(user.id);
29
+ }, [user, fetchUserData]);
30
+ useEffect(() => {
31
+ const { data: { subscription } } = supabase.auth.onAuthStateChange((_event, session) => {
32
+ setSession(session);
33
+ setUser(session?.user ?? null);
34
+ setLoading(false);
35
+ if (session?.user) {
36
+ setTimeout(() => fetchUserData(session.user.id), 0);
37
+ }
38
+ else {
39
+ setRoles([]);
40
+ setProfile(null);
41
+ }
42
+ });
43
+ supabase.auth.getSession().then(({ data: { session } }) => {
44
+ setSession(session);
45
+ setUser(session?.user ?? null);
46
+ setLoading(false);
47
+ if (session?.user)
48
+ fetchUserData(session.user.id);
49
+ });
50
+ return () => subscription.unsubscribe();
51
+ }, [fetchUserData]);
52
+ const signIn = async (email, password) => {
53
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
54
+ return { error: error };
55
+ };
56
+ const signOut = async () => {
57
+ await supabase.auth.signOut();
58
+ };
59
+ const resetPassword = async (email) => {
60
+ const { error } = await supabase.auth.resetPasswordForEmail(email, {
61
+ redirectTo: `${window.location.origin}/reset-wachtwoord`,
62
+ });
63
+ return { error: error };
64
+ };
65
+ return {
66
+ user, session, loading, roles, isAdmin, isAuthenticated, profile,
67
+ signIn, signOut, resetPassword, refreshRoles,
68
+ };
69
+ }
70
+ export function useAuth() {
71
+ return useAuthState();
72
+ }
@@ -0,0 +1,2 @@
1
+ export declare function NotificationSettingsCard(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=NotificationSettingsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationSettingsCard.d.ts","sourceRoot":"","sources":["../../../src/core/components/instellingen/NotificationSettingsCard.tsx"],"names":[],"mappings":"AAWA,wBAAgB,wBAAwB,4CAiFvC"}
@@ -0,0 +1,63 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
4
+ import { Switch } from "../ui/switch";
5
+ import { Bell, Loader2 } from "lucide-react";
6
+ import { supabase } from "../../supabase/client";
7
+ import { toast } from "sonner";
8
+ export function NotificationSettingsCard() {
9
+ const [prefs, setPrefs] = useState({ email_notifications: true });
10
+ const [loading, setLoading] = useState(true);
11
+ const [saving, setSaving] = useState(false);
12
+ useEffect(() => {
13
+ (async () => {
14
+ try {
15
+ const { data: { user } } = await supabase.auth.getUser();
16
+ if (!user)
17
+ return;
18
+ const { data } = await supabase
19
+ .from("profiles")
20
+ .select("notification_preferences")
21
+ .eq("id", user.id)
22
+ .maybeSingle();
23
+ if (data?.notification_preferences) {
24
+ setPrefs(data.notification_preferences);
25
+ }
26
+ }
27
+ catch {
28
+ // ignore
29
+ }
30
+ finally {
31
+ setLoading(false);
32
+ }
33
+ })();
34
+ }, []);
35
+ const updatePref = async (key, value) => {
36
+ const newPrefs = { ...prefs, [key]: value };
37
+ setPrefs(newPrefs);
38
+ setSaving(true);
39
+ try {
40
+ const { data: { user } } = await supabase.auth.getUser();
41
+ if (!user)
42
+ throw new Error("Niet ingelogd");
43
+ const { error } = await supabase
44
+ .from("profiles")
45
+ .update({ notification_preferences: newPrefs })
46
+ .eq("id", user.id);
47
+ if (error)
48
+ throw error;
49
+ toast.success("Notificatie-instelling opgeslagen");
50
+ }
51
+ catch {
52
+ setPrefs(prefs); // rollback
53
+ toast.error("Fout bij opslaan");
54
+ }
55
+ finally {
56
+ setSaving(false);
57
+ }
58
+ };
59
+ if (loading) {
60
+ return (_jsx(Card, { children: _jsx(CardContent, { className: "flex items-center justify-center py-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) }) }));
61
+ }
62
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(Bell, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Notificatievoorkeuren" })] }), _jsx(CardDescription, { children: "Beheer je notificatie-instellingen" })] }), _jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs("div", { children: [_jsx("p", { className: "text-sm font-medium", children: "E-mailnotificaties" }), _jsx("p", { className: "text-xs text-muted-foreground", children: "Ontvang meldingen per e-mail" })] }), _jsx(Switch, { checked: prefs.email_notifications, onCheckedChange: (v) => updatePref("email_notifications", v), disabled: saving })] }) })] }));
63
+ }
@@ -0,0 +1,7 @@
1
+ interface BugsDialogProps {
2
+ open: boolean;
3
+ onOpenChange: (open: boolean) => void;
4
+ }
5
+ export declare function BugsDialog({ open, onOpenChange }: BugsDialogProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=BugsDialog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BugsDialog.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/BugsDialog.tsx"],"names":[],"mappings":"AAyBA,UAAU,eAAe;IACvB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;CACvC;AAQD,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,eAAe,2CAqOjE"}
@@ -0,0 +1,148 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState, useEffect, useRef } from "react";
3
+ import { Dialog, DialogContent, DialogHeader, DialogTitle } from "../ui/dialog";
4
+ import { Button } from "../ui/button";
5
+ import { Input } from "../ui/input";
6
+ import { Textarea } from "../ui/textarea";
7
+ import { Label } from "../ui/label";
8
+ import { Badge } from "../ui/badge";
9
+ import { Select, SelectContent, SelectItem, SelectTrigger } from "../ui/select";
10
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from "../ui/tabs";
11
+ import { Plus, Loader2, CheckCircle, Trash2, Paperclip, X, FileText } from "lucide-react";
12
+ import { supabase } from "../../supabase/client";
13
+ import { toast } from "sonner";
14
+ import { bugReportSchema, validateFileUpload, ALLOWED_FILE_TYPES } from "../../lib/validationSchemas";
15
+ const statusConfig = {
16
+ open: { label: "Open", variant: "destructive" },
17
+ in_progress: { label: "In behandeling", variant: "default" },
18
+ resolved: { label: "Opgelost", variant: "secondary" },
19
+ };
20
+ export function BugsDialog({ open, onOpenChange }) {
21
+ const [bugs, setBugs] = useState([]);
22
+ const [loading, setLoading] = useState(true);
23
+ const [submitting, setSubmitting] = useState(false);
24
+ const [activeTab, setActiveTab] = useState("list");
25
+ const fileInputRef = useRef(null);
26
+ const [title, setTitle] = useState("");
27
+ const [description, setDescription] = useState("");
28
+ const [attachment, setAttachment] = useState(null);
29
+ const fetchBugs = async () => {
30
+ try {
31
+ const { data, error } = await supabase
32
+ .from("bugs")
33
+ .select("*")
34
+ .order("created_at", { ascending: false });
35
+ if (error)
36
+ throw error;
37
+ setBugs(data || []);
38
+ }
39
+ catch {
40
+ toast.error("Fout bij ophalen van bugs");
41
+ }
42
+ finally {
43
+ setLoading(false);
44
+ }
45
+ };
46
+ useEffect(() => {
47
+ if (open)
48
+ fetchBugs();
49
+ }, [open]);
50
+ const handleSubmit = async () => {
51
+ const validation = bugReportSchema.safeParse({ title: title.trim(), description: description.trim() });
52
+ if (!validation.success) {
53
+ toast.error(validation.error.issues[0].message);
54
+ return;
55
+ }
56
+ setSubmitting(true);
57
+ try {
58
+ const { data: { user } } = await supabase.auth.getUser();
59
+ if (!user)
60
+ throw new Error("Niet ingelogd");
61
+ let attachmentUrl = null;
62
+ if (attachment) {
63
+ const fileExt = attachment.name.split('.').pop();
64
+ const fileName = `${Date.now()}-${Math.random().toString(36).substring(7)}.${fileExt}`;
65
+ const { error: uploadError } = await supabase.storage.from('bug-attachments').upload(fileName, attachment);
66
+ if (uploadError)
67
+ throw uploadError;
68
+ const { data: signedData, error: signedError } = await supabase.storage
69
+ .from('bug-attachments').createSignedUrl(fileName, 3600 * 24 * 7);
70
+ if (signedError)
71
+ throw signedError;
72
+ attachmentUrl = signedData?.signedUrl || null;
73
+ }
74
+ const { error } = await supabase.from("bugs").insert({
75
+ title: title.trim(),
76
+ description: description.trim(),
77
+ priority: "normaal",
78
+ status: "open",
79
+ attachment_url: attachmentUrl,
80
+ user_id: user.id,
81
+ });
82
+ if (error)
83
+ throw error;
84
+ toast.success("Bug gemeld!");
85
+ setTitle("");
86
+ setDescription("");
87
+ setAttachment(null);
88
+ setActiveTab("list");
89
+ await fetchBugs();
90
+ }
91
+ catch {
92
+ toast.error("Fout bij melden van bug");
93
+ }
94
+ finally {
95
+ setSubmitting(false);
96
+ }
97
+ };
98
+ const handleFileChange = (e) => {
99
+ const file = e.target.files?.[0];
100
+ if (file) {
101
+ const validation = validateFileUpload(file);
102
+ if (!validation.valid) {
103
+ toast.error(validation.error);
104
+ return;
105
+ }
106
+ setAttachment(file);
107
+ }
108
+ };
109
+ const removeAttachment = () => {
110
+ setAttachment(null);
111
+ if (fileInputRef.current)
112
+ fileInputRef.current.value = "";
113
+ };
114
+ const handleUpdateStatus = async (id, newStatus) => {
115
+ try {
116
+ const { error } = await supabase.from("bugs").update({ status: newStatus }).eq("id", id);
117
+ if (error)
118
+ throw error;
119
+ await fetchBugs();
120
+ toast.success("Status bijgewerkt");
121
+ }
122
+ catch {
123
+ toast.error("Fout bij bijwerken");
124
+ }
125
+ };
126
+ const handleDelete = async (id) => {
127
+ try {
128
+ const { error } = await supabase.from("bugs").delete().eq("id", id);
129
+ if (error)
130
+ throw error;
131
+ await fetchBugs();
132
+ toast.success("Bug verwijderd");
133
+ }
134
+ catch {
135
+ toast.error("Fout bij verwijderen");
136
+ }
137
+ };
138
+ const formatDate = (dateStr) => {
139
+ return new Date(dateStr).toLocaleDateString("nl-NL", {
140
+ day: "2-digit", month: "2-digit", year: "numeric", hour: "2-digit", minute: "2-digit",
141
+ });
142
+ };
143
+ const acceptString = ALLOWED_FILE_TYPES.join(",");
144
+ return (_jsx(Dialog, { open: open, onOpenChange: onOpenChange, children: _jsxs(DialogContent, { className: "max-w-2xl max-h-[85vh] overflow-hidden flex flex-col", children: [_jsx(DialogHeader, { children: _jsx(DialogTitle, { children: "Bugs" }) }), _jsxs(Tabs, { value: activeTab, onValueChange: setActiveTab, className: "flex-1 flex flex-col overflow-hidden", children: [_jsxs(TabsList, { className: "grid w-full grid-cols-2", children: [_jsxs(TabsTrigger, { value: "list", children: ["Overzicht (", bugs.filter(b => b.status !== "resolved").length, ")"] }), _jsxs(TabsTrigger, { value: "new", children: [_jsx(Plus, { className: "mr-2 h-4 w-4" }), "Nieuwe Bug"] })] }), _jsx(TabsContent, { value: "list", className: "flex-1 overflow-y-auto mt-4", children: loading ? (_jsx("div", { className: "flex items-center justify-center py-8", children: _jsx(Loader2, { className: "h-6 w-6 animate-spin text-muted-foreground" }) })) : bugs.length === 0 ? (_jsx("div", { className: "text-center py-8 text-muted-foreground", children: "Geen bugs gemeld \uD83C\uDF89" })) : (_jsx("div", { className: "space-y-3", children: bugs.map((bug) => {
145
+ const status = statusConfig[bug.status] || statusConfig.open;
146
+ return (_jsxs("div", { className: "rounded-lg border border-border p-4 space-y-2", children: [_jsxs("div", { className: "flex items-start justify-between gap-2", children: [_jsxs("div", { className: "flex-1", children: [_jsx("h4", { className: "font-medium", children: bug.title }), _jsx("p", { className: "text-sm text-muted-foreground mt-1", children: bug.description }), bug.attachment_url && (_jsxs("a", { href: bug.attachment_url, target: "_blank", rel: "noopener noreferrer", className: "inline-flex items-center gap-1 text-xs text-primary hover:underline mt-2", children: [_jsx(FileText, { className: "h-3 w-3" }), "Bijlage bekijken"] }))] }), _jsx(Button, { variant: "ghost", size: "icon", className: "shrink-0", onClick: () => handleDelete(bug.id), children: _jsx(Trash2, { className: "h-4 w-4" }) })] }), _jsxs("div", { className: "flex items-center justify-between", children: [_jsxs(Select, { value: bug.status, onValueChange: (value) => handleUpdateStatus(bug.id, value), children: [_jsx(SelectTrigger, { className: "h-7 w-auto", children: _jsx(Badge, { variant: status.variant, children: status.label }) }), _jsxs(SelectContent, { children: [_jsx(SelectItem, { value: "open", children: _jsx(Badge, { variant: "destructive", children: "Open" }) }), _jsx(SelectItem, { value: "in_progress", children: _jsx(Badge, { variant: "default", children: "In behandeling" }) }), _jsx(SelectItem, { value: "resolved", children: _jsx(Badge, { variant: "secondary", children: "Opgelost" }) })] })] }), _jsx("span", { className: "text-xs text-muted-foreground", children: formatDate(bug.created_at) })] })] }, bug.id));
147
+ }) })) }), _jsx(TabsContent, { value: "new", className: "flex-1 overflow-y-auto mt-4", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "bug-title", children: "Titel" }), _jsx(Input, { id: "bug-title", placeholder: "Korte beschrijving van de bug...", value: title, onChange: (e) => setTitle(e.target.value), maxLength: 200 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "bug-description", children: "Beschrijving" }), _jsx(Textarea, { id: "bug-description", placeholder: "Beschrijf de bug in detail. Wat ging er mis? Wat verwachtte je?", value: description, onChange: (e) => setDescription(e.target.value), rows: 4, maxLength: 5000 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { children: "Bijlage (optioneel)" }), _jsx("input", { type: "file", ref: fileInputRef, onChange: handleFileChange, className: "hidden", accept: acceptString }), attachment ? (_jsxs("div", { className: "flex items-center gap-2 p-2 border rounded-md bg-muted/50", children: [_jsx(FileText, { className: "h-4 w-4 text-muted-foreground" }), _jsx("span", { className: "text-sm flex-1 truncate", children: attachment.name }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", className: "h-6 w-6", onClick: removeAttachment, children: _jsx(X, { className: "h-4 w-4" }) })] })) : (_jsxs(Button, { type: "button", variant: "outline", className: "w-full", onClick: () => fileInputRef.current?.click(), children: [_jsx(Paperclip, { className: "mr-2 h-4 w-4" }), "Document toevoegen (max 10MB)"] })), _jsx("p", { className: "text-xs text-muted-foreground", children: "Toegestane bestandstypen: PNG, JPEG, GIF, PDF" })] }), _jsx(Button, { onClick: handleSubmit, disabled: submitting || !title.trim() || !description.trim(), className: "w-full", children: submitting ? (_jsxs(_Fragment, { children: [_jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Melden..."] })) : (_jsxs(_Fragment, { children: [_jsx(CheckCircle, { className: "mr-2 h-4 w-4" }), "Bug Melden"] })) })] }) })] })] }) }));
148
+ }
@@ -0,0 +1,6 @@
1
+ interface HeaderProps {
2
+ title: string;
3
+ }
4
+ export declare function Header({ title }: HeaderProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=Header.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/Header.tsx"],"names":[],"mappings":"AAOA,UAAU,WAAW;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,wBAAgB,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,WAAW,2CAsB5C"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Bug, Search } from "lucide-react";
4
+ import { Input } from "../ui/input";
5
+ import { Button } from "../ui/button";
6
+ import { BugsDialog } from "./BugsDialog";
7
+ import { NotificationBell } from "./NotificationBell";
8
+ export function Header({ title }) {
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 })] }));
11
+ }
@@ -0,0 +1,5 @@
1
+ import type { FlowModule } from "../../types/module";
2
+ export declare function InstellingenPage({ modules }: {
3
+ modules: FlowModule[];
4
+ }): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=InstellingenPage.d.ts.map
@@ -0,0 +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;AAsInE,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,EAAE;IAAE,OAAO,EAAE,UAAU,EAAE,CAAA;CAAE,2CActE"}
@@ -0,0 +1,79 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { Header } from "./Header";
3
+ import { useState } from "react";
4
+ import { Button } from "../ui/button";
5
+ import { Input } from "../ui/input";
6
+ import { Label } from "../ui/label";
7
+ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
8
+ import { supabase } from "../../supabase/client";
9
+ import { toast } from "sonner";
10
+ import { Loader2, User } from "lucide-react";
11
+ import { NotificationSettingsCard } from "../instellingen/NotificationSettingsCard";
12
+ // ─── Profile Card ─────────────────────────────────────────────────────────────
13
+ function ProfileCard() {
14
+ const [displayName, setDisplayName] = useState("");
15
+ const [saving, setSaving] = useState(false);
16
+ const handleUpdateProfile = async () => {
17
+ setSaving(true);
18
+ try {
19
+ const { data: { user } } = await supabase.auth.getUser();
20
+ if (!user)
21
+ throw new Error("Niet ingelogd");
22
+ const { error } = await supabase.from("profiles").update({ display_name: displayName.trim() }).eq("id", user.id);
23
+ if (error)
24
+ throw error;
25
+ toast.success("Profiel bijgewerkt");
26
+ }
27
+ catch {
28
+ toast.error("Fout bij het bijwerken van profiel");
29
+ }
30
+ finally {
31
+ setSaving(false);
32
+ }
33
+ };
34
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx(User, { className: "h-5 w-5 text-primary" }), _jsx(CardTitle, { children: "Profielgegevens" })] }), _jsx(CardDescription, { children: "Werk je persoonlijke gegevens bij" })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "displayName", children: "Weergavenaam" }), _jsx(Input, { id: "displayName", value: displayName, onChange: (e) => setDisplayName(e.target.value), maxLength: 100, placeholder: "Je naam" })] }), _jsxs(Button, { onClick: handleUpdateProfile, disabled: saving, children: [saving && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Opslaan"] })] })] }));
35
+ }
36
+ // ─── Password Card ────────────────────────────────────────────────────────────
37
+ function PasswordCard() {
38
+ const [newPassword, setNewPassword] = useState("");
39
+ const [confirmPassword, setConfirmPassword] = useState("");
40
+ const [changingPassword, setChangingPassword] = useState(false);
41
+ const handleChangePassword = async () => {
42
+ if (newPassword.length < 6) {
43
+ toast.error("Wachtwoord moet minimaal 6 tekens bevatten");
44
+ return;
45
+ }
46
+ if (newPassword !== confirmPassword) {
47
+ toast.error("Wachtwoorden komen niet overeen");
48
+ return;
49
+ }
50
+ setChangingPassword(true);
51
+ try {
52
+ const { error } = await supabase.auth.updateUser({ password: newPassword });
53
+ if (error)
54
+ throw error;
55
+ toast.success("Wachtwoord bijgewerkt");
56
+ setNewPassword("");
57
+ setConfirmPassword("");
58
+ }
59
+ catch {
60
+ toast.error("Fout bij het wijzigen van wachtwoord");
61
+ }
62
+ finally {
63
+ setChangingPassword(false);
64
+ }
65
+ };
66
+ return (_jsxs(Card, { children: [_jsxs(CardHeader, { children: [_jsx(CardTitle, { children: "Wachtwoord wijzigen" }), _jsx(CardDescription, { children: "Kies een nieuw wachtwoord" })] }), _jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "newPassword", children: "Nieuw wachtwoord" }), _jsx(Input, { id: "newPassword", type: "password", value: newPassword, onChange: (e) => setNewPassword(e.target.value), maxLength: 128, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _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), maxLength: 128, placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022" })] }), _jsxs(Button, { onClick: handleChangePassword, disabled: changingPassword, children: [changingPassword && _jsx(Loader2, { className: "mr-2 h-4 w-4 animate-spin" }), "Wachtwoord wijzigen"] })] })] }));
67
+ }
68
+ // ─── Core cards registry ──────────────────────────────────────────────────────
69
+ const coreCards = [
70
+ { component: ProfileCard, order: 0 },
71
+ { component: PasswordCard, order: 1 },
72
+ { component: NotificationSettingsCard, order: 2 },
73
+ ];
74
+ // ─── Main export ──────────────────────────────────────────────────────────────
75
+ export function InstellingenPage({ modules }) {
76
+ const moduleCards = modules.flatMap((m) => m.settingsCards);
77
+ const allCards = [...coreCards, ...moduleCards].sort((a, b) => a.order - b.order);
78
+ return (_jsxs(_Fragment, { children: [_jsx(Header, { title: "Instellingen" }), _jsx("main", { className: "p-6", children: _jsx("div", { className: "grid gap-6 lg:grid-cols-2", children: allCards.map((card, i) => _jsx(card.component, {}, i)) }) })] }));
79
+ }
@@ -0,0 +1,6 @@
1
+ interface LoginPageProps {
2
+ redirectTo: string;
3
+ }
4
+ export declare function LoginPage({ redirectTo }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=LoginPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"LoginPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/LoginPage.tsx"],"names":[],"mappings":"AASA,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,SAAS,CAAC,EAAE,UAAU,EAAE,EAAE,cAAc,2CA4GvD"}
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { Button } from "../ui/button";
4
+ import { Input } from "../ui/input";
5
+ import { Label } from "../ui/label";
6
+ import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "../ui/card";
7
+ import { toast } from "sonner";
8
+ import { loginSchema } from "../../lib/validationSchemas";
9
+ import { supabase } from "../../supabase/client";
10
+ export function LoginPage({ redirectTo }) {
11
+ const [email, setEmail] = useState("");
12
+ const [password, setPassword] = useState("");
13
+ const [isLoading, setIsLoading] = useState(false);
14
+ const [showForgot, setShowForgot] = useState(false);
15
+ const [forgotEmail, setForgotEmail] = useState("");
16
+ const [forgotLoading, setForgotLoading] = useState(false);
17
+ const handleSubmit = async (e) => {
18
+ e.preventDefault();
19
+ const validation = loginSchema.safeParse({ email, password });
20
+ if (!validation.success) {
21
+ toast.error(validation.error.issues[0].message);
22
+ return;
23
+ }
24
+ setIsLoading(true);
25
+ const { error } = await supabase.auth.signInWithPassword({ email, password });
26
+ setIsLoading(false);
27
+ if (error) {
28
+ toast.error("Controleer je e-mailadres en wachtwoord.");
29
+ }
30
+ else {
31
+ window.location.href = redirectTo;
32
+ }
33
+ };
34
+ const handleForgotPassword = async (e) => {
35
+ e.preventDefault();
36
+ if (!forgotEmail) {
37
+ toast.error("Vul je e-mailadres in");
38
+ return;
39
+ }
40
+ setForgotLoading(true);
41
+ const { error } = await supabase.auth.resetPasswordForEmail(forgotEmail, {
42
+ redirectTo: `${window.location.origin}/reset-wachtwoord`,
43
+ });
44
+ setForgotLoading(false);
45
+ if (error) {
46
+ toast.error("Fout bij het versturen van de reset-link");
47
+ }
48
+ else {
49
+ toast.success("Reset-link verstuurd! Controleer je e-mail.");
50
+ setShowForgot(false);
51
+ }
52
+ };
53
+ if (showForgot) {
54
+ 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: "Wachtwoord vergeten" }), _jsx(CardDescription, { children: "Vul je e-mailadres in om een reset-link te ontvangen" })] }), _jsxs("form", { onSubmit: handleForgotPassword, children: [_jsx(CardContent, { className: "space-y-4", children: _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "forgot-email", children: "E-mailadres" }), _jsx(Input, { id: "forgot-email", type: "email", placeholder: "naam@bedrijf.nl", value: forgotEmail, onChange: (e) => setForgotEmail(e.target.value), required: true, maxLength: 255 })] }) }), _jsxs(CardFooter, { className: "flex flex-col gap-4", children: [_jsx(Button, { type: "submit", className: "w-full", disabled: forgotLoading, children: forgotLoading ? "Versturen..." : "Reset-link versturen" }), _jsx("button", { type: "button", onClick: () => setShowForgot(false), className: "text-sm text-primary hover:underline", children: "Terug naar inloggen" })] })] })] }) }));
55
+ }
56
+ 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: "Inloggen" }), _jsx(CardDescription, { children: "Voer je gegevens in om toegang te krijgen tot het systeem" })] }), _jsxs("form", { onSubmit: handleSubmit, children: [_jsxs(CardContent, { className: "space-y-4", children: [_jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "email", children: "E-mailadres" }), _jsx(Input, { id: "email", type: "email", placeholder: "naam@bedrijf.nl", value: email, onChange: (e) => setEmail(e.target.value), required: true, maxLength: 255 })] }), _jsxs("div", { className: "space-y-2", children: [_jsx(Label, { htmlFor: "password", children: "Wachtwoord" }), _jsx(Input, { id: "password", placeholder: "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022", type: "password", value: password, onChange: (e) => setPassword(e.target.value), required: true, maxLength: 128 })] })] }), _jsxs(CardFooter, { className: "flex flex-col gap-4", children: [_jsx(Button, { type: "submit", className: "w-full", disabled: isLoading, children: isLoading ? "Bezig met inloggen..." : "Inloggen" }), _jsx("button", { type: "button", onClick: () => setShowForgot(true), className: "text-sm text-muted-foreground hover:text-primary hover:underline", children: "Wachtwoord vergeten?" })] })] })] }) }));
57
+ }
@@ -0,0 +1,2 @@
1
+ export declare function NotificationBell(): import("react/jsx-runtime").JSX.Element;
2
+ //# sourceMappingURL=NotificationBell.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotificationBell.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/NotificationBell.tsx"],"names":[],"mappings":"AAeA,wBAAgB,gBAAgB,4CA2F/B"}
@@ -0,0 +1,45 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState, useEffect } from "react";
3
+ import { Bell } from "lucide-react";
4
+ import { Button } from "../ui/button";
5
+ import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover";
6
+ import { supabase } from "../../supabase/client";
7
+ import { cn } from "../../lib/utils";
8
+ export function NotificationBell() {
9
+ const [notifications, setNotifications] = useState([]);
10
+ const [open, setOpen] = useState(false);
11
+ const unreadCount = notifications.filter((n) => !n.read).length;
12
+ const fetchNotifications = async () => {
13
+ const { data } = await supabase
14
+ .from("notifications")
15
+ .select("*")
16
+ .order("created_at", { ascending: false })
17
+ .limit(20);
18
+ if (data)
19
+ setNotifications(data);
20
+ };
21
+ useEffect(() => {
22
+ fetchNotifications();
23
+ const channel = supabase
24
+ .channel("notifications")
25
+ .on("postgres_changes", { event: "*", schema: "public", table: "notifications" }, () => {
26
+ fetchNotifications();
27
+ })
28
+ .subscribe();
29
+ return () => { supabase.removeChannel(channel); };
30
+ }, []);
31
+ const markAsRead = async (id) => {
32
+ await supabase.from("notifications").update({ read: true }).eq("id", id);
33
+ setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)));
34
+ };
35
+ const dismissNotification = async (id) => {
36
+ await supabase.from("notifications").delete().eq("id", id);
37
+ setNotifications((prev) => prev.filter((n) => n.id !== id));
38
+ };
39
+ const formatDate = (dateStr) => {
40
+ return new Date(dateStr).toLocaleDateString("nl-NL", {
41
+ day: "2-digit", month: "2-digit", hour: "2-digit", minute: "2-digit",
42
+ });
43
+ };
44
+ return (_jsxs(Popover, { open: open, onOpenChange: setOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { variant: "ghost", size: "icon", className: "relative", children: [_jsx(Bell, { className: "h-5 w-5" }), unreadCount > 0 && (_jsx("span", { className: "absolute -top-1 -right-1 flex h-5 w-5 items-center justify-center rounded-full bg-destructive text-[10px] font-bold text-destructive-foreground", children: unreadCount > 9 ? "9+" : unreadCount }))] }) }), _jsxs(PopoverContent, { className: "w-80 p-0", align: "end", children: [_jsx("div", { className: "border-b p-3", children: _jsx("h4", { className: "font-semibold text-sm", children: "Notificaties" }) }), _jsx("div", { className: "max-h-80 overflow-y-auto", children: notifications.length === 0 ? (_jsx("div", { className: "p-4 text-center text-sm text-muted-foreground", children: "Geen notificaties" })) : (notifications.map((n) => (_jsxs("div", { className: cn("flex items-start gap-2 border-b p-3 cursor-pointer hover:bg-muted/50 transition-colors", !n.read && "bg-accent/30"), onClick: () => markAsRead(n.id), children: [_jsxs("div", { className: "flex-1 min-w-0", children: [_jsx("p", { className: cn("text-sm", !n.read && "font-medium"), children: n.title }), n.message && _jsx("p", { className: "text-xs text-muted-foreground mt-0.5 truncate", children: n.message }), _jsx("p", { className: "text-xs text-muted-foreground mt-1", children: formatDate(n.created_at) })] }), _jsx(Button, { variant: "ghost", size: "icon", className: "h-6 w-6 shrink-0", onClick: (e) => { e.stopPropagation(); dismissNotification(n.id); }, children: _jsx("span", { className: "text-xs", children: "\u2715" }) })] }, n.id)))) })] })] }));
45
+ }
@@ -0,0 +1,6 @@
1
+ interface ResetWachtwoordPageProps {
2
+ redirectTo: string;
3
+ }
4
+ export declare function ResetWachtwoordPage({ redirectTo }: ResetWachtwoordPageProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
6
+ //# sourceMappingURL=ResetWachtwoordPage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ResetWachtwoordPage.d.ts","sourceRoot":"","sources":["../../../src/core/components/layout/ResetWachtwoordPage.tsx"],"names":[],"mappings":"AAQA,UAAU,wBAAwB;IAChC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,CAAC,EAAE,UAAU,EAAE,EAAE,wBAAwB,2CAuF3E"}