diff --git a/src/components/layout/nav/Login.tsx b/src/components/layout/nav/Login.tsx
deleted file mode 100644
index fc1ee6e..0000000
--- a/src/components/layout/nav/Login.tsx
+++ /dev/null
@@ -1,93 +0,0 @@
-import {IconExclamationCircle, IconLogin} from "@tabler/icons-react";
-import {ActionIcon, Alert, Anchor, Button, Checkbox, Modal, PasswordInput, Text, TextInput, Title} from "@mantine/core";
-import {useDisclosure} from "@mantine/hooks";
-import {usePB} from "../../../lib/pocketbase.tsx";
-import {useForm} from "@mantine/form";
-import {useMutation} from "@tanstack/react-query";
-import classes from "./index.module.css";
-import {Link} from "react-router-dom";
-
-/**
- * This component renders a login button and a login modal.
- */
-export default function Login() {
- const [opened, {open, close}] = useDisclosure(false)
-
- const {ldapLogin} = usePB()
-
- const formValues = useForm({
- initialValues: {
- username: "",
- password: "",
- privacy: false
- }
- })
-
- const loginMutation = useMutation({
- mutationFn: async () => {
- await ldapLogin(formValues.values.username, formValues.values.password)
- }
- })
-
- return <>
-
-
-
-
-
-
-
- >
-}
\ No newline at end of file
diff --git a/src/components/layout/nav/MenuItems.tsx b/src/components/layout/nav/MenuItems.tsx
index f6b8245..d88abc8 100644
--- a/src/components/layout/nav/MenuItems.tsx
+++ b/src/components/layout/nav/MenuItems.tsx
@@ -1,14 +1,38 @@
-import {Menu, rem} from "@mantine/core";
-import {NAV_ITEMS} from "../../../../config.ts";
+import {Menu} from "@mantine/core";
import {NavLink} from "react-router-dom";
import {Fragment} from "react";
+import {IconConfetti, IconHome, IconQrcode} from "@tabler/icons-react";
+const NavItems = [
+ {
+ section: "Seiten",
+ items: [
+ {
+ title: "Home",
+ icon: IconHome,
+ description: "Home",
+ link: "/"
+ },
+ {
+ title: "Events",
+ icon: IconConfetti,
+ description: "Administration für StuVe Events.",
+ link: "/events"
+ },
+ {
+ title: "QR Code Generator",
+ icon: IconQrcode,
+ description: "Generiere einen QR Code",
+ link: "/util/qr"
+ }
+ ]
+ },
+]
+
export default function MenuItems() {
-
-
return <>
- {NAV_ITEMS.map((section, index) => (
+ {NavItems.map((section, index) => (
{section.section}
@@ -18,7 +42,7 @@ export default function MenuItems() {
return (
}
+ leftSection={}
component={NavLink}
to={item.link}
aria-label={item.description}
diff --git a/src/components/layout/nav/UserMenu.tsx b/src/components/layout/nav/UserMenu.tsx
deleted file mode 100644
index 2612f17..0000000
--- a/src/components/layout/nav/UserMenu.tsx
+++ /dev/null
@@ -1,107 +0,0 @@
-import {ActionIcon, Button, Code, Divider, Modal, Text, ThemeIcon, Title} from "@mantine/core";
-import {IconBalloon, IconCalendar, IconId, IconLogout, IconServer, IconServerOff} from "@tabler/icons-react";
-import {useDisclosure} from "@mantine/hooks";
-import {usePB} from "../../../lib/pocketbase.tsx";
-import classes from "./index.module.css";
-import LdapGroupsDisplay from "../../auth/LdapGroupsDisplay.tsx";
-
-/**
- * This component renders a user menu button and a user menu modal with user information.
- */
-export default function UserMenu() {
-
- const [opened, {open, close}] = useDisclosure(false)
-
- const {user, logout, apiIsHealthy} = usePB()
-
- return <>
-
-
-
Hallo {user!.username}
-
-
-
-
-
-
- {user?.id}
-
-
-
-
-
-
-
-
- {user?.accountExpires ? (
-
- new Date(user?.accountExpires).getTime() > Date.now() ? (
- "Account ist aktiv und läuft am " + new Date(user?.accountExpires).toLocaleDateString() + " ab"
- ) : (
- "Account ist abgelaufen"
- )
- ) : (
- "Dein Account läuft nicht ab"
- )}
-
-
-
-
- {apiIsHealthy ? (
-
-
-
-
- ) : (
-
-
-
- )}
-
-
- {apiIsHealthy ? "Das Backend ist erreichbar" : "Das Backend ist nicht erreichbar"}
-
-
-
- {user?.memberOf.length && <>
-
-
Deine Gruppen
-
-
- >}
-
-
- }
- color={"orange"}
- onClick={logout}
- >
- Ausloggen
-
-
-
-
-
-
-
- >
-}
\ No newline at end of file
diff --git a/src/components/layout/nav/index.tsx b/src/components/layout/nav/index.tsx
index f92f7e5..be79591 100644
--- a/src/components/layout/nav/index.tsx
+++ b/src/components/layout/nav/index.tsx
@@ -1,17 +1,19 @@
-import {usePB} from "../../../lib/pocketbase.tsx";
+import {usePB} from "@/lib/pocketbase.tsx";
import classes from "./index.module.css";
import {ActionIcon, Image, Menu, ThemeIcon, useMantineColorScheme} from "@mantine/core";
-import {IconChevronDown, IconMoon, IconSun} from "@tabler/icons-react";
-import UserMenu from "./UserMenu.tsx";
-import Login from "./Login.tsx";
+import {IconChevronDown, IconLogin, IconMoon, IconSun, IconUserStar} from "@tabler/icons-react";
import MenuItems from "./MenuItems.tsx";
+import {useLogin, useUserMenu} from "@/components/auth/modals/hooks.ts";
export default function NavBar() {
- const {user} = usePB()
+ const {userRecord} = usePB()
const {colorScheme, toggleColorScheme} = useMantineColorScheme()
+ const {handler: userMenuHandler} = useUserMenu()
+ const {handler: loginHandler} = useLogin()
+
return
@@ -56,7 +58,25 @@ export default function NavBar() {
}
- {user ? : }
+ {userRecord ?
+
+
+
+ : (
+
+
+
+ )}
}
\ No newline at end of file
diff --git a/src/components/util.tsx b/src/components/util.tsx
new file mode 100644
index 0000000..5f3ee9b
--- /dev/null
+++ b/src/components/util.tsx
@@ -0,0 +1,20 @@
+import {notifications} from "@mantine/notifications";
+import {IconAlertTriangle, IconCheck} from "@tabler/icons-react";
+
+export const showSuccessNotification = (message?: string) => {
+ notifications.show({
+ title: "Erfolg",
+ message: message ?? "Die Aktion wurde erfolgreich durchgeführt",
+ color: "green",
+ icon:
,
+ })
+}
+
+export const showErrorNotification = (message?: string) => {
+ notifications.show({
+ title: "Fehler",
+ message: message ?? "Die Aktion wurde konnte nicht durchgeführt werden",
+ color: "red",
+ icon:
,
+ })
+}
\ No newline at end of file
diff --git a/src/illustrations/ai.svg b/src/illustrations/ai.svg
new file mode 100644
index 0000000..fdef1a9
--- /dev/null
+++ b/src/illustrations/ai.svg
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/bad-news.svg b/src/illustrations/bad-news.svg
new file mode 100644
index 0000000..d61e574
--- /dev/null
+++ b/src/illustrations/bad-news.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/bicycle.svg b/src/illustrations/bicycle.svg
new file mode 100644
index 0000000..651294d
--- /dev/null
+++ b/src/illustrations/bicycle.svg
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy-and-cat.svg b/src/illustrations/boy-and-cat.svg
new file mode 100644
index 0000000..80ceb3c
--- /dev/null
+++ b/src/illustrations/boy-and-cat.svg
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy-and-laptop.svg b/src/illustrations/boy-and-laptop.svg
new file mode 100644
index 0000000..432dce6
--- /dev/null
+++ b/src/illustrations/boy-and-laptop.svg
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy-girl.svg b/src/illustrations/boy-girl.svg
new file mode 100644
index 0000000..e6abca4
--- /dev/null
+++ b/src/illustrations/boy-girl.svg
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy-gives-flowers.svg b/src/illustrations/boy-gives-flowers.svg
new file mode 100644
index 0000000..43e9641
--- /dev/null
+++ b/src/illustrations/boy-gives-flowers.svg
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy-with-key.svg b/src/illustrations/boy-with-key.svg
new file mode 100644
index 0000000..b42d1a6
--- /dev/null
+++ b/src/illustrations/boy-with-key.svg
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/boy.svg b/src/illustrations/boy.svg
new file mode 100644
index 0000000..1ce7b29
--- /dev/null
+++ b/src/illustrations/boy.svg
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/building.svg b/src/illustrations/building.svg
new file mode 100644
index 0000000..8f50609
--- /dev/null
+++ b/src/illustrations/building.svg
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/calendar.svg b/src/illustrations/calendar.svg
new file mode 100644
index 0000000..4b99d00
--- /dev/null
+++ b/src/illustrations/calendar.svg
@@ -0,0 +1,65 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/chart-circle.svg b/src/illustrations/chart-circle.svg
new file mode 100644
index 0000000..37cd4dc
--- /dev/null
+++ b/src/illustrations/chart-circle.svg
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/chart.svg b/src/illustrations/chart.svg
new file mode 100644
index 0000000..bd5c6d2
--- /dev/null
+++ b/src/illustrations/chart.svg
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/clock-and-cat.svg b/src/illustrations/clock-and-cat.svg
new file mode 100644
index 0000000..3c648b9
--- /dev/null
+++ b/src/illustrations/clock-and-cat.svg
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/computer-fix.svg b/src/illustrations/computer-fix.svg
new file mode 100644
index 0000000..46cebc1
--- /dev/null
+++ b/src/illustrations/computer-fix.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/conversation.svg b/src/illustrations/conversation.svg
new file mode 100644
index 0000000..9f00cc1
--- /dev/null
+++ b/src/illustrations/conversation.svg
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/dance.svg b/src/illustrations/dance.svg
new file mode 100644
index 0000000..7f5c164
--- /dev/null
+++ b/src/illustrations/dance.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/dart.svg b/src/illustrations/dart.svg
new file mode 100644
index 0000000..dd8ff49
--- /dev/null
+++ b/src/illustrations/dart.svg
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/email.svg b/src/illustrations/email.svg
new file mode 100644
index 0000000..000aa82
--- /dev/null
+++ b/src/illustrations/email.svg
@@ -0,0 +1,67 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/error.svg b/src/illustrations/error.svg
new file mode 100644
index 0000000..b65aa5c
--- /dev/null
+++ b/src/illustrations/error.svg
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/exit.svg b/src/illustrations/exit.svg
new file mode 100644
index 0000000..66cb95e
--- /dev/null
+++ b/src/illustrations/exit.svg
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/fingerprint.svg b/src/illustrations/fingerprint.svg
new file mode 100644
index 0000000..bb0c594
--- /dev/null
+++ b/src/illustrations/fingerprint.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/flowers.svg b/src/illustrations/flowers.svg
new file mode 100644
index 0000000..7061e8b
--- /dev/null
+++ b/src/illustrations/flowers.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/folders.svg b/src/illustrations/folders.svg
new file mode 100644
index 0000000..5f8abc6
--- /dev/null
+++ b/src/illustrations/folders.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/gift.svg b/src/illustrations/gift.svg
new file mode 100644
index 0000000..049b54e
--- /dev/null
+++ b/src/illustrations/gift.svg
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/girl-phone.svg b/src/illustrations/girl-phone.svg
new file mode 100644
index 0000000..1f18224
--- /dev/null
+++ b/src/illustrations/girl-phone.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/girl-refresh.svg b/src/illustrations/girl-refresh.svg
new file mode 100644
index 0000000..f308a3d
--- /dev/null
+++ b/src/illustrations/girl-refresh.svg
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/good-news.svg b/src/illustrations/good-news.svg
new file mode 100644
index 0000000..68222be
--- /dev/null
+++ b/src/illustrations/good-news.svg
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/ice-skates.svg b/src/illustrations/ice-skates.svg
new file mode 100644
index 0000000..f5d6906
--- /dev/null
+++ b/src/illustrations/ice-skates.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/icons-drawing.svg b/src/illustrations/icons-drawing.svg
new file mode 100644
index 0000000..393259d
--- /dev/null
+++ b/src/illustrations/icons-drawing.svg
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/icons-ladder.svg b/src/illustrations/icons-ladder.svg
new file mode 100644
index 0000000..ba25158
--- /dev/null
+++ b/src/illustrations/icons-ladder.svg
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/icons-production.svg b/src/illustrations/icons-production.svg
new file mode 100644
index 0000000..609f39b
--- /dev/null
+++ b/src/illustrations/icons-production.svg
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/icons-workshop.svg b/src/illustrations/icons-workshop.svg
new file mode 100644
index 0000000..2761d81
--- /dev/null
+++ b/src/illustrations/icons-workshop.svg
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/icons.svg b/src/illustrations/icons.svg
new file mode 100644
index 0000000..d52350a
--- /dev/null
+++ b/src/illustrations/icons.svg
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/loading.svg b/src/illustrations/loading.svg
new file mode 100644
index 0000000..19c41e8
--- /dev/null
+++ b/src/illustrations/loading.svg
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/map-destination.svg b/src/illustrations/map-destination.svg
new file mode 100644
index 0000000..f19bfca
--- /dev/null
+++ b/src/illustrations/map-destination.svg
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/message.svg b/src/illustrations/message.svg
new file mode 100644
index 0000000..192a26f
--- /dev/null
+++ b/src/illustrations/message.svg
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/mobile-computer.svg b/src/illustrations/mobile-computer.svg
new file mode 100644
index 0000000..46e88e1
--- /dev/null
+++ b/src/illustrations/mobile-computer.svg
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/music.svg b/src/illustrations/music.svg
new file mode 100644
index 0000000..fd8c927
--- /dev/null
+++ b/src/illustrations/music.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/neutral-info.svg b/src/illustrations/neutral-info.svg
new file mode 100644
index 0000000..62ac51a
--- /dev/null
+++ b/src/illustrations/neutral-info.svg
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/not-found.svg b/src/illustrations/not-found.svg
new file mode 100644
index 0000000..d47fa80
--- /dev/null
+++ b/src/illustrations/not-found.svg
@@ -0,0 +1,60 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/project.svg b/src/illustrations/project.svg
new file mode 100644
index 0000000..4acda00
--- /dev/null
+++ b/src/illustrations/project.svg
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/search.svg b/src/illustrations/search.svg
new file mode 100644
index 0000000..e4f0e55
--- /dev/null
+++ b/src/illustrations/search.svg
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/shield.svg b/src/illustrations/shield.svg
new file mode 100644
index 0000000..ee36cdc
--- /dev/null
+++ b/src/illustrations/shield.svg
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/snowman.svg b/src/illustrations/snowman.svg
new file mode 100644
index 0000000..1f3d5c2
--- /dev/null
+++ b/src/illustrations/snowman.svg
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/telescope.svg b/src/illustrations/telescope.svg
new file mode 100644
index 0000000..1518f9f
--- /dev/null
+++ b/src/illustrations/telescope.svg
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/to-do.svg b/src/illustrations/to-do.svg
new file mode 100644
index 0000000..212da67
--- /dev/null
+++ b/src/illustrations/to-do.svg
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/video.svg b/src/illustrations/video.svg
new file mode 100644
index 0000000..030e0d0
--- /dev/null
+++ b/src/illustrations/video.svg
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/wait.svg b/src/illustrations/wait.svg
new file mode 100644
index 0000000..0e8070a
--- /dev/null
+++ b/src/illustrations/wait.svg
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/illustrations/weightlifting.svg b/src/illustrations/weightlifting.svg
new file mode 100644
index 0000000..50df825
--- /dev/null
+++ b/src/illustrations/weightlifting.svg
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/lib/datetime.ts b/src/lib/datetime.ts
index 4f64df3..a6dab0a 100644
--- a/src/lib/datetime.ts
+++ b/src/lib/datetime.ts
@@ -12,23 +12,33 @@ dayjs.extend(duration);
* @param d2 date string
* @return {boolean} True if the dates are the same, false otherwise.
*/
-export const areDatesSame = (d1: string | Date, d2: string | Date) => {
- const date1 = d1 instanceof Date ? d1 : new Date(d1);
- const date2 = d2 instanceof Date ? d2 : new Date(d2);
+export const areDatesSame = (d1: string | Date | Dayjs, d2: string | Date | Dayjs) => {
+ const date1 = dayjs(d1)
+ const date2 = dayjs(d2)
return (
- date1.getFullYear() === date2.getFullYear() &&
- date1.getMonth() === date2.getMonth() &&
- date1.getDate() === date2.getDate()
- );
+ date1.year() === date2.year() &&
+ date1.month() === date2.month() &&
+ date1.day() === date2.day()
+ )
}
/**
- * Pretty print a date. The date is formatted as "DD.MM.YYYY".
+ * Pretty print a date. The date is formatted as "DAY DD.MM.YY".
* @param date - The date string to pretty print.
* @return {string} The pretty printed date.
*/
-export const pprintDate = (date: string | Date): string => {
- const d = new Date(date);
- return `${d.toLocaleDateString(navigator.language, {weekday: 'short'})} ${d.getDate()}.${d.getMonth() + 1}.${d.getFullYear()}`
+export const pprintDate = (date: string | Date | Dayjs): string => {
+ const d = dayjs(date)
+ return `${d.format('dd')} ${d.format('DD.MM.YY')}`
+}
+
+/**
+ * Pretty print a date and time. The date is formatted as "HH:MM DAY DD.MM.YYYY".
+ * Uses Dayjs
+ * @param date
+ */
+export const pprintDateTime = (date: string | Date | Dayjs): string => {
+ const d = dayjs(date)
+ return `${d.format('HH:mm')} ${d.format('dd')} ${d.format('DD.MM.YY')}`
}
@@ -73,4 +83,50 @@ export const humanDeltaFromNow = (start: string | Date | Dayjs, end: string | Da
message: getLocalizedNowWord(navigator.language),
delta: "NOW"
}
+}
+
+export const pprintDateRange = (d1: string | Date | Dayjs, d2: string | Date | Dayjs): string => {
+ const date1 = dayjs(d1)
+ const date2 = dayjs(d2)
+
+ if (areDatesSame(date1, date2)) {
+ return `${date1.format('HH:mm')} - ${date2.format('HH:mm')} ${dayjs(date2).format('DD.MM.YY')}`
+ } else {
+ return `${
+ date1.hour() == 0 && date1.minute() == 0 && date1.second() == 0 && date1.millisecond() == 0 ?
+ date1.format("dd DD.MM.YY") :
+ date1.format('HH:mm DD.MM')
+ } - ${
+ date2.hour() == 0 && date2.minute() == 0 && date2.second() == 0 && date2.millisecond() == 0 ?
+ date2.format("dd DD.MM.YY") :
+ date2.format("HH:mm DD.MM")
+ }`
+ }
+}
+
+/**
+ * Format the duration between two dates.
+ * Example: "1 Tag, 2 Std"
+ * @param date1
+ * @param date2
+ */
+export function formatDuration(date1: string | Date | Dayjs, date2: string | Date | Dayjs) {
+ const start = dayjs(date1)
+ const end = dayjs(date2)
+ const diff = end.diff(start)
+ const dur = dayjs.duration(diff)
+
+ if (dur.asHours() < 1) {
+ return `${dur.minutes()} Min`
+ } else if (dur.asDays() < 1) {
+ return `${dur.hours()} Std`
+ } else if (dur.asWeeks() < 1) {
+ const days = dur.days()
+ const hours = dur.hours()
+ return hours === 0 ? `${days} Tag(e)` : `${days} Tag(e), ${hours} Std`
+ } else {
+ const weeks = Math.floor(dur.asWeeks())
+ const days = dur.days()
+ return days === 0 ? `${weeks} Wo` : `${weeks} Wo, ${days} Tag(e)`
+ }
}
\ No newline at end of file
diff --git a/src/lib/helperTypes.ts b/src/lib/helperTypes.ts
new file mode 100644
index 0000000..a0d42b9
--- /dev/null
+++ b/src/lib/helperTypes.ts
@@ -0,0 +1,8 @@
+import {CSSProperties} from "react";
+
+export type TablerIconProps = {
+ style?: CSSProperties;
+ size?: number;
+ color?: string;
+ stroke?: number
+}
\ No newline at end of file
diff --git a/src/lib/pocketbase.tsx b/src/lib/pocketbase.tsx
index 80dfcbd..cc7ebb8 100644
--- a/src/lib/pocketbase.tsx
+++ b/src/lib/pocketbase.tsx
@@ -1,23 +1,68 @@
import {createContext, DependencyList, ReactNode, useCallback, useContext, useEffect, useMemo, useState} from "react"
-import PocketBase, {LocalAuthStore, RecordAuthResponse, RecordSubscription} from 'pocketbase'
+import PocketBase, {ClientResponseError, LocalAuthStore, RecordAuthResponse, RecordSubscription} from 'pocketbase'
import ms from "ms";
import {useInterval} from "@mantine/hooks";
import {useQuery} from "@tanstack/react-query";
-import {TypedPocketBase} from "../models";
+import {TypedPocketBase} from "@/models";
import {PB_BASE_URL, PB_STORAGE_KEY, PB_USER_COLLECTION} from "../../config.ts";
-import {LdapUserModel} from "../models/AuthTypes.ts";
+import {GuestUserModel, LdapUserModel} from "@/models/AuthTypes.ts";
+import {Alert, List} from "@mantine/core";
+import {IconAlertTriangle} from "@tabler/icons-react";
+import {showSuccessNotification} from "@/components/util.tsx";
+
+
+/**
+ * This component displays an alert for a PocketBase error.
+ * If the error is a ClientResponseError, it will display the error messages.
+ * If the error is any other error, it will display the error message.
+ * If there is no error, it will return null (no alert).
+ * @param error The error to display
+ */
+export const PocketBaseErrorAlert = ({error}: { error?: Error | null }) => {
+
+ if (error == null) {
+ return null
+ }
+
+ if (error instanceof ClientResponseError) {
+ return
} title={`Fehler - ${error.status}`}
+ color="red" variant="light">
+
+ {error.message}
+
+ {error.response.data && (
+
+ {Object.keys(error.response.data).map((key, index) => (
+
+ {error.response.data[key].message}
+
+ ))}
+
+ )}
+
+ }
+
+ return
} title="Fehler"
+ color="red" variant="light">
+ Fehler beim Laden der Daten: {" "}
+ {error.message}
+
+
+}
const oneMinuteInMs = ms("1 minute");
const PocketContext = createContext({})
const PocketData = () => {
- const pb = useMemo(() =>
- new PocketBase(
- PB_BASE_URL,
- new LocalAuthStore(PB_STORAGE_KEY)
- ) as TypedPocketBase,
- [])
+ const pb = useMemo(() => {
+ return new PocketBase(
+ PB_BASE_URL,
+ new LocalAuthStore(PB_STORAGE_KEY)
+ ) as TypedPocketBase
+ }, [])
const apiIsHealthyQuery = useQuery({
queryKey: ["apiHealthCheck"],
@@ -43,7 +88,7 @@ const PocketData = () => {
}, [pb])
const ldapLogin = useCallback(async (usernameOrCN: string, password: string) => {
- const res = await pb.send
("/api/ldap/login", {
+ await pb.send("/api/ldap/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
@@ -52,12 +97,23 @@ const PocketData = () => {
username: usernameOrCN,
password: password
})
+ }).then(res => {
+ pb.authStore.clear()
+ pb.authStore.save(res.token, res.record)
+ })
+ }, [pb])
+
+ const guestLogin = useCallback(async (usernameOrEmail: string, password: string) => {
+ await pb.collection("guest_users").authWithPassword(usernameOrEmail, password).then(res => {
+ pb.authStore.clear()
+ pb.authStore.save(res.token, res.record)
+ console.log(res.record)
})
- pb.authStore.save(res.token, res.record)
}, [pb])
const logout = useCallback(async () => {
- pb.authStore.clear();
+ pb.authStore.clear()
+ showSuccessNotification("Logout erfolgreich")
}, [pb.authStore])
const useSubscription = ({idOrName, topic = "*", callback}: {
@@ -69,14 +125,18 @@ const PocketData = () => {
return () => {
pb.collection(idOrName).unsubscribe(topic)
}
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, deps ? deps : []);
useInterval(refreshUser, oneMinuteInMs)
return {
ldapLogin,
+ guestLogin,
logout,
- user: user as LdapUserModel | null,
+ userRecord: user as LdapUserModel | GuestUserModel | null,
+ guestUser: user && !Object.keys(user).includes("objectGUID") ? user as GuestUserModel : null,
+ ldapUser: user && Object.keys(user).includes("objectGUID") ? user as LdapUserModel : null,
pb,
refreshUser,
useSubscription,
@@ -90,14 +150,13 @@ export const PocketBaseProvider = ({children}: {
}) => {
const data = PocketData()
return (
-
+
{children}
)
}
+// eslint-disable-next-line react-refresh/only-export-components
export const usePB = () => {
const pb = useContext(PocketContext)
if (!pb) {
diff --git a/src/lib/settings.ts b/src/lib/settings.ts
index 33033f1..10048c4 100644
--- a/src/lib/settings.ts
+++ b/src/lib/settings.ts
@@ -1,6 +1,6 @@
import {usePB} from "./pocketbase.tsx";
import {useQuery} from "@tanstack/react-query";
-import {TypedPocketBase} from "../models";
+import {TypedPocketBase} from "@/models";
type Setting = {
value: string;
@@ -11,14 +11,15 @@ type Setting = {
type Settings = {
privacyPolicy: Setting
agb: Setting
- stexGroup: Setting
+ stexGroupId: Setting
+ stuveEventQuestions: Setting
}
const loadSettings = async (pb: TypedPocketBase) => {
const data = await pb.collection('settings').getFullList()
return data.reduce((acc, s) => {
- acc[s.key as keyof Settings] = {
- value: s.value,
+ acc[s.key as unknown as keyof Settings] = {
+ value: s.value as string,
description: s.description,
updated: new Date(s.updated)
}
@@ -33,7 +34,6 @@ export const useSettings = () => {
const settingsQuery = useQuery({
queryKey: ['settings'],
queryFn: async () => await loadSettings(pb)
-
})
return settingsQuery.data
diff --git a/src/lib/user.ts b/src/lib/user.ts
new file mode 100644
index 0000000..5a5c168
--- /dev/null
+++ b/src/lib/user.ts
@@ -0,0 +1,27 @@
+import {usePB} from "@/lib/pocketbase.tsx";
+import {useQuery} from "@tanstack/react-query";
+
+/**
+ * This hook returns the user record of the currently logged-in user.
+ *
+ * If the specific LDAP user model or guest user model is needed, use the usePB hook
+ * to get the specific user model.
+ *
+ * @see usePB
+ * @returns The user record of the currently logged-in user.
+ */
+export const useUser = () => {
+ const {pb, userRecord} = usePB()
+
+ const query = useQuery({
+ queryKey: ["user", userRecord?.id ?? ""],
+ queryFn: async () => {
+ return await pb.collection("users").getOne(userRecord?.id ?? "", {
+ expand: "memberOf"
+ })
+ },
+ enabled: userRecord !== null
+ })
+
+ return query.data
+}
\ No newline at end of file
diff --git a/src/lib/util.ts b/src/lib/util.ts
index c4ffa96..1469ba7 100644
--- a/src/lib/util.ts
+++ b/src/lib/util.ts
@@ -40,4 +40,27 @@ export const createQRCodeUrl = (options: {
return `${PB_BASE_URL}/api/qr/v1?${createQueryParams(options)}`
}
+export const flattenReducer = (acc: T[], val: T | T[]): T[] => {
+ if (Array.isArray(val)) {
+ return [...acc, ...val]
+ }
+ return [...acc, val]
+}
+export const objectMap = (
+ obj: T,
+ fn: (k: T1, v: T[T1], i: number) => R
+) =>
+ Object.fromEntries(
+ Object.entries(obj).map(
+ ([k, v], i) => [k, fn((k as T1), (v as T[T1]), i)]
+ )
+ )
+
+/**
+ * This functions filters out all duplicate values from an array.
+ * @example [1, 2, 3, 1, 2, 4].filter(onlyUnique) // [1, 2, 3, 4]
+ */
+export function onlyUnique(value: T, index: number, array: T[]) {
+ return array.indexOf(value) === index;
+}
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 4a53e2e..6f86dd8 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,20 +1,24 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import Router from './Router.tsx'
-import '@mantine/core/styles.css';
-import '@mantine/code-highlight/styles.css';
-import '@mantine/dates/styles.css';
-import '@mantine/tiptap/styles.css';
-import {createTheme, DEFAULT_THEME, MantineProvider, mergeMantineTheme} from "@mantine/core";
+import '@mantine/core/styles.layer.css';
+import '@mantine/code-highlight/styles.layer.css';
+import '@mantine/dates/styles.layer.css';
+import '@mantine/tiptap/styles.layer.css';
+import '@mantine/notifications/styles.layer.css';
+import {Alert, createTheme, DEFAULT_THEME, MantineProvider, mergeMantineTheme} from "@mantine/core";
import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
-import {PocketBaseProvider} from "./lib/pocketbase.tsx";
-import "./global.css";
+import {PocketBaseProvider} from "@/lib/pocketbase.tsx";
+import "./style/global.css";
+import "./style/EventCalender.scss"
// fonts
import "@fontsource/overpass"
import "@fontsource/fira-code"
+import {Notifications} from "@mantine/notifications";
+import {ModalsProvider} from "@mantine/modals";
-const queryClient = new QueryClient()
+export const queryClient = new QueryClient()
const themeOverride = createTheme({
fontFamilyMonospace: 'Fira Code VF, monospace',
@@ -22,18 +26,30 @@ const themeOverride = createTheme({
headings: {
fontFamily: 'Overpass, sans-serif'
},
+ components: {
+ Alert: Alert.extend({
+ defaultProps: {
+ radius: 'md',
+ }
+ })
+ }
});
export const theme = mergeMantineTheme(DEFAULT_THEME, themeOverride);
-
ReactDOM.createRoot(document.getElementById('root')!).render(
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+ {/*
+
+ */}
+
+
+
+
)
\ No newline at end of file
diff --git a/src/models/AuthTypes.ts b/src/models/AuthTypes.ts
index 0b186d0..73da75a 100644
--- a/src/models/AuthTypes.ts
+++ b/src/models/AuthTypes.ts
@@ -1,7 +1,19 @@
-import {RecordModel} from "pocketbase";
+import {AuthModel, RecordModel} from "pocketbase";
+
+export type UserModal = {
+ name: string;
+ verified: boolean;
+ realm: string;
+ email: string;
+} & RecordModel
+
+export type GuestUserModel = {
+ username: string;
+ verified: boolean;
+ email: string;
+} & AuthModel & RecordModel
export type LdapUserModel = {
-
username: string;
email: string;
cn: string;
@@ -9,17 +21,19 @@ export type LdapUserModel = {
sn: string;
givenName: string;
accountExpires: string | null;
+ objectGUID: string;
memberOf: string[];
expand: {
memberOf: LdapGroupModel[]
}
-} & RecordModel
+} & AuthModel & RecordModel
export type LdapGroupModel = {
description: string;
cn: string;
dn: string;
+ objectGUID: string;
memberOf: string[];
expand: {
diff --git a/src/models/EventTypes.ts b/src/models/EventTypes.ts
index 084b549..440a8ef 100644
--- a/src/models/EventTypes.ts
+++ b/src/models/EventTypes.ts
@@ -1,61 +1,89 @@
-import {LdapUserModel} from "./AuthTypes.ts";
+import {GuestUserModel, LdapUserModel} from "./AuthTypes.ts";
import {RecordModel} from "pocketbase";
+import {FieldEntries} from "@/components/formUtil/fromInput/types.ts";
+import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
export type EventModel = {
name: string;
- description?: string;
+ description: string | null;
startDate: string;
endDate: string;
- adminMembers: string[];
- img?: string; // png, jpg, gif
- location: string;
+ eventAdmins: string[];
+ eventListAdmins: string[];
+ img: string | null; // png, jpg, gif
+ location: string | null;
isStuveEvent: boolean;
- additionalAgb?: string;
+ additionalAgb: string | null;
hideFromPublic: boolean;
- expand: {
- adminMembers: LdapUserModel[];
+ eventLinks: EventLink[];
+ defaultEntryQuestionSchema: FormSchema | null;
+ defaultEntryStatusSchema: FormSchema | null;
+ expand?: {
+ eventAdmins: LdapUserModel[] | null | [];
+ eventListAdmins: LdapUserModel[] | null | [];
}
} & RecordModel
+export type EventLink = {
+ name: string;
+ url: string;
+}
+
export type EventListModel = {
name: string;
- description?: string;
- registrable: boolean;
+ description: string | null;
+ open: boolean | null;
+ favourite: boolean | null;
event: string
- questionSchema: object
- expand: {
+ entryQuestionSchema: FormSchema | null
+ entryStatusSchema: FormSchema | null;
+ expand?: {
event: EventModel;
}
} & RecordModel
+
export type EventListSlotModel = {
- name: string;
eventList: string;
- description?: string;
- slotStart: string;
- slotEnd: string;
- expand: {
+ startDate: string;
+ endDate: string;
+ maxEntries: number | null;
+ description: string | null;
+ expand?: {
eventList: EventListModel;
}
} & RecordModel
-export type EventListSlotEntry = {
- questionAnswers: object;
- acceptTerms: boolean;
- eventListSlot: string;
- expand: {
- eventListSlot: EventListSlotModel;
+export type EventListSlotsWithEntriesCountModel = EventListSlotModel
+ & { entriesCount: number }
+ & Pick
+ & Pick
+
+export type EventListSlotEntryModel = {
+ entryQuestionData: FieldEntries;
+ entryStatusData: FieldEntries | null;
+ eventListsSlot: string;
+ ldapUser: string | null
+ guestUser: string | null
+ expand?: {
+ eventListsSlot: EventListSlotModel;
+ ldapUser: LdapUserModel | null;
+ guestUser: GuestUserModel | null;
}
-} & ({
- user: string;
- expand: {
- user: LdapUserModel;
- }
-} | {
- email: string;
- token: string;
-}) & RecordModel
-
-
-
+} & RecordModel
+export type EventListSlotEntriesWithUserModel =
+ EventListSlotEntryModel
+ & {
+ userId: string;
+ userName: string;
+ listName: string,
+ slotStartDate: string,
+ slotEndDate: string,
+ slotDescription: string | null,
+ eventList: string,
+ listDescription: string | null,
+ event: string
+}
+ & Pick
+ & Pick
\ No newline at end of file
diff --git a/src/models/index.ts b/src/models/index.ts
index 10aff0a..3de22e3 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -1,18 +1,30 @@
import PocketBase, {RecordModel, RecordService} from "pocketbase";
-import {LdapGroupModel, LdapSyncLogModel, LdapUserModel} from "./AuthTypes.ts";
-import {EventModel} from "./EventTypes.ts";
+import {GuestUserModel, LdapGroupModel, LdapSyncLogModel, LdapUserModel, UserModal} from "./AuthTypes.ts";
+import {
+ EventListModel,
+ EventListSlotEntriesWithUserModel,
+ EventListSlotEntryModel,
+ EventListSlotModel,
+ EventListSlotsWithEntriesCountModel,
+ EventModel
+} from "./EventTypes.ts";
export type SettingsModel = {
- key: string;
- value: string;
- description?: string;
- public: boolean;
+ key: ['privacyPolicy', 'agb', 'stexGroup', 'stuveEventQuestions']
+ value: string
+ description?: string
+ public?: boolean
} & RecordModel
export interface TypedPocketBase extends PocketBase {
- collection(idOrName: string): RecordService // default fallback for any other collection
+ collection(idOrName: string): RecordService
+
collection(idOrName: 'settings'): RecordService
+ collection(idOrName: 'users'): RecordService
+
+ collection(idOrName: 'guest_users'): RecordService
+
collection(idOrName: 'ldap_users'): RecordService
collection(idOrName: 'ldap_groups'): RecordService
@@ -21,4 +33,13 @@ export interface TypedPocketBase extends PocketBase {
collection(idOrName: 'events'): RecordService
+ collection(idOrName: 'eventLists'): RecordService
+
+ collection(idOrName: 'eventListSlots'): RecordService
+
+ collection(idOrName: 'eventListSlotEntries'): RecordService
+
+ collection(idOrName: 'eventListSlotsWithEntriesCount'): RecordService
+
+ collection(idOrName: 'eventListSlotEntriesWithUser'): RecordService
}
\ No newline at end of file
diff --git a/src/pages/events/:eventId/index.page.tsx b/src/pages/events/:eventId/index.page.tsx
deleted file mode 100644
index 74a009b..0000000
--- a/src/pages/events/:eventId/index.page.tsx
+++ /dev/null
@@ -1,299 +0,0 @@
-import {Link} from "react-router-dom";
-import {usePB} from "../../../lib/pocketbase.tsx";
-import {QueryObserverResult, useMutation} from "@tanstack/react-query";
-import {
- Alert,
- Anchor,
- Breadcrumbs,
- Button,
- Grid,
- Group,
- Modal,
- Stack,
- Text,
- ThemeIcon,
- Title,
- Transition
-} from "@mantine/core";
-import PBAvatar from "../../../components/PBAvatar.tsx";
-import UsersDisplay from "../../../components/auth/UsersDisplay.tsx";
-
-import classes from "./index.module.css";
-import {
- IconAdjustments,
- IconAlertTriangle,
- IconArchive,
- IconCalendar,
- IconCheck,
- IconHourglass,
- IconLock,
- IconMap,
- IconPencil,
- IconSectionSign,
- IconSettings,
- IconSettingsOff,
- IconSparkles,
- IconTrash
-} from "@tabler/icons-react";
-import {areDatesSame, humanDeltaFromNow, pprintDate} from "../../../lib/datetime.ts";
-import {useDisclosure} from "@mantine/hooks";
-import InnerHtml from "../../../components/InnerHtml";
-import EditEventModal from "../EditEventModal.tsx";
-import {EventModel} from "../../../models/EventTypes.ts";
-
-
-const ArchiveEventModal = (props: {
- event: EventModel,
- opened: boolean,
- onClose: () => void
-}) => {
- const {pb} = usePB()
-
- const archiveMutation = useMutation({
- mutationFn: async () => await pb.collection("events").update(props.event.id, {
- "adminMembers": []
- }),
- onSuccess: props.onClose
- })
-
- return <>
-
-
-
- Achtung
-
-
-
- Wenn Du dieses Ereignis archivierst, kann es weiterhin gesehen, jedoch nicht mehr bearbeitet
- werden.
-
- Nur ein Systemadministrator (C-Ref) kann diese Aktion rückgängig machen.
-
-
- {
- archiveMutation.error &&
-
- Das Event konnte nicht archiviert werden:
- {" "}
- {archiveMutation.error.message}
-
- }
-
-
- }
- onClick={() => archiveMutation.mutate()}
- loading={archiveMutation.isPending}
- >
- Weiter
-
-
- }
- onClick={props.onClose}
- >
- Abbrechen
-
-
-
-
- >
-}
-
-export default function EventView({event, refetchEvent}: {
- event: EventModel,
- refetchEvent: () => Promise>
-}) {
-
- const {user} = usePB()
-
- const [showSettings, showSettingsHandler] = useDisclosure(false)
- const [showEditModal, showEditModalHandler] = useDisclosure(false)
- const [showArchiveModal, showArchiveModalHandler] = useDisclosure(false)
-
- // the time delta of the event from now
- const delta = humanDeltaFromNow(event.startDate, event.endDate)
-
- // whether the user is an admin of the event
- const isEventAdmin = user && event.adminMembers.includes(user.id)
-
- const readOnlyEvent = event.adminMembers.length == 0
-
- return <>
-
-
{[
-
- Home
- ,
-
- Events
- ,
-
- {event.name}
-
- ]}
-
-
- {
- readOnlyEvent && }
- className={"section-transparent"}
- p={"sm"}
- >
- Dieses Event ist archiviert und wird nicht mehr verwaltet.
-
- }
-
-
-
-
- {isEventAdmin &&
- }
- {event.name}
-
-
-
- {isEventAdmin &&
- <>
- {
- refetchEvent().then(() => {
- showEditModalHandler.close()
- })
- }}/>
-
- {
- refetchEvent().then(() =>
- showArchiveModalHandler.close()
- )
- }}/>
-
-
-
:
}
- variant={"transparent"}
- onClick={showSettingsHandler.toggle}
- >
- Einstellungen
-
-
-
- {(styles) =>
- }
- variant={"transparent"}
- color={"green"}
- onClick={showEditModalHandler.open}
- >
- Bearbeiten
-
-
- }
- variant={"transparent"}
- color={"orange"}
- onClick={showArchiveModalHandler.open}
- >
- Archivieren
-
-
- }
- variant={"transparent"}
- color={"red"}
- disabled
- >
- Löschen
-
-
}
-
-
- >
- }
-
-
-
-
-
Daten
-
- {
- event.isStuveEvent && <>
-
-
- StuVe Event
-
-
-
-
- AGB der StuVe
-
-
- >
- }
-
- {
- event.additionalAgb && (
-
-
-
- Event AGB
-
-
- )
- }
-
-
-
- {event.location}
-
-
-
-
- {delta.message}
-
-
-
-
- {areDatesSame(event.startDate, event.endDate) ?
- pprintDate(event.startDate)
- :
- `${pprintDate(event.startDate)} - ${pprintDate(event.endDate)}`
- }
-
-
- {
- event.expand?.adminMembers &&
-
-
-
- Verwaltet von:
-
-
-
- }
-
-
-
- {
- event.description &&
-
-
-
Beschreibung
-
-
-
- }
-
- >
-}
\ No newline at end of file
diff --git a/src/pages/events/:eventId/l/:listId.page.tsx b/src/pages/events/:eventId/l/:listId.page.tsx
deleted file mode 100644
index 434cfdf..0000000
--- a/src/pages/events/:eventId/l/:listId.page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function EventListView() {
- return <>
-
-
List
-
- >
-}
\ No newline at end of file
diff --git a/src/pages/events/:eventId/terms-and-conditions.page.tsx b/src/pages/events/:eventId/terms-and-conditions.page.tsx
deleted file mode 100644
index 77d1dc7..0000000
--- a/src/pages/events/:eventId/terms-and-conditions.page.tsx
+++ /dev/null
@@ -1,7 +0,0 @@
-export default function EventTermsAndConditions() {
- return <>
-
-
Event AGB
-
- >
-}
\ No newline at end of file
diff --git a/src/pages/events/EditEventModal.tsx b/src/pages/events/EditEventModal.tsx
deleted file mode 100644
index 6bf26d5..0000000
--- a/src/pages/events/EditEventModal.tsx
+++ /dev/null
@@ -1,225 +0,0 @@
-import {Alert, Avatar, Button, Checkbox, Divider, Modal, Stack, Textarea, TextInput} from "@mantine/core";
-import {EventModel} from "../../models/EventTypes.ts";
-import {hasLength, useForm} from "@mantine/form";
-import {usePB} from "../../lib/pocketbase.tsx";
-import {DateTimePicker} from "@mantine/dates";
-import TextEditor from "../../components/input/Editor";
-import UserInput from "../../components/auth/UserInput.tsx";
-import {LdapUserModel} from "../../models/AuthTypes.ts";
-import {IconCheck, IconInfoCircle, IconX} from "@tabler/icons-react";
-import ImageSelect from "../../components/input/ImageSelect.tsx";
-import {useMutation} from "@tanstack/react-query";
-import dayjs from "dayjs";
-import {useNavigate} from "react-router-dom";
-
-export default function EditEventModal({event, onClose, opened}: {
- event?: EventModel,
- opened: boolean,
- onClose: () => void
-}) {
- const {user, pb} = usePB()
- const navigate = useNavigate()
-
- const formValues = useForm({
- initialValues: {
- name: event?.name ?? "",
- startDate: event?.startDate ? new Date(event.startDate) : null,
- endDate: event?.endDate ? new Date(event.endDate) : null,
- location: event?.location ?? "",
- description: event?.description ?? "",
- adminMembers: event?.expand.adminMembers ?? [user] as LdapUserModel[],
- img: null as File | null,
- isStuveEvent: event?.isStuveEvent ?? true,
- additionalAgb: event?.externalAgb ?? "",
- hideFromPublic: event?.hideFromPublic ?? false
- },
- validate: {
- name: hasLength({min: 4, max: 50}, 'Der Name muss zwischen 4 und 50 Zeichen lang sein.'),
- startDate: (value) => dayjs(value).isAfter(dayjs(), "day") ? null : "Das Startdatum muss in der Zukunft liegen.",
- endDate: (value, values) => dayjs(value).isAfter(dayjs(values.startDate), "day") ? null : "Das Enddatum muss nach dem Startdatum liegen.",
- location: hasLength({min: 4, max: 500}, 'Der Ort muss zwischen 4 und 500 Zeichen lang sein.'),
- adminMembers: (value) => value.length > 0 ? null : "Es muss mindestens ein Admin ausgewählt werden.",
- additionalAgb: (value, values) => !values.isStuveEvent && value.length < 10 ? "Die AGB für externe Events müssen angegeben werden. (min. 10 Zeichen)" : null
- }
- })
-
- const upsertEventMutation = useMutation({
- mutationFn: async () => {
- const formData = new FormData()
- formValues.values.img && formData.append("img", formValues.values.img as File)
- formData.append("name", formValues.values.name)
- formData.append("startDate", formValues.values.startDate!.toISOString())
- formData.append("endDate", formValues.values.endDate!.toISOString())
- formData.append("location", formValues.values.location)
- formData.append("description", formValues.values.description)
- formData.append("isStuveEvent", formValues.values.isStuveEvent.toString())
- formData.append("additionalAgb", formValues.values.additionalAgb)
- formData.append("hideFromPublic", (
- // all stuve are public by default
- formValues.values.isStuveEvent ? false : formValues.values.hideFromPublic
- ).toString())
- formValues.values.adminMembers.forEach((member) => {
- formData.append("adminMembers", member.id)
- })
-
- if (event) {
- return await pb.collection("events").update(event.id, formData)
- } else {
- return await pb.collection("events").create(formData)
- }
- },
- onSuccess: (res) => {
- onClose()
- navigate(`/events/${res.id}`)
- }
- })
-
- return <>
-
-
-
- >
-}
\ No newline at end of file
diff --git a/src/pages/events/EventNavigate.tsx b/src/pages/events/EventNavigate.tsx
new file mode 100644
index 0000000..42429db
--- /dev/null
+++ b/src/pages/events/EventNavigate.tsx
@@ -0,0 +1,45 @@
+import {Navigate, useParams} from "react-router-dom";
+import {usePB} from "@/lib/pocketbase.tsx";
+import {useQuery} from "@tanstack/react-query";
+import {LoadingOverlay} from "@mantine/core";
+import NotFound from "@/pages/not-found/index.page.tsx";
+import {useEventRights} from "@/pages/events/util.ts";
+
+/**
+ * This Router loads the event and checks if the user is an admin of the event.
+ * If the user is an admin, it redirects to the event admin panel, otherwise to the shared event view.
+ * This is the main entry point for the event page. This way we can ensure that the user is always redirected to the correct page.
+ * @constructor
+ */
+export default function EventNavigate() {
+
+ const {pb} = usePB()
+ const {eventId} = useParams() as { eventId: string }
+
+ const eventQuery = useQuery({
+ queryKey: ["event", eventId],
+ queryFn: async () => (await pb.collection("events").getOne(eventId, {
+ expand: "eventAdmins, eventListAdmins"
+ }))
+ })
+
+ const {canEditEventList, canEditEvent} = useEventRights(eventQuery.data)
+
+ if (eventQuery.isLoading) {
+ return
+ }
+
+ if (eventQuery.isError || !eventQuery.data) {
+ return
+ }
+
+ if (canEditEvent) {
+ return
+ }
+
+ if (canEditEventList) {
+ return
+ }
+
+ return
+}
\ No newline at end of file
diff --git a/src/pages/events/EventOverview/CreateEvent.tsx b/src/pages/events/EventOverview/CreateEvent.tsx
new file mode 100644
index 0000000..f7fba8a
--- /dev/null
+++ b/src/pages/events/EventOverview/CreateEvent.tsx
@@ -0,0 +1,164 @@
+import {Alert, Button, Checkbox, Divider, Grid, TextInput, Title} from "@mantine/core";
+import {hasLength, useForm} from "@mantine/form";
+import {DateTimePicker} from "@mantine/dates";
+import {IconCheck, IconInfoCircle, IconX} from "@tabler/icons-react";
+import {useMutation} from "@tanstack/react-query";
+import dayjs from "dayjs";
+import {LdapUserModel} from "@/models/AuthTypes.ts";
+import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
+import LdapUserInput from "@/components/auth/LdapUserInput.tsx";
+import {EventModel} from "@/models/EventTypes.ts";
+import ShowHelp from "@/components/ShowHelp.tsx";
+import {useSettings} from "@/lib/settings.ts";
+import {FormSchema} from "@/components/formUtil/formBuilder/types.ts";
+
+/**
+ * This component allows the user to create a new event.
+ * It only asks for the most basic information (name, start/end date, admins, isStuVeEvent) and creates the event.
+ * If the event is a StuVe event, it will use the StuVe event questions as default question schema.
+ * @param onSuccess - Callback that is called when the event was successfully created. The created event is passed as argument.
+ * @param onAbort - Callback that is called when the user aborts the creation of the event.
+ */
+export default function CreateEvent({onSuccess, onAbort}: {
+ onSuccess: (event: EventModel) => void,
+ onAbort?: () => void
+}) {
+ const {ldapUser, pb} = usePB()
+ const settings = useSettings()
+
+ const stuveQuestions = JSON.parse(settings?.stuveEventQuestions?.value ?? `{ "fields":[] }`) as FormSchema
+
+ const formValues = useForm({
+ initialValues: {
+ name: "",
+ startDate: null,
+ endDate: null,
+ eventAdmins: [ldapUser] as LdapUserModel[],
+ isStuveEvent: true,
+ },
+ validate: {
+ name: hasLength({min: 4, max: 50}, 'Der Name muss zwischen 4 und 50 Zeichen lang sein.'),
+ startDate: (value) => dayjs(value).isAfter(dayjs(), "day") ? null : "Das Startdatum muss in der Zukunft liegen.",
+ endDate: (value, values) => dayjs(value).isAfter(dayjs(values.startDate), "day") ? null : "Das Enddatum muss nach dem Startdatum liegen.",
+ eventAdmins: (value) => value.length > 0 ? null : "Es muss mindestens ein Admin ausgewählt werden.",
+ }
+ })
+
+ const createEventMutation = useMutation({
+ mutationFn: async () => {
+ if (!ldapUser) {
+ throw new Error("Nur mit StuVe IT Account eingeloggte Personen können Events erstellen")
+ }
+ return await pb.collection("events").create({
+ ...formValues.values,
+ eventAdmins: formValues.values.eventAdmins.map((member) => member.id),
+ defaultEntryQuestionSchema: formValues.values.isStuveEvent ? {
+ fields: stuveQuestions.fields.map((field) => ({
+ ...field, meta: {
+ ...field.meta,
+ required: true
+ }
+ }))
+ } : {fields: []}
+ })
+ },
+ onSuccess: (data) => onSuccess(data)
+ })
+
+ return <>
+ createEventMutation.mutate())}>
+
+ Neues Event erstellen
+
+
+ Hier kannst du ein neues Event erstellen. Fülle dazu die Felder aus und klicke auf "Speichern".
+
+ Alle weiteren Einstellungen (z.B. die Beschreibung, Listen, ...) kannst du nach dem Erstellen des Events
+ vornehmen.
+
+ Du kannst auch alle Werte die du hier angibst später noch bearbeiten.
+
+
+
+
+
+
+
+
+
+
+ formValues.setFieldValue("eventAdmins", records)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+ }>
+ StuVe Events (z.B. Uni-Party, Fachschafts-Event, ...) werden zusammen mit der
+ Studierenden Exekutive verwaltet und es gelten die AGB der Studierendenvertretung (StuVe).
+
+ Externe Events (z.B. private Feiern, ...), haben eigene AGB
+ und die StuVe ist nicht der Veranstalter.
+
+
+
+
+
+
+
+
+
+ {onAbort && (
+ }
+ >
+ Abbrechen
+
+ )}
+
+ }
+ type={"submit"}
+ loading={createEventMutation.isPending}
+ >
+ Speichern
+
+
+
+ >
+}
\ No newline at end of file
diff --git a/src/pages/events/eventOverview/eventCalendar.tsx b/src/pages/events/EventOverview/EventCalendar.tsx
similarity index 95%
rename from src/pages/events/eventOverview/eventCalendar.tsx
rename to src/pages/events/EventOverview/EventCalendar.tsx
index 27362cc..2fb396c 100644
--- a/src/pages/events/eventOverview/eventCalendar.tsx
+++ b/src/pages/events/EventOverview/EventCalendar.tsx
@@ -1,11 +1,10 @@
-import {usePB} from "../../../lib/pocketbase.tsx";
+import {usePB} from "@/lib/pocketbase.tsx";
import {useNavigate} from "react-router-dom";
import {useState} from "react";
import dayjs from "dayjs";
import {useQuery} from "@tanstack/react-query";
import {LoadingOverlay} from "@mantine/core";
import {Calendar, dayjsLocalizer} from "react-big-calendar";
-import "./eventCalender.scss"
const localizer = dayjsLocalizer(dayjs)
@@ -18,7 +17,7 @@ export const EventCalendar = () => {
const {pb} = usePB()
- const navigate = useNavigate();
+ const navigate = useNavigate()
const [selectedMonth, setSelectedMonth] = useState({
start: dayjs().startOf("month"),
@@ -56,6 +55,7 @@ export const EventCalendar = () => {
return <>
+
{
- const {user} = usePB()
+ const {canEditEventList, canEditEvent} = useEventRights(event)
const [opened, handlers] = useDisclosure(false)
@@ -60,30 +63,27 @@ const EventRow = ({event}: { event: EventModel }) => {
- {areDatesSame(event.startDate, event.endDate) ?
- pprintDate(event.startDate)
- :
- `${pprintDate(event.startDate)} - ${pprintDate(event.endDate)}`
+ {
+ areDatesSame(event.startDate, event.endDate) ?
+ pprintDate(event.startDate) : `${pprintDate(event.startDate)} - ${pprintDate(event.endDate)}`
}
- {
- delta.message
- }
+ {delta.message}
{event.location}
-
+
{event.isStuveEvent ? "StuVe" : "Extern"}
{
- event.adminMembers.length === 0 ? (
+ event.eventAdmins.length === 0 ? (
{
) : (
-
- event.adminMembers.includes(user?.id ?? "") ? (
+ canEditEvent ? (
- ) : (
-
- )
+ ) : canEditEventList ?
+
+
+
+
+ : (
+
+ )
)
}
-
+
{
disabled={!event.description}
>
{
- opened ? :
+ opened ? :
}
- {opened && <>
+
+
- >}
+
>
}
diff --git a/src/pages/events/eventOverview/index.module.css b/src/pages/events/EventOverview/index.module.css
similarity index 100%
rename from src/pages/events/eventOverview/index.module.css
rename to src/pages/events/EventOverview/index.module.css
diff --git a/src/pages/events/EventOverview/index.page.tsx b/src/pages/events/EventOverview/index.page.tsx
new file mode 100644
index 0000000..237b8ce
--- /dev/null
+++ b/src/pages/events/EventOverview/index.page.tsx
@@ -0,0 +1,62 @@
+import {IconConfetti, IconPlus} from "@tabler/icons-react";
+import {EventCalendar} from "./EventCalendar.tsx";
+import {EventList} from "./EventList.tsx";
+import {Anchor, Breadcrumbs, Button, Collapse} from "@mantine/core";
+import {usePB} from "@/lib/pocketbase.tsx";
+import {useDisclosure} from "@mantine/hooks";
+import CreateEvent from "./CreateEvent.tsx";
+import {Link, useNavigate} from "react-router-dom";
+
+
+export default function EventOverview() {
+
+ const {ldapUser} = usePB()
+ const [showCreateEvent, showCreateEventHandler] = useDisclosure(false)
+ const navigate = useNavigate();
+
+ return <>
+
+ >
+}
\ No newline at end of file
diff --git a/src/pages/events/EventsRouter.tsx b/src/pages/events/EventsRouter.tsx
new file mode 100644
index 0000000..695468b
--- /dev/null
+++ b/src/pages/events/EventsRouter.tsx
@@ -0,0 +1,20 @@
+import {Outlet, Route, Routes} from "react-router-dom";
+import NotFound from "../not-found/index.page.tsx";
+import EventOverview from "@/pages/events/EventOverview/index.page.tsx";
+import EventView from "./s/EventView.tsx";
+import EventNavigate from "@/pages/events/EventNavigate.tsx";
+import EditEventRouter from "@/pages/events/e/:eventId/EditEventRouter.tsx";
+
+
+export default function EventsRouter() {
+ return <>
+
+ >
+}
\ No newline at end of file
diff --git a/src/pages/events/:eventId/index.module.css b/src/pages/events/e/:eventId/EditEventRouter.module.css
similarity index 81%
rename from src/pages/events/:eventId/index.module.css
rename to src/pages/events/e/:eventId/EditEventRouter.module.css
index 5be67a8..d32d713 100644
--- a/src/pages/events/:eventId/index.module.css
+++ b/src/pages/events/e/:eventId/EditEventRouter.module.css
@@ -2,7 +2,6 @@
max-width: var(--max-content-width);
}
-
.data {
display: flex;
flex-direction: column;
@@ -25,3 +24,9 @@
margin: 0;
}
}
+
+.navLink {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
\ No newline at end of file
diff --git a/src/pages/events/e/:eventId/EditEventRouter.tsx b/src/pages/events/e/:eventId/EditEventRouter.tsx
new file mode 100644
index 0000000..33a5798
--- /dev/null
+++ b/src/pages/events/e/:eventId/EditEventRouter.tsx
@@ -0,0 +1,234 @@
+import {Link, Navigate, NavLink, Outlet, Route, Routes, useMatch, useParams} from "react-router-dom";
+import {usePB} from "@/lib/pocketbase.tsx";
+import {useQuery} from "@tanstack/react-query";
+import {
+ ActionIcon,
+ ActionIconProps,
+ Alert,
+ Anchor,
+ Breadcrumbs,
+ createPolymorphicComponent,
+ Grid,
+ Group,
+ LoadingOverlay,
+ ThemeIcon,
+ Title,
+ Tooltip
+} from "@mantine/core";
+import PBAvatar from "@/components/PBAvatar.tsx";
+
+import classes from "./EditEventRouter.module.css";
+import {
+ IconArchive,
+ IconExternalLink,
+ IconInfoCircle,
+ IconList,
+ IconPencil,
+ IconQrcode,
+ IconSectionSign,
+ IconSettings
+} from "@tabler/icons-react";
+import NotFound from "@/pages/not-found/index.page.tsx";
+import EventDescription from "./EventComponents/EventDescription.tsx";
+import EventAGB from "./EventComponents/EventAGB.tsx";
+import EventData from "./EventComponents/EventData.tsx";
+import EventSettingsRouter, {
+ EventSettingsMenu
+} from "@/pages/events/e/:eventId/EventComponents/EventSettings/EventSettingsRouter.tsx";
+import EventLinks from "./EventComponents/EventLinks.tsx";
+import EventListsRouter, {EventListsMenu} from "./EventLists/EventListsRouter.tsx";
+import {useEventRights} from "@/pages/events/util.ts";
+import EventFavourites from "./EventComponents/EventFavourites.tsx";
+import {forwardRef} from "react";
+import {APP_URL} from "../../../../../config.ts";
+
+
+type NavIconProps = {
+ isActive?: boolean
+ label: string
+} & ActionIconProps
+
+const NavIcon = createPolymorphicComponent