@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.
- package/dist/auth/useAuth.d.ts +28 -0
- package/dist/auth/useAuth.d.ts.map +1 -0
- package/dist/auth/useAuth.js +72 -0
- package/dist/components/instellingen/NotificationSettingsCard.d.ts +2 -0
- package/dist/components/instellingen/NotificationSettingsCard.d.ts.map +1 -0
- package/dist/components/instellingen/NotificationSettingsCard.js +63 -0
- package/dist/components/layout/BugsDialog.d.ts +7 -0
- package/dist/components/layout/BugsDialog.d.ts.map +1 -0
- package/dist/components/layout/BugsDialog.js +148 -0
- package/dist/components/layout/Header.d.ts +6 -0
- package/dist/components/layout/Header.d.ts.map +1 -0
- package/dist/components/layout/Header.js +11 -0
- package/dist/components/layout/InstellingenPage.d.ts +5 -0
- package/dist/components/layout/InstellingenPage.d.ts.map +1 -0
- package/dist/components/layout/InstellingenPage.js +79 -0
- package/dist/components/layout/LoginPage.d.ts +6 -0
- package/dist/components/layout/LoginPage.d.ts.map +1 -0
- package/dist/components/layout/LoginPage.js +57 -0
- package/dist/components/layout/NotificationBell.d.ts +2 -0
- package/dist/components/layout/NotificationBell.d.ts.map +1 -0
- package/dist/components/layout/NotificationBell.js +45 -0
- package/dist/components/layout/ResetWachtwoordPage.d.ts +6 -0
- package/dist/components/layout/ResetWachtwoordPage.d.ts.map +1 -0
- package/dist/components/layout/ResetWachtwoordPage.js +51 -0
- package/dist/components/layout/Sidebar.d.ts +9 -0
- package/dist/components/layout/Sidebar.d.ts.map +1 -0
- package/dist/components/layout/Sidebar.js +32 -0
- package/dist/components/ui/accordion.d.ts +8 -0
- package/dist/components/ui/accordion.d.ts.map +1 -0
- package/dist/components/ui/accordion.js +13 -0
- package/dist/components/ui/alert-dialog.d.ts +21 -0
- package/dist/components/ui/alert-dialog.d.ts.map +1 -0
- package/dist/components/ui/alert-dialog.js +26 -0
- package/dist/components/ui/alert.d.ts +9 -0
- package/dist/components/ui/alert.d.ts.map +1 -0
- package/dist/components/ui/alert.js +22 -0
- package/dist/components/ui/aspect-ratio.d.ts +4 -0
- package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
- package/dist/components/ui/aspect-ratio.js +3 -0
- package/dist/components/ui/avatar.d.ts +7 -0
- package/dist/components/ui/avatar.d.ts.map +1 -0
- package/dist/components/ui/avatar.js +12 -0
- package/dist/components/ui/badge.d.ts +10 -0
- package/dist/components/ui/badge.d.ts.map +1 -0
- package/dist/components/ui/badge.js +20 -0
- package/dist/components/ui/breadcrumb.d.ts +20 -0
- package/dist/components/ui/breadcrumb.d.ts.map +1 -0
- package/dist/components/ui/breadcrumb.js +23 -0
- package/dist/components/ui/button.d.ts +12 -0
- package/dist/components/ui/button.d.ts.map +1 -0
- package/dist/components/ui/button.js +33 -0
- package/dist/components/ui/calendar.d.ts +9 -0
- package/dist/components/ui/calendar.d.ts.map +1 -0
- package/dist/components/ui/calendar.js +74 -0
- package/dist/components/ui/card.d.ts +9 -0
- package/dist/components/ui/card.d.ts.map +1 -0
- package/dist/components/ui/card.js +16 -0
- package/dist/components/ui/carousel.d.ts +19 -0
- package/dist/components/ui/carousel.d.ts.map +1 -0
- package/dist/components/ui/carousel.js +98 -0
- package/dist/components/ui/chart.d.ts +63 -0
- package/dist/components/ui/chart.d.ts.map +1 -0
- package/dist/components/ui/chart.js +134 -0
- package/dist/components/ui/checkbox.d.ts +5 -0
- package/dist/components/ui/checkbox.d.ts.map +1 -0
- package/dist/components/ui/checkbox.js +8 -0
- package/dist/components/ui/collapsible.d.ts +6 -0
- package/dist/components/ui/collapsible.d.ts.map +1 -0
- package/dist/components/ui/collapsible.js +6 -0
- package/dist/components/ui/command.d.ts +81 -0
- package/dist/components/ui/command.d.ts.map +1 -0
- package/dist/components/ui/command.js +29 -0
- package/dist/components/ui/context-menu.d.ts +28 -0
- package/dist/components/ui/context-menu.d.ts.map +1 -0
- package/dist/components/ui/context-menu.js +33 -0
- package/dist/components/ui/dialog.d.ts +20 -0
- package/dist/components/ui/dialog.d.ts.map +1 -0
- package/dist/components/ui/dialog.js +23 -0
- package/dist/components/ui/drawer.d.ts +23 -0
- package/dist/components/ui/drawer.d.ts.map +1 -0
- package/dist/components/ui/drawer.js +22 -0
- package/dist/components/ui/dropdown-menu.d.ts +28 -0
- package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/components/ui/dropdown-menu.js +36 -0
- package/dist/components/ui/form.d.ts +24 -0
- package/dist/components/ui/form.d.ts.map +1 -0
- package/dist/components/ui/form.js +65 -0
- package/dist/components/ui/hover-card.d.ts +7 -0
- package/dist/components/ui/hover-card.d.ts.map +1 -0
- package/dist/components/ui/hover-card.js +9 -0
- package/dist/components/ui/icon-picker.d.ts +15 -0
- package/dist/components/ui/icon-picker.d.ts.map +1 -0
- package/dist/components/ui/icon-picker.js +73 -0
- package/dist/components/ui/input-otp.d.ts +35 -0
- package/dist/components/ui/input-otp.d.ts.map +1 -0
- package/dist/components/ui/input-otp.js +18 -0
- package/dist/components/ui/input.d.ts +4 -0
- package/dist/components/ui/input.d.ts.map +1 -0
- package/dist/components/ui/input.js +8 -0
- package/dist/components/ui/label.d.ts +6 -0
- package/dist/components/ui/label.d.ts.map +1 -0
- package/dist/components/ui/label.js +10 -0
- package/dist/components/ui/menubar.d.ts +29 -0
- package/dist/components/ui/menubar.d.ts.map +1 -0
- package/dist/components/ui/menubar.js +45 -0
- package/dist/components/ui/navigation-menu.d.ts +13 -0
- package/dist/components/ui/navigation-menu.d.ts.map +1 -0
- package/dist/components/ui/navigation-menu.js +24 -0
- package/dist/components/ui/pagination.d.ts +29 -0
- package/dist/components/ui/pagination.d.ts.map +1 -0
- package/dist/components/ui/pagination.js +23 -0
- package/dist/components/ui/popover.d.ts +8 -0
- package/dist/components/ui/popover.d.ts.map +1 -0
- package/dist/components/ui/popover.js +10 -0
- package/dist/components/ui/progress.d.ts +5 -0
- package/dist/components/ui/progress.d.ts.map +1 -0
- package/dist/components/ui/progress.js +8 -0
- package/dist/components/ui/radio-group.d.ts +6 -0
- package/dist/components/ui/radio-group.d.ts.map +1 -0
- package/dist/components/ui/radio-group.js +14 -0
- package/dist/components/ui/resizable.d.ts +8 -0
- package/dist/components/ui/resizable.d.ts.map +1 -0
- package/dist/components/ui/resizable.js +8 -0
- package/dist/components/ui/scroll-area.d.ts +6 -0
- package/dist/components/ui/scroll-area.d.ts.map +1 -0
- package/dist/components/ui/scroll-area.js +11 -0
- package/dist/components/ui/select.d.ts +14 -0
- package/dist/components/ui/select.d.ts.map +1 -0
- package/dist/components/ui/select.js +27 -0
- package/dist/components/ui/separator.d.ts +5 -0
- package/dist/components/ui/separator.d.ts.map +1 -0
- package/dist/components/ui/separator.js +7 -0
- package/dist/components/ui/sheet.d.ts +26 -0
- package/dist/components/ui/sheet.d.ts.map +1 -0
- package/dist/components/ui/sheet.js +37 -0
- package/dist/components/ui/sidebar.d.ts +66 -0
- package/dist/components/ui/sidebar.d.ts.map +1 -0
- package/dist/components/ui/sidebar.js +224 -0
- package/dist/components/ui/skeleton.d.ts +3 -0
- package/dist/components/ui/skeleton.d.ts.map +1 -0
- package/dist/components/ui/skeleton.js +6 -0
- package/dist/components/ui/slider.d.ts +5 -0
- package/dist/components/ui/slider.d.ts.map +1 -0
- package/dist/components/ui/slider.js +7 -0
- package/dist/components/ui/sonner.d.ts +5 -0
- package/dist/components/ui/sonner.d.ts.map +1 -0
- package/dist/components/ui/sonner.js +13 -0
- package/dist/components/ui/switch.d.ts +5 -0
- package/dist/components/ui/switch.d.ts.map +1 -0
- package/dist/components/ui/switch.js +7 -0
- package/dist/components/ui/table.d.ts +11 -0
- package/dist/components/ui/table.d.ts.map +1 -0
- package/dist/components/ui/table.js +20 -0
- package/dist/components/ui/tabs.d.ts +8 -0
- package/dist/components/ui/tabs.d.ts.map +1 -0
- package/dist/components/ui/tabs.js +12 -0
- package/dist/components/ui/textarea.d.ts +4 -0
- package/dist/components/ui/textarea.d.ts.map +1 -0
- package/dist/components/ui/textarea.js +8 -0
- package/dist/components/ui/toggle-group.d.ts +13 -0
- package/dist/components/ui/toggle-group.d.ts.map +1 -0
- package/dist/components/ui/toggle-group.js +21 -0
- package/dist/components/ui/toggle.d.ts +13 -0
- package/dist/components/ui/toggle.d.ts.map +1 -0
- package/dist/components/ui/toggle.js +25 -0
- package/dist/components/ui/tooltip.d.ts +8 -0
- package/dist/components/ui/tooltip.d.ts.map +1 -0
- package/dist/components/ui/tooltip.js +11 -0
- package/dist/hooks/use-mobile.d.ts +2 -0
- package/dist/hooks/use-mobile.d.ts.map +1 -0
- package/dist/hooks/use-mobile.js +15 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +33 -0
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +5 -0
- package/dist/lib/validationSchemas.d.ts +15 -0
- package/dist/lib/validationSchemas.d.ts.map +1 -0
- package/dist/lib/validationSchemas.js +25 -0
- package/dist/supabase/auth-middleware.d.ts +639 -0
- package/dist/supabase/auth-middleware.d.ts.map +1 -0
- package/dist/supabase/auth-middleware.js +52 -0
- package/dist/supabase/client.d.ts +635 -0
- package/dist/supabase/client.d.ts.map +1 -0
- package/dist/supabase/client.js +13 -0
- package/dist/supabase/client.server.d.ts +635 -0
- package/dist/supabase/client.server.d.ts.map +1 -0
- package/dist/supabase/client.server.js +30 -0
- package/dist/supabase/types.d.ts +717 -0
- package/dist/supabase/types.d.ts.map +1 -0
- package/dist/supabase/types.js +7 -0
- package/dist/types/module.d.ts +20 -0
- package/dist/types/module.d.ts.map +1 -0
- package/dist/types/module.js +1 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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"}
|