feat(analytics): added analytics
Build and Push Docker image / build-and-push (push) Successful in 4m55s
Details
Build and Push Docker image / build-and-push (push) Successful in 4m55s
Details
This commit is contained in:
parent
50a22f6187
commit
2ace56cfab
|
@ -10,4 +10,8 @@ export const PB_STORAGE_KEY = "stuve-it-login-record"
|
||||||
// general
|
// general
|
||||||
export const APP_NAME = "StuVe IT"
|
export const APP_NAME = "StuVe IT"
|
||||||
export const APP_VERSION = "0.9.8 (beta)"
|
export const APP_VERSION = "0.9.8 (beta)"
|
||||||
export const APP_URL = "https://it.stuve.uni-ulm.de"
|
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"
|
|
@ -42,12 +42,16 @@
|
||||||
"jwt-decode": "^3.1.2",
|
"jwt-decode": "^3.1.2",
|
||||||
"ms": "^2.1.3",
|
"ms": "^2.1.3",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
|
"ofetch": "^1.4.1",
|
||||||
"papaparse": "^5.4.1",
|
"papaparse": "^5.4.1",
|
||||||
"pocketbase": "^0.19.0",
|
"pocketbase": "^0.19.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
|
"react-beforeunload": "^2.6.0",
|
||||||
"react-big-calendar": "^1.11.3",
|
"react-big-calendar": "^1.11.3",
|
||||||
|
"react-cookie": "^7.2.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-dropzone": "^14.2.3",
|
"react-dropzone": "^14.2.3",
|
||||||
|
"react-error-boundary": "^4.1.2",
|
||||||
"react-infinite-scroll-component": "^6.1.0",
|
"react-infinite-scroll-component": "^6.1.0",
|
||||||
"react-intersection-observer": "^9.10.2",
|
"react-intersection-observer": "^9.10.2",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
|
@ -57,6 +61,7 @@
|
||||||
"sanitize-html": "^2.13.0",
|
"sanitize-html": "^2.13.0",
|
||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tiptap": "^1.32.2",
|
"tiptap": "^1.32.2",
|
||||||
|
"ua-parser-js": "^2.0.0-rc.1",
|
||||||
"zod": "^3.22.4"
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
import {createBrowserRouter, RouterProvider} from "react-router-dom";
|
import {createBrowserRouter, RouterProvider} from "react-router-dom";
|
||||||
import HomePage from "./pages/home/index.page.tsx";
|
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 Layout from "@/components/layout";
|
||||||
import QRCodeGenerator from "./pages/util/qr/index.page.tsx";
|
import QRCodeGenerator from "./pages/util/qr/index.page.tsx";
|
||||||
import EventsRouter from "./pages/events/EventsRouter.tsx";
|
import EventsRouter from "./pages/events/EventsRouter.tsx";
|
||||||
import LegalPage from "@/pages/LegalPage.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 EmailRouter from "@/pages/email/EmailRouter.tsx";
|
||||||
|
import AdminRouter from "@/pages/admin/AdminRouter.tsx";
|
||||||
|
import Analytics from "@/components/analytics";
|
||||||
|
|
||||||
const router = createBrowserRouter([
|
const router = createBrowserRouter([
|
||||||
{
|
{
|
||||||
path: "/",
|
path: "/",
|
||||||
element: <Layout/>,
|
element: <>
|
||||||
|
<Analytics/>
|
||||||
|
<Layout/>
|
||||||
|
</>,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
index: true,
|
index: true,
|
||||||
|
@ -29,6 +34,10 @@ const router = createBrowserRouter([
|
||||||
path: "email/*",
|
path: "email/*",
|
||||||
element: <EmailRouter/>,
|
element: <EmailRouter/>,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "admin/*",
|
||||||
|
element: <AdminRouter/>,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: "debug",
|
path: "debug",
|
||||||
element: <DebugPage/>
|
element: <DebugPage/>
|
||||||
|
|
|
@ -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 (
|
||||||
|
<>
|
||||||
|
<Dialog opened={!visitorId} radius="md">
|
||||||
|
<div className={"stack"}>
|
||||||
|
<Text size={"sm"} c={"dimmed"}>
|
||||||
|
Wir nutzen Cookies um diese Seite zu verbessern.
|
||||||
|
</Text>
|
||||||
|
<Text size={"xs"} c={"dimmed"}>
|
||||||
|
Mit der Nutzung unserer Dienste erklärst du dich damit einverstanden,
|
||||||
|
dass wir Cookies verwenden und anonymisierte Nutzungsdaten erheben.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<div className={"group"}>
|
||||||
|
<Button
|
||||||
|
component={Link}
|
||||||
|
to={"/legal/privacy-policy"}
|
||||||
|
size={"xs"}
|
||||||
|
color={"gray"}
|
||||||
|
>
|
||||||
|
Infos
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
size={"xs"}
|
||||||
|
color={"green"}
|
||||||
|
onClick={initVisitor}
|
||||||
|
leftSection={<IconThumbUp size={16}/>}
|
||||||
|
>
|
||||||
|
Alles klar
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
|
@ -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])
|
||||||
|
}
|
|
@ -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])
|
||||||
|
}
|
|
@ -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<string | undefined>(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<AnalyticsIpApiResult>(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}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,9 +8,6 @@
|
||||||
@media (max-width: $mantine-breakpoint-sm) {
|
@media (max-width: $mantine-breakpoint-sm) {
|
||||||
padding: calc(var(--padding)/2) var(--mantine-spacing-xl);
|
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 {
|
.bottomContainer {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import {
|
||||||
IconSectionSign
|
IconSectionSign
|
||||||
} from "@tabler/icons-react";
|
} from "@tabler/icons-react";
|
||||||
import {useShowDebug} from "@/components/ShowDebug.tsx";
|
import {useShowDebug} from "@/components/ShowDebug.tsx";
|
||||||
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
|
|
||||||
|
|
||||||
const NavItems = [
|
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() {
|
export default function MenuItems() {
|
||||||
|
|
||||||
const {showDebug} = useShowDebug()
|
const {showDebug} = useShowDebug()
|
||||||
|
const {user} = usePB()
|
||||||
|
|
||||||
let nav
|
let nav = NavItems
|
||||||
|
|
||||||
if (showDebug) {
|
if (showDebug) {
|
||||||
nav = [...NavItems, ...DebugMenuItems]
|
nav = [...nav, ...DebugMenuItems]
|
||||||
} else {
|
}
|
||||||
nav = NavItems
|
|
||||||
|
if (user?.isAdmin) {
|
||||||
|
nav = [...nav, ...AdminMenuItems]
|
||||||
}
|
}
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
|
|
|
@ -46,9 +46,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||||
<PocketBaseProvider>
|
<PocketBaseProvider>
|
||||||
<Router/>
|
<Router/>
|
||||||
</PocketBaseProvider>
|
</PocketBaseProvider>
|
||||||
<ReactQueryDevtools initialIsOpen={false}/>
|
<ReactQueryDevtools initialIsOpen={false}/>
|
||||||
{/*
|
|
||||||
*/}
|
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
</MantineProvider>
|
</MantineProvider>
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -5,6 +5,7 @@ export type UserModel = {
|
||||||
verified: boolean;
|
verified: boolean;
|
||||||
email: string;
|
email: string;
|
||||||
emailVisibility: boolean;
|
emailVisibility: boolean;
|
||||||
|
isAdmin: boolean;
|
||||||
|
|
||||||
sn: string | null;
|
sn: string | null;
|
||||||
givenName: string | null;
|
givenName: string | null;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import PocketBase, {RecordModel, RecordService} from "pocketbase";
|
import PocketBase, {RecordModel, RecordService} from "pocketbase"
|
||||||
import {LdapGroupModel, LdapSyncLogModel, UserModel} from "./AuthTypes.ts";
|
import {LdapGroupModel, LdapSyncLogModel, UserModel} from "./AuthTypes.ts"
|
||||||
import {
|
import {
|
||||||
EventListModel,
|
EventListModel,
|
||||||
EventListSlotEntriesWithUserModel,
|
EventListSlotEntriesWithUserModel,
|
||||||
|
@ -8,8 +8,14 @@ import {
|
||||||
EventListSlotsWithEntriesCountModel,
|
EventListSlotsWithEntriesCountModel,
|
||||||
EventModel
|
EventModel
|
||||||
} from "./EventTypes.ts";
|
} from "./EventTypes.ts";
|
||||||
import {MessagesModel} from "@/models/MessageTypes.ts";
|
import {MessagesModel} from "@/models/MessageTypes.ts"
|
||||||
import {EmailModel} from "@/models/EmailTypes.ts";
|
import {EmailModel} from "@/models/EmailTypes.ts"
|
||||||
|
import {
|
||||||
|
AnalyticsErrorModel,
|
||||||
|
AnalyticsPageViewModel,
|
||||||
|
AnalyticsSessionModel,
|
||||||
|
AnalyticsVisitorsModel
|
||||||
|
} from "@/models/AnalyticsTypes.ts"
|
||||||
|
|
||||||
export type SettingsModel = {
|
export type SettingsModel = {
|
||||||
key: ['privacyPolicy', 'agb', 'stexGroup', 'stuveEventQuestions']
|
key: ['privacyPolicy', 'agb', 'stexGroup', 'stuveEventQuestions']
|
||||||
|
@ -53,4 +59,12 @@ export interface TypedPocketBase extends PocketBase {
|
||||||
collection(idOrName: 'eventListSlotEntriesWithUser'): RecordService<EventListSlotEntriesWithUserModel>
|
collection(idOrName: 'eventListSlotEntriesWithUser'): RecordService<EventListSlotEntriesWithUserModel>
|
||||||
|
|
||||||
collection(idOrName: 'emails'): RecordService<EmailModel>
|
collection(idOrName: 'emails'): RecordService<EmailModel>
|
||||||
|
|
||||||
|
collection(idOrName: 'analyticsVisitors'): RecordService<AnalyticsVisitorsModel>
|
||||||
|
|
||||||
|
collection(idOrName: 'analyticsSessions'): RecordService<AnalyticsSessionModel>
|
||||||
|
|
||||||
|
collection(idOrName: 'analyticsPageViews'): RecordService<AnalyticsPageViewModel>
|
||||||
|
|
||||||
|
collection(idOrName: 'analyticsErrors'): RecordService<AnalyticsErrorModel>
|
||||||
}
|
}
|
|
@ -4,7 +4,7 @@ import {pprintDate} from "@/lib/datetime.ts";
|
||||||
import {useParams} from "react-router-dom";
|
import {useParams} from "react-router-dom";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
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() {
|
export default function LegalPage() {
|
||||||
const {page} = useParams() as { page: string }
|
const {page} = useParams() as { page: string }
|
||||||
|
|
|
@ -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 <NotAllowed/>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h1>Admin Panel</h1>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -1,16 +1,5 @@
|
||||||
import ShowDebug, {useShowDebug} from "@/components/ShowDebug.tsx";
|
import ShowDebug, {useShowDebug} from "@/components/ShowDebug.tsx";
|
||||||
import {
|
import {ActionIcon, Alert, Divider, Group, LoadingOverlay, Pagination, Text, TextInput, Title} from "@mantine/core";
|
||||||
ActionIcon,
|
|
||||||
Alert,
|
|
||||||
Button,
|
|
||||||
Divider,
|
|
||||||
Group,
|
|
||||||
LoadingOverlay,
|
|
||||||
Pagination,
|
|
||||||
Text,
|
|
||||||
TextInput,
|
|
||||||
Title
|
|
||||||
} from "@mantine/core";
|
|
||||||
import {useForm} from "@mantine/form";
|
import {useForm} from "@mantine/form";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
import {PocketBaseErrorAlert, usePB} from "@/lib/pocketbase.tsx";
|
||||||
|
@ -18,19 +7,12 @@ import {CodeHighlight} from "@mantine/code-highlight";
|
||||||
import {IconRefresh} from "@tabler/icons-react";
|
import {IconRefresh} from "@tabler/icons-react";
|
||||||
import {RecordListOptions} from "pocketbase";
|
import {RecordListOptions} from "pocketbase";
|
||||||
import {useState} from "react";
|
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() {
|
export default function DebugPage() {
|
||||||
|
|
||||||
const {showDebug} = useShowDebug()
|
const {showDebug} = useShowDebug()
|
||||||
|
|
||||||
const [users, setUsers] = useState<UserModel[]>([])
|
|
||||||
|
|
||||||
const {pb} = usePB()
|
const {pb} = usePB()
|
||||||
const [opened, handler] = useDisclosure()
|
|
||||||
|
|
||||||
const [page, setPage] = useState(1)
|
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
|
const result = formValues.values.select ? debugQuery.data?.items.map(i => i[formValues.values.select]) : debugQuery.data?.items
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<UserInput
|
|
||||||
required
|
|
||||||
placeholder={"Empfängerinnen"}
|
|
||||||
variant={"filled"}
|
|
||||||
selectedRecords={users}
|
|
||||||
setSelectedRecords={setUsers}
|
|
||||||
error={formValues.errors.recipients}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EmailModal
|
|
||||||
opened={opened}
|
|
||||||
onClose={handler.close}
|
|
||||||
recipients={users}
|
|
||||||
description={"Hier kannst du eine Email an einen oder mehrere Empfänger senden."}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<Button onClick={handler.toggle}>Open Email Modal</Button>
|
|
||||||
|
|
||||||
<div className={"section"}>
|
<div className={"section"}>
|
||||||
<Title c={"orange"} order={1}>Debug</Title>
|
<Title c={"orange"} order={1}>Debug</Title>
|
||||||
</div>
|
</div>
|
|
@ -8,7 +8,7 @@ import SentEmailsNavigation from "@/pages/email/SentEmailsNavigation.tsx";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useLogin} from "@/components/users/modals/hooks.ts";
|
import {useLogin} from "@/components/users/modals/hooks.ts";
|
||||||
import {useEffect} from "react";
|
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";
|
import EmailView from "@/pages/email/EmailView";
|
||||||
|
|
||||||
const EmailIndex = () => {
|
const EmailIndex = () => {
|
||||||
|
|
|
@ -54,7 +54,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.emailListContainer {
|
.emailListContainer {
|
||||||
//flex-grow: 1;
|
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
border: var(--border);
|
border: var(--border);
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import classes from "./NotFound.module.css"
|
||||||
|
|
||||||
|
import ErrorSVG from "@/illustrations/error.svg?react"
|
||||||
|
|
||||||
|
export default function ErrorPage() {
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
|
||||||
|
<ErrorSVG className={classes.svg} aria-label={"error image"}/>
|
||||||
|
|
||||||
|
<h1 className={classes.status}>S*!:#!</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Irgendwas ist schiefgelaufen. Bitte versuche es später erneut.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
import classes from "./NotFound.module.css"
|
||||||
|
|
||||||
|
import ErrorSVG from "@/illustrations/error.svg?react"
|
||||||
|
|
||||||
|
export default function NotAllowed() {
|
||||||
|
return (
|
||||||
|
<div className={classes.container}>
|
||||||
|
|
||||||
|
<ErrorSVG className={classes.svg} aria-label={"not allowed image"}/>
|
||||||
|
|
||||||
|
<h1 className={classes.status}>Nein!</h1>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<p>
|
||||||
|
Du hast nicht die nötigen Berechtigungen, um diese Seite zu nutzen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Nutze die Navigation, um zurück in vertraute Dimensionen zu gelangen.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import classes from "./index.module.css"
|
import classes from "./NotFound.module.css"
|
||||||
|
|
||||||
import NotFoundSvg from "@/illustrations/not-found.svg?react"
|
import NotFoundSvg from "@/illustrations/not-found.svg?react"
|
||||||
|
|
|
@ -2,7 +2,7 @@ import {Navigate, useParams} from "react-router-dom";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {LoadingOverlay} from "@mantine/core";
|
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";
|
import {useEventRights} from "@/pages/events/util.ts";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import {Outlet, Route, Routes} from "react-router-dom";
|
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 EventOverview from "@/pages/events/EventOverview/index.page.tsx";
|
||||||
import EventView from "./s/EventView.tsx";
|
import EventView from "./s/EventView.tsx";
|
||||||
import EventNavigate from "@/pages/events/EventNavigate.tsx";
|
import EventNavigate from "@/pages/events/EventNavigate.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 {getUserName} from "@/components/users/modals/util.tsx";
|
||||||
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx";
|
import {RenderDateRange} from "@/pages/events/e/:eventId/EventLists/EventListComponents/RenderDateRange.tsx";
|
||||||
import EditStatus from "@/pages/events/StatusEditor/EditEntryStatus/EditStatus.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 {humanDeltaFromNow} from "@/lib/datetime.ts";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
import {useDisclosure} from "@mantine/hooks";
|
import {useDisclosure} from "@mantine/hooks";
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {Link, Navigate, Outlet, Route, Routes, useParams} from "react-router-dom
|
||||||
import {useQuery} from "@tanstack/react-query";
|
import {useQuery} from "@tanstack/react-query";
|
||||||
import {useEventRights} from "@/pages/events/util.ts";
|
import {useEventRights} from "@/pages/events/util.ts";
|
||||||
import {Anchor, Breadcrumbs, LoadingOverlay} from "@mantine/core";
|
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 Index from "@/pages/events/StatusEditor/EditEntryStatus";
|
||||||
import EntrySearch from "@/pages/events/StatusEditor/EntrySearch.tsx";
|
import EntrySearch from "@/pages/events/StatusEditor/EntrySearch.tsx";
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,7 @@ import {
|
||||||
IconSectionSign,
|
IconSectionSign,
|
||||||
IconSettings
|
IconSettings
|
||||||
} from "@tabler/icons-react";
|
} 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 EventDescription from "./EventComponents/EventDescription.tsx";
|
||||||
import EventAGB from "./EventComponents/EventAGB.tsx";
|
import EventAGB from "./EventComponents/EventAGB.tsx";
|
||||||
import EventData from "./EventComponents/EventData.tsx";
|
import EventData from "./EventComponents/EventData.tsx";
|
||||||
|
|
|
@ -23,7 +23,7 @@ import ListEntryStatusSettings
|
||||||
import ShowDebug from "@/components/ShowDebug.tsx";
|
import ShowDebug from "@/components/ShowDebug.tsx";
|
||||||
import {pprintDateTime} from "@/lib/datetime.ts";
|
import {pprintDateTime} from "@/lib/datetime.ts";
|
||||||
import {useEventRights} from "@/pages/events/util.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 }) {
|
export default function EventListRouter({event}: { event: EventModel }) {
|
||||||
|
|
|
@ -12,7 +12,7 @@ import EditDefaultEntryStatusSchema
|
||||||
import EditDefaultEntryQuestionSchema
|
import EditDefaultEntryQuestionSchema
|
||||||
from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryQuestionSchema.tsx";
|
from "@/pages/events/e/:eventId/EventLists/GeneralListSettings/EditDefaultEntryQuestionSchema.tsx";
|
||||||
import {useEventRights} from "@/pages/events/util.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";
|
||||||
|
|
||||||
|
|
||||||
const viewNav = [
|
const viewNav = [
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {Link, useParams, useSearchParams} from "react-router-dom";
|
import {Link, useParams, useSearchParams} from "react-router-dom";
|
||||||
import {usePB} from "@/lib/pocketbase.tsx";
|
import {usePB} from "@/lib/pocketbase.tsx";
|
||||||
import {useQuery} from "@tanstack/react-query";
|
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 {Accordion, Alert, Anchor, Breadcrumbs, Button, Center, Group, Loader, Title} from "@mantine/core";
|
||||||
import PBAvatar from "@/components/PBAvatar.tsx";
|
import PBAvatar from "@/components/PBAvatar.tsx";
|
||||||
import InnerHtml from "@/components/InnerHtml";
|
import InnerHtml from "@/components/InnerHtml";
|
||||||
|
|
84
yarn.lock
84
yarn.lock
|
@ -168,6 +168,13 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
regenerator-runtime "^0.14.0"
|
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":
|
"@babel/runtime@^7.20.13", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7":
|
||||||
version "7.23.2"
|
version "7.23.2"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.23.2.tgz#062b0ac103261d68a966c4c7baf2ae3e62ec3885"
|
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"
|
resolved "https://registry.yarnpkg.com/@tiptap/suggestion/-/suggestion-2.4.0.tgz#1926cde5f197d116baf7794f55bd971245540e5c"
|
||||||
integrity sha512-6dCkjbL8vIzcLWtS6RCBx0jlYPKf2Beuyq5nNLrDDZZuyJow5qJAY0eGu6Xomp9z0WDK/BYOxT4hHNoGMDkoAg==
|
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@*":
|
"@types/date-arithmetic@*":
|
||||||
version "4.1.4"
|
version "4.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@types/date-arithmetic/-/date-arithmetic-4.1.4.tgz#bdb441f61a916f11af1874a8c2cf787f77ffcb94"
|
resolved "https://registry.yarnpkg.com/@types/date-arithmetic/-/date-arithmetic-4.1.4.tgz#bdb441f61a916f11af1874a8c2cf787f77ffcb94"
|
||||||
|
@ -1123,7 +1135,7 @@
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/unist" "*"
|
"@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"
|
version "3.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz#dab7867ef789d87e2b4b0003c9d65c49cc44a494"
|
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==
|
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"
|
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a"
|
||||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
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:
|
cosmiconfig@^8.1.3:
|
||||||
version "8.3.6"
|
version "8.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-8.3.6.tgz#060a2b871d66dba6c8538ea1118ba1ac16f5fae3"
|
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"
|
resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be"
|
||||||
integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==
|
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:
|
detect-node-es@^1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493"
|
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"
|
lower-case "^2.0.2"
|
||||||
tslib "^2.0.3"
|
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:
|
node-releases@^2.0.14:
|
||||||
version "2.0.14"
|
version "2.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
|
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"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
|
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:
|
once@^1.3.0:
|
||||||
version "1.4.0"
|
version "1.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
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"
|
resolved "https://registry.yarnpkg.com/raf-schd/-/raf-schd-4.0.3.tgz#5d6c34ef46f8b2a0e880a8fcdb743efc5bfdbc1a"
|
||||||
integrity sha512-tQkJl2GRWh83ui2DiPTJz9wEiMN20syf+5oKfB03yYP7ioZcJwsIK8FjrtLwH1m7C7e+Tt2yYBlrOpdT+dyeIQ==
|
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:
|
react-big-calendar@^1.11.3:
|
||||||
version "1.11.3"
|
version "1.11.3"
|
||||||
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-1.11.3.tgz#78f19f3acd11c8f8a7738ac836e83dc852fefdb9"
|
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"
|
react-overlays "^5.2.1"
|
||||||
uncontrollable "^7.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:
|
react-dom@^18.2.0:
|
||||||
version "18.2.0"
|
version "18.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
|
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"
|
file-selector "^0.6.0"
|
||||||
prop-types "^15.8.1"
|
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:
|
react-infinite-scroll-component@^6.1.0:
|
||||||
version "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"
|
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"
|
resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78"
|
||||||
integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==
|
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:
|
uc.micro@^2.0.0, uc.micro@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
|
resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee"
|
||||||
integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==
|
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:
|
uncontrollable@^7.2.1:
|
||||||
version "7.2.1"
|
version "7.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/uncontrollable/-/uncontrollable-7.2.1.tgz#1fa70ba0c57a14d5f78905d533cf63916dc75738"
|
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-is "^6.0.0"
|
||||||
unist-util-visit-parents "^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:
|
update-browserslist-db@^1.0.13:
|
||||||
version "1.0.15"
|
version "1.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz#60ed9f8cba4a728b7ecf7356f641a31e3a691d97"
|
resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz#60ed9f8cba4a728b7ecf7356f641a31e3a691d97"
|
||||||
|
|
Loading…
Reference in New Issue