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
|
@ -11,3 +11,7 @@ export const PB_STORAGE_KEY = "stuve-it-login-record"
|
|||
export const APP_NAME = "StuVe IT"
|
||||
export const APP_VERSION = "0.9.8 (beta)"
|
||||
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",
|
||||
"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": {
|
||||
|
|
|
@ -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: <Layout/>,
|
||||
element: <>
|
||||
<Analytics/>
|
||||
<Layout/>
|
||||
</>,
|
||||
children: [
|
||||
{
|
||||
index: true,
|
||||
|
@ -29,6 +34,10 @@ const router = createBrowserRouter([
|
|||
path: "email/*",
|
||||
element: <EmailRouter/>,
|
||||
},
|
||||
{
|
||||
path: "admin/*",
|
||||
element: <AdminRouter/>,
|
||||
},
|
||||
{
|
||||
path: "debug",
|
||||
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) {
|
||||
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 {
|
||||
|
|
|
@ -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 <>
|
||||
|
|
|
@ -47,8 +47,6 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||
<Router/>
|
||||
</PocketBaseProvider>
|
||||
<ReactQueryDevtools initialIsOpen={false}/>
|
||||
{/*
|
||||
*/}
|
||||
</QueryClientProvider>
|
||||
</ModalsProvider>
|
||||
</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;
|
||||
email: string;
|
||||
emailVisibility: boolean;
|
||||
isAdmin: boolean;
|
||||
|
||||
sn: string | null;
|
||||
givenName: string | null;
|
||||
|
|
|
@ -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<EventListSlotEntriesWithUserModel>
|
||||
|
||||
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 {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 }
|
||||
|
|
|
@ -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 {
|
||||
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<UserModel[]>([])
|
||||
|
||||
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 <>
|
||||
<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"}>
|
||||
<Title c={"orange"} order={1}>Debug</Title>
|
||||
</div>
|
|
@ -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 = () => {
|
||||
|
|
|
@ -54,7 +54,6 @@
|
|||
}
|
||||
|
||||
.emailListContainer {
|
||||
//flex-grow: 1;
|
||||
overflow: auto;
|
||||
border: var(--border);
|
||||
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"
|
||||
|
|
@ -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";
|
||||
|
||||
/**
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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 = [
|
||||
|
|
|
@ -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";
|
||||
|
|
84
yarn.lock
84
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"
|
||||
|
|
Loading…
Reference in New Issue