diff --git a/config.ts b/config.ts index 131c4b3..bd347d1 100644 --- a/config.ts +++ b/config.ts @@ -10,4 +10,8 @@ export const PB_STORAGE_KEY = "stuve-it-login-record" // general export const APP_NAME = "StuVe IT" export const APP_VERSION = "0.9.8 (beta)" -export const APP_URL = "https://it.stuve.uni-ulm.de" \ No newline at end of file +export const APP_URL = "https://it.stuve.uni-ulm.de" + +// analytics +export const ANALYTICS_COOKIE_NAME = "stuve-it-analytics" +export const ANALYTICS_IP_API = "https://backend.stuve-it.de/api/ip/json" \ No newline at end of file diff --git a/package.json b/package.json index 5ddea36..5ef9b4e 100644 --- a/package.json +++ b/package.json @@ -42,12 +42,16 @@ "jwt-decode": "^3.1.2", "ms": "^2.1.3", "nanoid": "^5.0.7", + "ofetch": "^1.4.1", "papaparse": "^5.4.1", "pocketbase": "^0.19.0", "react": "^18.2.0", + "react-beforeunload": "^2.6.0", "react-big-calendar": "^1.11.3", + "react-cookie": "^7.2.2", "react-dom": "^18.2.0", "react-dropzone": "^14.2.3", + "react-error-boundary": "^4.1.2", "react-infinite-scroll-component": "^6.1.0", "react-intersection-observer": "^9.10.2", "react-markdown": "^9.0.1", @@ -57,6 +61,7 @@ "sanitize-html": "^2.13.0", "tippy.js": "^6.3.7", "tiptap": "^1.32.2", + "ua-parser-js": "^2.0.0-rc.1", "zod": "^3.22.4" }, "devDependencies": { diff --git a/src/Router.tsx b/src/Router.tsx index 50c3281..31040c8 100644 --- a/src/Router.tsx +++ b/src/Router.tsx @@ -1,17 +1,22 @@ import {createBrowserRouter, RouterProvider} from "react-router-dom"; import HomePage from "./pages/home/index.page.tsx"; -import NotFound from "./pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import Layout from "@/components/layout"; import QRCodeGenerator from "./pages/util/qr/index.page.tsx"; import EventsRouter from "./pages/events/EventsRouter.tsx"; import LegalPage from "@/pages/LegalPage.tsx"; -import DebugPage from "@/pages/test/DebugPage.tsx"; +import DebugPage from "@/pages/debug/DebugPage.tsx"; import EmailRouter from "@/pages/email/EmailRouter.tsx"; +import AdminRouter from "@/pages/admin/AdminRouter.tsx"; +import Analytics from "@/components/analytics"; const router = createBrowserRouter([ { path: "/", - element: , + element: <> + + + >, children: [ { index: true, @@ -29,6 +34,10 @@ const router = createBrowserRouter([ path: "email/*", element: , }, + { + path: "admin/*", + element: , + }, { path: "debug", element: diff --git a/src/components/analytics/index.tsx b/src/components/analytics/index.tsx new file mode 100644 index 0000000..1a0f522 --- /dev/null +++ b/src/components/analytics/index.tsx @@ -0,0 +1,60 @@ +import {Button, Dialog, Text} from "@mantine/core"; +import {IconThumbUp} from "@tabler/icons-react"; +import {Link} from "react-router-dom"; +import useAnalyticsVisitor from "@/components/analytics/useAnalyticsVisitor.ts"; +import useAnalyticsSession from "@/components/analytics/useAnalyticsSession.ts"; +import useAnalyticsError from "@/components/analytics/useAnalyticsError.ts"; +import useAnalyticsPageView from "@/components/analytics/useAnalyticsPageView.ts"; + +/** + * Component to handle analytics and display a dialog for cookie consent. + * + * @returns The rendered Analytics component. + */ +export default function Analytics() { + + // Initialize visitor analytics + const {visitorId, initVisitor} = useAnalyticsVisitor() + // Initialize session analytics with the visitor ID + const {sessionId} = useAnalyticsSession(visitorId) + // Track page views with the session ID + useAnalyticsPageView(sessionId) + // Track errors + useAnalyticsError(sessionId) + + return ( + <> + + + + Wir nutzen Cookies um diese Seite zu verbessern. + + + Mit der Nutzung unserer Dienste erklärst du dich damit einverstanden, + dass wir Cookies verwenden und anonymisierte Nutzungsdaten erheben. + + + + + Infos + + + } + > + Alles klar + + + + + > + ) +} \ No newline at end of file diff --git a/src/components/analytics/useAnalyticsError.ts b/src/components/analytics/useAnalyticsError.ts new file mode 100644 index 0000000..84e67eb --- /dev/null +++ b/src/components/analytics/useAnalyticsError.ts @@ -0,0 +1,84 @@ +import {useEffect} from "react"; +import {usePB} from "@/lib/pocketbase.tsx"; +import {useMutation} from "@tanstack/react-query"; +import {useLocation} from "react-router-dom"; + +/** + * Custom hook to track and log errors for analytics. + * + * @param {string} [sessionId] - The ID of the current analytics session. + */ +export default function useAnalyticsError(sessionId?: string) { + + const {pb} = usePB() + const location = useLocation() + + /** + * Mutation to log an error in the analytics system. + * + * @param {Object} error - The error object containing error details. + * @param {string} error.session - The session ID. + * @param {string} error.error_message - The error message. + * @param {string} error.error_type - The type of error. + * @param {string} error.stack_trace - The stack trace of the error. + */ + const trackErrorMutation = useMutation({ + mutationFn: async (error: { + session: string; + error_message: string; + error_type: string; + stack_trace: string; + }) => { + if (!sessionId) return + return await pb.collection('analyticsErrors').create({ + ...error, + path: location.pathname + }) + } + }) + + useEffect(() => { + // Check if current session is available + if (!sessionId) return; + + /** + * Global error handler to track errors. + * + * @param {ErrorEvent} event - The error event. + */ + const errorHandler = function (event: ErrorEvent) { + trackErrorMutation.mutate({ + session: sessionId, + error_message: event.message || 'Unknown error', + error_type: 'Global Error', + stack_trace: event.error?.stack || '', + }) + } + + /** + * Unhandled rejection handler to track promise rejections. + * + * @param {PromiseRejectionEvent} event - The promise rejection event. + */ + const unhandledRejectionHandler = function (event: PromiseRejectionEvent) { + trackErrorMutation.mutate({ + session: sessionId, + error_message: event.reason?.message || 'Unhandled Rejection', + error_type: 'Promise Rejection', + stack_trace: event.reason?.stack || '', + }) + } + + // Add event listeners + window.addEventListener('error', errorHandler) + window.addEventListener('unhandledrejection', unhandledRejectionHandler) + + // Cleanup function in useEffect + return () => { + window.removeEventListener('error', errorHandler) + window.removeEventListener('unhandledrejection', unhandledRejectionHandler) + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sessionId]) +} \ No newline at end of file diff --git a/src/components/analytics/useAnalyticsPageView.ts b/src/components/analytics/useAnalyticsPageView.ts new file mode 100644 index 0000000..e08812b --- /dev/null +++ b/src/components/analytics/useAnalyticsPageView.ts @@ -0,0 +1,36 @@ +import {useEffect} from "react"; +import {useLocation} from "react-router-dom"; +import {useMutation} from "@tanstack/react-query"; +import {usePB} from "@/lib/pocketbase.tsx"; + +/** + * Custom hook to track page views for analytics. + * + * @param {string} [sessionId] - The ID of the current analytics session. + */ +export default function useAnalyticsPageView(sessionId?: string) { + const location = useLocation() + const {pb} = usePB() + + /** + * Mutation to log a page view in the analytics system. + * + * @param {string} path - The path of the page being viewed. + */ + const pageViewMutation = useMutation({ + mutationFn: async (path: string) => { + await pb.collection("analyticsPageViews").create({ + path, + session: sessionId, + }) + } + }) + + // Effect to log the page view when the session ID or location changes + useEffect(() => { + if (!sessionId) return + + pageViewMutation.mutate(location.pathname) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sessionId, location]) +} \ No newline at end of file diff --git a/src/components/analytics/useAnalyticsSession.ts b/src/components/analytics/useAnalyticsSession.ts new file mode 100644 index 0000000..cc936ea --- /dev/null +++ b/src/components/analytics/useAnalyticsSession.ts @@ -0,0 +1,64 @@ +import {useEffect, useState} from "react"; +import {useMutation} from "@tanstack/react-query"; +import {usePB} from "@/lib/pocketbase.tsx"; +import {nanoid} from "nanoid/non-secure"; +import {UAParser} from "ua-parser-js"; +import {AnalyticsIpApiResult} from "@/models/AnalyticsTypes.ts"; +import {ANALYTICS_IP_API} from "../../../config.ts"; +import {ofetch} from "ofetch"; + +/** + * Custom hook to manage an analytics session. + * + * @param {string} [visitorId] - The ID of the visitor. + * @returns {Object} - An object containing the session ID. + */ +export default function useAnalyticsSession(visitorId?: string) { + + // State to store the session ID + const [sessionId, setSessionId] = useState(undefined) + const {pb} = usePB() + + // Mutation to initialize the analytics session + const initSessionMutation = useMutation({ + mutationFn: async () => { + if (!visitorId) return + + // Parse the user agent to get device and browser information + const parser = new UAParser() + const result = parser.getResult() + + // Fetch IP and country code from the analytics IP API + const { + ip, + country_code + } = await ofetch(ANALYTICS_IP_API, {ignoreResponseError: true}) + + // Create a new analytics session in the PocketBase collection + return await pb.collection("analyticsSessions").create({ + id: nanoid(15), + visitor: visitorId, + device_type: result.device.type || 'desktop', + browser_name: result.browser.name, + browser_version: result.browser.version, + operating_system: result.os.name, + operating_system_version: result.os.version, + user_agent: navigator.userAgent, + ip_address: ip, + geo_country_code: country_code + }) + }, + onSuccess: (data) => { + // Set the session ID on successful creation + setSessionId(data?.id) + } + }) + + // Effect to initialize the session when the visitor ID changes + useEffect(() => { + initSessionMutation.mutate() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [visitorId]) + + return {sessionId} +} \ No newline at end of file diff --git a/src/components/analytics/useAnalyticsVisitor.ts b/src/components/analytics/useAnalyticsVisitor.ts new file mode 100644 index 0000000..9669199 --- /dev/null +++ b/src/components/analytics/useAnalyticsVisitor.ts @@ -0,0 +1,62 @@ +import {useCookies} from "react-cookie"; +import {ANALYTICS_COOKIE_NAME} from "../../../config.ts"; +import {usePB} from "@/lib/pocketbase.tsx"; +import {nanoid} from "nanoid/non-secure"; +import {useMutation} from "@tanstack/react-query"; +import {ClientResponseError} from "pocketbase"; +import {useEffect} from "react"; + + +/** + * This hook is used to manage the analytics visitor id and the cookie. + * + * If the cookie is not set, the returned visitorId will be undefined. + * To set the cookie and create the visitor in the database, call the initVisitor function. + */ +export default function useAnalyticsVisitor() { + const [cookies, setCookie] = useCookies([ANALYTICS_COOKIE_NAME]) + + const {pb} = usePB() + + const initVisitor = () => { + // create 15 chars long random string + const visitorId = nanoid(15) + + // create visitor in database + upsertVisitorMutation.mutateAsync(visitorId).then(() => { + // set cookie + setCookie(ANALYTICS_COOKIE_NAME, visitorId, {path: "/"}) + }) + } + + const cookieValue = cookies[ANALYTICS_COOKIE_NAME] + const cookieIsSet = cookieValue !== undefined + + const upsertVisitorMutation = useMutation({ + mutationFn: async (visitorId: string) => { + try { + // set visitor updated date to now + await pb.collection("analyticsVisitors").update(visitorId, {}) + } catch (e) { + if ((e as ClientResponseError).status === 404) { + // visitor does not exist, create it + await pb.collection("analyticsVisitors").create({id: visitorId}) + } + } + } + }) + + // this effect will run when ever the cookie is set or the page reloads + useEffect(() => { + if (cookieIsSet) { + upsertVisitorMutation.mutate(cookieValue) + } + // eslint-disable-next-line + }, [cookieIsSet, cookieValue]) + + + return { + visitorId: cookieValue as string | undefined, + initVisitor + } +} \ No newline at end of file diff --git a/src/components/layout/footer/index.module.css b/src/components/layout/footer/index.module.css index 58b416e..ecf09c3 100644 --- a/src/components/layout/footer/index.module.css +++ b/src/components/layout/footer/index.module.css @@ -8,9 +8,6 @@ @media (max-width: $mantine-breakpoint-sm) { padding: calc(var(--padding)/2) var(--mantine-spacing-xl); } - - /*noinspection CssInvalidFunction*/ - //box-shadow: 0 0 5px 10px light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-9));; } .bottomContainer { diff --git a/src/components/layout/nav/MenuItems.tsx b/src/components/layout/nav/MenuItems.tsx index be9af5c..05b669e 100644 --- a/src/components/layout/nav/MenuItems.tsx +++ b/src/components/layout/nav/MenuItems.tsx @@ -11,6 +11,7 @@ import { IconSectionSign } from "@tabler/icons-react"; import {useShowDebug} from "@/components/ShowDebug.tsx"; +import {usePB} from "@/lib/pocketbase.tsx"; const NavItems = [ @@ -101,16 +102,34 @@ const DebugMenuItems = [ } ] +const AdminMenuItems = [ + { + section: "Admin", + items: [ + { + title: "Dashboard", + icon: IconBug, + description: "Dashboard für Admins", + link: "/admin", + color: "blue" + } + ] + } +] + export default function MenuItems() { const {showDebug} = useShowDebug() + const {user} = usePB() - let nav + let nav = NavItems if (showDebug) { - nav = [...NavItems, ...DebugMenuItems] - } else { - nav = NavItems + nav = [...nav, ...DebugMenuItems] + } + + if (user?.isAdmin) { + nav = [...nav, ...AdminMenuItems] } return <> diff --git a/src/main.tsx b/src/main.tsx index a480ab0..c3929d0 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -46,9 +46,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render( - - {/* - */} + diff --git a/src/models/AnalyticsTypes.ts b/src/models/AnalyticsTypes.ts new file mode 100644 index 0000000..d3f6c59 --- /dev/null +++ b/src/models/AnalyticsTypes.ts @@ -0,0 +1,50 @@ +import {RecordModel} from "pocketbase"; + +export type AnalyticsVisitorsModel = { + meta: object; +} & RecordModel + +export type AnalyticsSessionModel = { + visitor: string + device_type?: string + browser_name?: string + browser_version?: string + operating_system?: string + operating_system_version?: string + user_agent?: string + ip_address?: string + geo_country_code?: string + expand: { + visitor: AnalyticsVisitorsModel + } +} & RecordModel + +export type AnalyticsPageViewModel = { + session: string + path: string + + expand?: { + session: AnalyticsSessionModel + } +} & RecordModel + +export type AnalyticsErrorModel = { + session: string + error_message?: string + error_type?: string + stack_trace?: string + path?: string + + expand?: { + session: AnalyticsSessionModel + } +} & RecordModel + +export type AnalyticsIpApiResult = { + ip: string + country_code: string + country_name: string + asn: string + as_desc: string + user_agent: string +} \ No newline at end of file diff --git a/src/models/AuthTypes.ts b/src/models/AuthTypes.ts index cef938e..e17a55a 100644 --- a/src/models/AuthTypes.ts +++ b/src/models/AuthTypes.ts @@ -5,6 +5,7 @@ export type UserModel = { verified: boolean; email: string; emailVisibility: boolean; + isAdmin: boolean; sn: string | null; givenName: string | null; diff --git a/src/models/index.ts b/src/models/index.ts index 5a29d0d..7cdef60 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,5 +1,5 @@ -import PocketBase, {RecordModel, RecordService} from "pocketbase"; -import {LdapGroupModel, LdapSyncLogModel, UserModel} from "./AuthTypes.ts"; +import PocketBase, {RecordModel, RecordService} from "pocketbase" +import {LdapGroupModel, LdapSyncLogModel, UserModel} from "./AuthTypes.ts" import { EventListModel, EventListSlotEntriesWithUserModel, @@ -8,8 +8,14 @@ import { EventListSlotsWithEntriesCountModel, EventModel } from "./EventTypes.ts"; -import {MessagesModel} from "@/models/MessageTypes.ts"; -import {EmailModel} from "@/models/EmailTypes.ts"; +import {MessagesModel} from "@/models/MessageTypes.ts" +import {EmailModel} from "@/models/EmailTypes.ts" +import { + AnalyticsErrorModel, + AnalyticsPageViewModel, + AnalyticsSessionModel, + AnalyticsVisitorsModel +} from "@/models/AnalyticsTypes.ts" export type SettingsModel = { key: ['privacyPolicy', 'agb', 'stexGroup', 'stuveEventQuestions'] @@ -53,4 +59,12 @@ export interface TypedPocketBase extends PocketBase { collection(idOrName: 'eventListSlotEntriesWithUser'): RecordService collection(idOrName: 'emails'): RecordService + + collection(idOrName: 'analyticsVisitors'): RecordService + + collection(idOrName: 'analyticsSessions'): RecordService + + collection(idOrName: 'analyticsPageViews'): RecordService + + collection(idOrName: 'analyticsErrors'): RecordService } \ No newline at end of file diff --git a/src/pages/LegalPage.tsx b/src/pages/LegalPage.tsx index 76d0506..e72c999 100644 --- a/src/pages/LegalPage.tsx +++ b/src/pages/LegalPage.tsx @@ -4,7 +4,7 @@ import {pprintDate} from "@/lib/datetime.ts"; import {useParams} from "react-router-dom"; import {useQuery} from "@tanstack/react-query"; import {usePB} from "@/lib/pocketbase.tsx"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; export default function LegalPage() { const {page} = useParams() as { page: string } diff --git a/src/pages/admin/AdminRouter.tsx b/src/pages/admin/AdminRouter.tsx new file mode 100644 index 0000000..1770596 --- /dev/null +++ b/src/pages/admin/AdminRouter.tsx @@ -0,0 +1,18 @@ +import {usePB} from "@/lib/pocketbase.tsx"; +import NotAllowed from "@/pages/error-pages/NotAllowed.tsx"; + +export default function AdminRouter() { + + const {user} = usePB() + + if (!user?.isAdmin) { + return + } + + return ( + + Admin Panel + + ) + +} \ No newline at end of file diff --git a/src/pages/test/DebugPage.tsx b/src/pages/debug/DebugPage.tsx similarity index 81% rename from src/pages/test/DebugPage.tsx rename to src/pages/debug/DebugPage.tsx index 50385e6..6ca7383 100644 --- a/src/pages/test/DebugPage.tsx +++ b/src/pages/debug/DebugPage.tsx @@ -1,16 +1,5 @@ import ShowDebug, {useShowDebug} from "@/components/ShowDebug.tsx"; -import { - ActionIcon, - Alert, - Button, - Divider, - Group, - LoadingOverlay, - Pagination, - Text, - TextInput, - Title -} from "@mantine/core"; +import {ActionIcon, Alert, Divider, Group, LoadingOverlay, Pagination, Text, TextInput, Title} from "@mantine/core"; import {useForm} from "@mantine/form"; import {useQuery} from "@tanstack/react-query"; import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx"; @@ -18,19 +7,12 @@ import {CodeHighlight} from "@mantine/code-highlight"; import {IconRefresh} from "@tabler/icons-react"; import {RecordListOptions} from "pocketbase"; import {useState} from "react"; -import {useDisclosure} from "@mantine/hooks"; -import {EmailModal} from "@/components/EmailModal"; -import {UserModel} from "@/models/AuthTypes.ts"; -import UserInput from "@/components/users/UserInput.tsx"; export default function DebugPage() { const {showDebug} = useShowDebug() - const [users, setUsers] = useState([]) - const {pb} = usePB() - const [opened, handler] = useDisclosure() const [page, setPage] = useState(1) @@ -70,24 +52,6 @@ export default function DebugPage() { const result = formValues.values.select ? debugQuery.data?.items.map(i => i[formValues.values.select]) : debugQuery.data?.items return <> - - - - - Open Email Modal - Debug diff --git a/src/pages/email/EmailRouter.tsx b/src/pages/email/EmailRouter.tsx index 4f67087..b10ff56 100644 --- a/src/pages/email/EmailRouter.tsx +++ b/src/pages/email/EmailRouter.tsx @@ -8,7 +8,7 @@ import SentEmailsNavigation from "@/pages/email/SentEmailsNavigation.tsx"; import {usePB} from "@/lib/pocketbase.tsx"; import {useLogin} from "@/components/users/modals/hooks.ts"; import {useEffect} from "react"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import EmailView from "@/pages/email/EmailView"; const EmailIndex = () => { diff --git a/src/pages/email/SentEmailsNavigation.module.css b/src/pages/email/SentEmailsNavigation.module.css index cf013f4..5d20c37 100644 --- a/src/pages/email/SentEmailsNavigation.module.css +++ b/src/pages/email/SentEmailsNavigation.module.css @@ -54,7 +54,6 @@ } .emailListContainer { - //flex-grow: 1; overflow: auto; border: var(--border); border-radius: var(--border-radius); diff --git a/src/pages/error-pages/ErrorPage.tsx b/src/pages/error-pages/ErrorPage.tsx new file mode 100644 index 0000000..626458e --- /dev/null +++ b/src/pages/error-pages/ErrorPage.tsx @@ -0,0 +1,20 @@ +import classes from "./NotFound.module.css" + +import ErrorSVG from "@/illustrations/error.svg?react" + +export default function ErrorPage() { + return ( + + + + + S*!:#! + + + + Irgendwas ist schiefgelaufen. Bitte versuche es später erneut. + + + + ) +} \ No newline at end of file diff --git a/src/pages/error-pages/NotAllowed.tsx b/src/pages/error-pages/NotAllowed.tsx new file mode 100644 index 0000000..90f3270 --- /dev/null +++ b/src/pages/error-pages/NotAllowed.tsx @@ -0,0 +1,24 @@ +import classes from "./NotFound.module.css" + +import ErrorSVG from "@/illustrations/error.svg?react" + +export default function NotAllowed() { + return ( + + + + + Nein! + + + + Du hast nicht die nötigen Berechtigungen, um diese Seite zu nutzen. + + + + Nutze die Navigation, um zurück in vertraute Dimensionen zu gelangen. + + + + ) +} \ No newline at end of file diff --git a/src/pages/not-found/index.module.css b/src/pages/error-pages/NotFound.module.css similarity index 100% rename from src/pages/not-found/index.module.css rename to src/pages/error-pages/NotFound.module.css diff --git a/src/pages/not-found/index.page.tsx b/src/pages/error-pages/NotFound.tsx similarity index 93% rename from src/pages/not-found/index.page.tsx rename to src/pages/error-pages/NotFound.tsx index 8204419..78fb8cc 100644 --- a/src/pages/not-found/index.page.tsx +++ b/src/pages/error-pages/NotFound.tsx @@ -1,4 +1,4 @@ -import classes from "./index.module.css" +import classes from "./NotFound.module.css" import NotFoundSvg from "@/illustrations/not-found.svg?react" diff --git a/src/pages/events/EventNavigate.tsx b/src/pages/events/EventNavigate.tsx index 252ad6c..a3dfc27 100644 --- a/src/pages/events/EventNavigate.tsx +++ b/src/pages/events/EventNavigate.tsx @@ -2,7 +2,7 @@ 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 NotFound from "@/pages/error-pages/NotFound.tsx"; import {useEventRights} from "@/pages/events/util.ts"; /** diff --git a/src/pages/events/EventsRouter.tsx b/src/pages/events/EventsRouter.tsx index 4ff6c65..b62252d 100644 --- a/src/pages/events/EventsRouter.tsx +++ b/src/pages/events/EventsRouter.tsx @@ -1,5 +1,5 @@ import {Outlet, Route, Routes} from "react-router-dom"; -import NotFound from "../not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import EventOverview from "@/pages/events/EventOverview/index.page.tsx"; import EventView from "./s/EventView.tsx"; import EventNavigate from "@/pages/events/EventNavigate.tsx"; diff --git a/src/pages/events/StatusEditor/EditEntryStatus/index.tsx b/src/pages/events/StatusEditor/EditEntryStatus/index.tsx index e5e95aa..35c2eab 100644 --- a/src/pages/events/StatusEditor/EditEntryStatus/index.tsx +++ b/src/pages/events/StatusEditor/EditEntryStatus/index.tsx @@ -5,7 +5,7 @@ import {Button, Center, Collapse, Divider, Group, Loader, Text, Title} from "@ma import {getUserName} from "@/components/users/modals/util.tsx"; import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx"; import EditStatus from "@/pages/events/StatusEditor/EditEntryStatus/EditStatus.tsx"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import {humanDeltaFromNow} from "@/lib/datetime.ts"; import InnerHtml from "@/components/InnerHtml"; import {useDisclosure} from "@mantine/hooks"; diff --git a/src/pages/events/StatusEditor/StatusEditor.tsx b/src/pages/events/StatusEditor/StatusEditor.tsx index 5de60c5..5c61d25 100644 --- a/src/pages/events/StatusEditor/StatusEditor.tsx +++ b/src/pages/events/StatusEditor/StatusEditor.tsx @@ -3,7 +3,7 @@ import {Link, Navigate, Outlet, Route, Routes, useParams} from "react-router-dom import {useQuery} from "@tanstack/react-query"; import {useEventRights} from "@/pages/events/util.ts"; import {Anchor, Breadcrumbs, LoadingOverlay} from "@mantine/core"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import Index from "@/pages/events/StatusEditor/EditEntryStatus"; import EntrySearch from "@/pages/events/StatusEditor/EntrySearch.tsx"; diff --git a/src/pages/events/e/:eventId/EditEventRouter.tsx b/src/pages/events/e/:eventId/EditEventRouter.tsx index 542f150..432d1c9 100644 --- a/src/pages/events/e/:eventId/EditEventRouter.tsx +++ b/src/pages/events/e/:eventId/EditEventRouter.tsx @@ -29,7 +29,7 @@ import { IconSectionSign, IconSettings } from "@tabler/icons-react"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import EventDescription from "./EventComponents/EventDescription.tsx"; import EventAGB from "./EventComponents/EventAGB.tsx"; import EventData from "./EventComponents/EventData.tsx"; diff --git a/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx b/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx index 4783130..77e9096 100644 --- a/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx +++ b/src/pages/events/e/:eventId/EventLists/:listId/EventListRouter.tsx @@ -23,7 +23,7 @@ import ListEntryStatusSettings import ShowDebug from "@/components/ShowDebug.tsx"; import {pprintDateTime} from "@/lib/datetime.ts"; import {useEventRights} from "@/pages/events/util.ts"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; export default function EventListRouter({event}: { event: EventModel }) { diff --git a/src/pages/events/e/:eventId/EventLists/EventListsRouter.tsx b/src/pages/events/e/:eventId/EventLists/EventListsRouter.tsx index d358638..9fb1beb 100644 --- a/src/pages/events/e/:eventId/EventLists/EventListsRouter.tsx +++ b/src/pages/events/e/:eventId/EventLists/EventListsRouter.tsx @@ -12,7 +12,7 @@ import EditDefaultEntryStatusSchema import EditDefaultEntryQuestionSchema from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryQuestionSchema.tsx"; import {useEventRights} from "@/pages/events/util.ts"; -import NotFound from "@/pages/not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; const viewNav = [ diff --git a/src/pages/events/s/EventView.tsx b/src/pages/events/s/EventView.tsx index d7c48e5..6126d94 100644 --- a/src/pages/events/s/EventView.tsx +++ b/src/pages/events/s/EventView.tsx @@ -1,7 +1,7 @@ import {Link, useParams, useSearchParams} from "react-router-dom"; import {usePB} from "@/lib/pocketbase.tsx"; import {useQuery} from "@tanstack/react-query"; -import NotFound from "../../not-found/index.page.tsx"; +import NotFound from "@/pages/error-pages/NotFound.tsx"; import {Accordion, Alert, Anchor, Breadcrumbs, Button, Center, Group, Loader, Title} from "@mantine/core"; import PBAvatar from "@/components/PBAvatar.tsx"; import InnerHtml from "@/components/InnerHtml"; diff --git a/yarn.lock b/yarn.lock index 16ef781..648f365 100644 --- a/yarn.lock +++ b/yarn.lock @@ -168,6 +168,13 @@ dependencies: regenerator-runtime "^0.14.0" +"@babel/runtime@^7.12.5": + version "7.26.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.26.0.tgz#8600c2f595f277c60815256418b85356a65173c1" + integrity sha512-FDSOghenHTiToteC/QRlv2q3DhPZ/oOXTBoirfWNx1Cx3TMVcGWQtMMmQcSvb/JjpNeGzx8Pq/b4fKEJuWm1sw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885" @@ -1072,6 +1079,11 @@ resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.4.0.tgz#1926cde5f197d116baf7794f55bd971245540e5c" integrity sha512-6dCkjbL8vIzcLWtS6RCBx0jlYPKf2Beuyq5nNLrDDZZuyJow5qJAY0eGu6Xomp9z0WDK/BYOxT4hHNoGMDkoAg== +"@types/cookie@^0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@types/cookie/-/cookie-0.6.0.tgz#eac397f28bf1d6ae0ae081363eca2f425bedf0d5" + integrity sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA== + "@types/date-arithmetic@*": version "4.1.4" resolved "https://registry.yarnpkg.com/@types/date-arithmetic/-/date-arithmetic-4.1.4.tgz#bdb441f61a916f11af1874a8c2cf787f77ffcb94" @@ -1123,7 +1135,7 @@ dependencies: "@types/unist" "*" -"@types/hoist-non-react-statics@^3.3.1": +"@types/hoist-non-react-statics@^3.3.1", "@types/hoist-non-react-statics@^3.3.5": version "3.3.5" resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494" integrity sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg== @@ -1595,6 +1607,11 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.7.2.tgz#556369c472a2ba910f2979891b526b3436237ed7" + integrity sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w== + cosmiconfig@^8.1.3: version "8.3.6" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3" @@ -1675,6 +1692,16 @@ dequal@^2.0.0, dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== +destr@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/destr/-/destr-2.0.3.tgz#7f9e97cb3d16dbdca7be52aca1644ce402cfe449" + integrity sha512-2N3BOUU4gYMpTP24s5rF5iP7BDr7uNTCs4ozw3kf/eKfvWSIu93GEBi5m427YoyJoeOzQ5smuu4nNAPGb8idSQ== + +detect-europe-js@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/detect-europe-js/-/detect-europe-js-0.1.1.tgz#b12eda6a32cf701c0c9a5a3895818a12ad790e12" + integrity sha512-+bUXDf+tI3L4dcEuRdAFa44Amx9aEaJzoZssx7Xis4H1bXWc5fAcOP850BOj0wJPRzOdovOuOVEvrg6T+GflZA== + detect-node-es@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" @@ -2973,6 +3000,11 @@ no-case@^3.0.4: lower-case "^2.0.2" tslib "^2.0.3" +node-fetch-native@^1.6.4: + version "1.6.4" + resolved "https://registry.yarnpkg.com/node-fetch-native/-/node-fetch-native-1.6.4.tgz#679fc8fd8111266d47d7e72c379f1bed9acff06e" + integrity sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ== + node-releases@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" @@ -2988,6 +3020,15 @@ object-assign@^4.1.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== +ofetch@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/ofetch/-/ofetch-1.4.1.tgz#b6bf6b0d75ba616cef6519dd8b6385a8bae480ec" + integrity sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw== + dependencies: + destr "^2.0.3" + node-fetch-native "^1.6.4" + ufo "^1.5.4" + once@^1.3.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" @@ -3371,6 +3412,11 @@ raf-schd@^4.0.3: resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a" integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ== +react-beforeunload@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/react-beforeunload/-/react-beforeunload-2.6.0.tgz#e7ead8dc39009a205b4249619050772595bfeefd" + integrity sha512-aKrGaRNc7fZQlDnmSYrXu4cbz9QEPhScA4A2mLxhjcULDy4VILLyLhSEjg2goIw3o5LQ1zss44kmQh5LXWYGCw== + react-big-calendar@^1.11.3: version "1.11.3" resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-1.11.3.tgz#78f19f3acd11c8f8a7738ac836e83dc852fefdb9" @@ -3393,6 +3439,15 @@ react-big-calendar@^1.11.3: react-overlays "^5.2.1" uncontrollable "^7.2.1" +react-cookie@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/react-cookie/-/react-cookie-7.2.2.tgz#a7559e552ea9cca39a4b3686723a5acf504b8f84" + integrity sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg== + dependencies: + "@types/hoist-non-react-statics" "^3.3.5" + hoist-non-react-statics "^3.3.2" + universal-cookie "^7.0.0" + react-dom@^18.2.0: version "18.2.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d" @@ -3410,6 +3465,13 @@ react-dropzone@^14.2.3: file-selector "^0.6.0" prop-types "^15.8.1" +react-error-boundary@^4.1.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/react-error-boundary/-/react-error-boundary-4.1.2.tgz#bc750ad962edb8b135d6ae922c046051eb58f289" + integrity sha512-GQDxZ5Jd+Aq/qUxbCm1UtzmL/s++V7zKgE8yMktJiCQXCCFZnMZh9ng+6/Ne6PjNSXH0L9CjeOEREfRnq6Duag== + dependencies: + "@babel/runtime" "^7.12.5" + react-infinite-scroll-component@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/react-infinite-scroll-component/-/react-infinite-scroll-component-6.1.0.tgz#7e511e7aa0f728ac3e51f64a38a6079ac522407f" @@ -3939,11 +4001,23 @@ typescript@^5.0.2: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +ua-parser-js@^2.0.0-rc.1: + version "2.0.0-rc.1" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-2.0.0-rc.1.tgz#e1d3e71a144fd56abb81e195a9006c040c4ea646" + integrity sha512-H+kTJv7j04XbMuASIIrp7TwKj8rVHfGLR9dI36FAWneGHyQ6JZVwFukFgxSyM/OChmk7dxsV82R/tnEQxb1EXg== + dependencies: + detect-europe-js "^0.1.1" + uc.micro@^2.0.0, uc.micro@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== +ufo@^1.5.4: + version "1.5.4" + resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.4.tgz#16d6949674ca0c9e0fbbae1fa20a71d7b1ded754" + integrity sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ== + uncontrollable@^7.2.1: version "7.2.1" resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738" @@ -4018,6 +4092,14 @@ unist-util-visit@^5.0.0: unist-util-is "^6.0.0" unist-util-visit-parents "^6.0.0" +universal-cookie@^7.0.0: + version "7.2.2" + resolved "https://registry.yarnpkg.com/universal-cookie/-/universal-cookie-7.2.2.tgz#93ae9ec55baab89b24300473543170bb8112773c" + integrity sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ== + dependencies: + "@types/cookie" "^0.6.0" + cookie "^0.7.2" + update-browserslist-db@^1.0.13: version "1.0.15" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz#60ed9f8cba4a728b7ecf7356f641a31e3a691d97"
+ Irgendwas ist schiefgelaufen. Bitte versuche es später erneut. +
+ Du hast nicht die nötigen Berechtigungen, um diese Seite zu nutzen. +
+ Nutze die Navigation, um zurück in vertraute Dimensionen zu gelangen. +