diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml index b513f944..ea841a8e 100644 --- a/.github/workflows/deploy-production.yml +++ b/.github/workflows/deploy-production.yml @@ -15,7 +15,7 @@ jobs: - name: Pull latest code uses: appleboy/ssh-action@v1 with: - host: ${{ vars.STAGING_HOST }} + host: ${{ vars.PRODUCTION_HOST }} username: ${{ secrets.SSH_USERNAME }} password: ${{ secrets.SSH_PASSWORD }} port: 22 @@ -26,7 +26,7 @@ jobs: - name: Deploy migration to Database uses: appleboy/ssh-action@v1 with: - host: ${{ vars.STAGING_HOST }} + host: ${{ vars.PRODUCTION_HOST }} username: ${{ secrets.SSH_USERNAME }} password: ${{ secrets.SSH_PASSWORD }} port: 22 diff --git a/.github/workflows/deploy-staging.yml b/.github/workflows/deploy-staging.yml index e3fb7c2d..20440e60 100644 --- a/.github/workflows/deploy-staging.yml +++ b/.github/workflows/deploy-staging.yml @@ -40,6 +40,7 @@ jobs: username: ${{ secrets.SSH_USERNAME }} password: ${{ secrets.SSH_PASSWORD }} port: 22 + command_timeout: 30m script: | export NVM_DIR="$HOME/.nvm" source "$NVM_DIR/nvm.sh" diff --git a/.vscode/settings.json b/.vscode/settings.json index 07bb728a..7265ca71 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -2,6 +2,7 @@ "editor.formatOnSave": true, "files.autoSave": "off", "editor.defaultFormatter": "esbenp.prettier-vscode", + "eslint.workingDirectories": [{ "mode": "auto" }], "editor.codeActionsOnSave": ["source.formatDocument", "source.fixAll.eslint"], "typescript.validate.enable": true, "typescript.tsserver.experimental.enableProjectDiagnostics": true, diff --git a/apps/core-server/.env.example b/apps/core-server/.env.example index e53b08af..5de93807 100644 --- a/apps/core-server/.env.example +++ b/apps/core-server/.env.example @@ -1,7 +1,9 @@ +REDIS_HOST=localhost +REDIS_PORT=6379 DISCORD_SERVER_PORT=3005 DISCORD_GUILD_ID=1077269395019141140 -DISCORD_OAUTH_CLIENT_ID=930384053344034846 -DISCORD_OAUTH_SECRET=96aSvmIePqFTbGc54mad0QsZfDnYwhl1 -DISCORD_BOT_TOKEN=OTMwMzg0MDUzMzQ0MDM0ODQ2.G7zIy-._hE3dTbtUv6sd7nIP2PUn3d8s-2MFk0x3nYMg8 +DISCORD_OAUTH_CLIENT_ID= +DISCORD_OAUTH_SECRET= +DISCORD_BOT_TOKEN= DISCORD_REDIRECT_URL=https://hub.premiumag.de/api/discord-redirect -NEXT_PUBLIC_DISCORD_URL=https://discord.com/oauth2/authorize?client_id=930384053344034846&response_type=code&redirect_uri=https%3A%2F%2Fhub.premiumag.de%2Fapi%2Fdiscord-redirect&scope=identify+guilds+email \ No newline at end of file +NEXT_PUBLIC_DISCORD_URL= \ No newline at end of file diff --git a/apps/core-server/index.ts b/apps/core-server/index.ts index 8c089588..69ce61c0 100644 --- a/apps/core-server/index.ts +++ b/apps/core-server/index.ts @@ -3,10 +3,19 @@ import express from "express"; import { createServer } from "http"; import router from "routes/router"; import cors from "cors"; +import { Server } from "socket.io"; +import { createAdapter } from "@socket.io/redis-adapter"; +import { pubClient, subClient } from "modules/redis"; +import "modules/chron"; const app = express(); const server = createServer(app); +export const io = new Server(server, { + adapter: createAdapter(pubClient, subClient), + cors: {}, +}); + app.use(cors()); app.use(express.json()); app.use(router); diff --git a/apps/core-server/modules/chron.ts b/apps/core-server/modules/chron.ts new file mode 100644 index 00000000..7f8764db --- /dev/null +++ b/apps/core-server/modules/chron.ts @@ -0,0 +1,161 @@ +import { MissionLog, NotificationPayload, prisma } from "@repo/db"; +import { io } from "index"; +import cron from "node-cron"; + +const removeClosedMissions = async () => { + const oldMissions = await prisma.mission.findMany({ + where: { + state: "running", + }, + }); + oldMissions.forEach(async (mission) => { + const lastAlert = (mission.missionLog as unknown as MissionLog[]).find((l) => { + return l.type === "alert-log"; + }); + + const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null; + + const aircraftsInMission = await prisma.connectedAircraft.findMany({ + where: { + stationId: { + in: mission.missionStationIds, + }, + }, + }); + + const allConnectedAircraftsInIdleStatus = aircraftsInMission.every((a) => + ["1", "2", "6"].includes(a.fmsStatus), + ); + + const allStationsInMissionChangedFromStatus4to1Or8to1 = mission.missionStationIds.every( + (stationId) => { + const status4Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => { + return ( + l.type === "station-log" && + l.data?.stationId === stationId && + l.data?.newFMSstatus === "4" + ); + }); + const status8Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => { + return ( + l.type === "station-log" && + l.data?.stationId === stationId && + l.data?.newFMSstatus === "8" + ); + }); + + const status1Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => { + return ( + l.type === "station-log" && + l.data?.stationId === stationId && + l.data?.newFMSstatus === "1" + ); + }); + const status6Log = (mission.missionLog as unknown as MissionLog[]).findIndex((l) => { + return ( + l.type === "station-log" && + l.data?.stationId === stationId && + l.data?.newFMSstatus === "6" + ); + }); + return ( + (status4Log !== -1 || status8Log !== -1) && + (status1Log !== -1 || status6Log !== -1) && + (status4Log < status1Log || + status8Log < status1Log || + status8Log < status6Log || + status1Log < status6Log) + ); + }, + ); + + const missionHastManualReactivation = (mission.missionLog as unknown as MissionLog[]).some( + (l) => l.type === "reopened-log", + ); + console.log({ + missionId: mission.publicId, + allConnectedAircraftsInIdleStatus, + lastAlertTime, + allStationsInMissionChangedFromStatus4to1Or8to1, + missionHastManualReactivation, + }); + if ( + !allConnectedAircraftsInIdleStatus // If some aircrafts are still active, do not close the mission + ) + return; + + const now = new Date(); + if (!lastAlertTime) return; + + // Case 1: Forgotten Mission, last alert more than 3 Hours ago + // Case 2: All stations in mission changed from status 4 to 1 or from status 8 to 1 + if ( + !( + now.getTime() - lastAlertTime.getTime() > 1000 * 60 * 180 || + allStationsInMissionChangedFromStatus4to1Or8to1 + ) || + missionHastManualReactivation + ) + return; + + const log: MissionLog = { + type: "completed-log", + auto: true, + timeStamp: new Date().toISOString(), + data: {}, + }; + + const updatedMission = await prisma.mission.update({ + where: { + id: mission.id, + }, + data: { + state: "finished", + missionLog: { + push: log as any, + }, + }, + }); + io.to("dispatchers").emit("new-mission", { updatedMission }); + io.to("dispatchers").emit("notification", { + type: "mission-auto-close", + status: "chron", + message: `Einsatz ${updatedMission.publicId} wurde aufgrund ${allStationsInMissionChangedFromStatus4to1Or8to1 ? "des Freimeldens aller Stationen" : "von Inaktivität"} geschlossen.`, + data: { + missionId: updatedMission.id, + publicMissionId: updatedMission.publicId, + }, + } as NotificationPayload); + console.log(`Mission ${mission.id} closed due to inactivity.`); + }); +}; + +const removeConnectedAircrafts = async () => { + const connectedAircrafts = await prisma.connectedAircraft.findMany({ + where: { + logoutTime: null, + }, + }); + + connectedAircrafts.forEach(async (aircraft) => { + const lastUpdate = new Date(aircraft.lastHeartbeat); + const now = new Date(); + if (now.getTime() - lastUpdate.getTime() > 12 * 60 * 60 * 1000) { + await prisma.connectedAircraft.update({ + where: { id: aircraft.id }, + data: { logoutTime: now }, + }); + + console.log(`Aircraft ${aircraft.id} disconnected due to inactivity.`); + } + }); +}; + +cron.schedule("*/1 * * * *", async () => { + try { + await removeClosedMissions(); + await removeConnectedAircrafts(); + } catch (error) { + console.error("Error on cron job:", error); + } +}); diff --git a/apps/core-server/modules/redis.ts b/apps/core-server/modules/redis.ts new file mode 100644 index 00000000..448fe34c --- /dev/null +++ b/apps/core-server/modules/redis.ts @@ -0,0 +1,13 @@ +import { createClient, RedisClientType } from "redis"; + +export const pubClient: RedisClientType = createClient({ + url: `redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, +}); +export const subClient: RedisClientType = pubClient.duplicate(); + +Promise.all([pubClient.connect(), subClient.connect()]).then(() => { + console.log("Redis connected"); +}); + +pubClient.on("error", (err: unknown) => console.log("Redis Client Error", err)); +subClient.on("error", (err: unknown) => console.log("Redis Client Error", err)); diff --git a/apps/core-server/package.json b/apps/core-server/package.json index 8004f5b2..210ed116 100644 --- a/apps/core-server/package.json +++ b/apps/core-server/package.json @@ -20,6 +20,7 @@ "typescript": "latest" }, "dependencies": { + "@socket.io/redis-adapter": "^8.3.0", "axios": "^1.9.0", "cors": "^2.8.5", "cron": "^4.3.1", @@ -30,6 +31,8 @@ "nodemon": "^3.1.10", "prom-client": "^15.1.3", "react": "^19.1.0", + "redis": "^5.1.1", + "socket.io": "^4.8.1", "tsx": "^4.19.4" } } diff --git a/apps/dispatch-server/index.ts b/apps/dispatch-server/index.ts index 9147703c..eeb3fe46 100644 --- a/apps/dispatch-server/index.ts +++ b/apps/dispatch-server/index.ts @@ -13,7 +13,6 @@ import { handleConnectDesktop } from "socket-events/connect-desktop"; import cookieParser from "cookie-parser"; import cors from "cors"; import { authMiddleware } from "modules/expressMiddleware"; -import "modules/chron"; const app = express(); const server = createServer(app); diff --git a/apps/dispatch-server/modules/chron.ts b/apps/dispatch-server/modules/chron.ts deleted file mode 100644 index f703c9d0..00000000 --- a/apps/dispatch-server/modules/chron.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { MissionLog, prisma } from "@repo/db"; -import cron from "node-cron"; - -const removeClosedMissions = async () => { - const oldMissions = await prisma.mission.findMany({ - where: { - state: "running", - }, - }); - oldMissions.forEach(async (mission) => { - const lastAlert = (mission.missionLog as unknown as MissionLog[]).find((l) => { - return l.type === "alert-log"; - }); - const lastAlertTime = lastAlert ? new Date(lastAlert.timeStamp) : null; - - const aircraftsInMission = await prisma.connectedAircraft.findMany({ - where: { - stationId: { - in: mission.missionStationIds, - }, - }, - }); - - if ( - !aircraftsInMission || - !aircraftsInMission.some((a) => ["1", "2", "6"].includes(a.fmsStatus)) - ) - return; - - const now = new Date(); - if (!lastAlertTime) return; - // change State to closed if last alert was more than 180 minutes ago - if (now.getTime() - lastAlertTime.getTime() < 30 * 60 * 1000) return; - const log: MissionLog = { - type: "completed-log", - auto: true, - timeStamp: new Date().toISOString(), - data: {}, - }; - - await prisma.mission.update({ - where: { - id: mission.id, - }, - data: { - state: "finished", - missionLog: { - push: log as any, - }, - }, - }); - console.log(`Mission ${mission.id} closed due to inactivity.`); - }); -}; -const removeConnectedAircrafts = async () => { - const connectedAircrafts = await prisma.connectedAircraft.findMany({ - where: { - logoutTime: null, - }, - }); - - connectedAircrafts.forEach(async (aircraft) => { - const lastUpdate = new Date(aircraft.lastHeartbeat); - const now = new Date(); - if (now.getTime() - lastUpdate.getTime() > 12 * 60 * 60 * 1000) { - await prisma.connectedAircraft.update({ - where: { id: aircraft.id }, - data: { logoutTime: now }, - }); - - console.log(`Aircraft ${aircraft.id} disconnected due to inactivity.`); - } - }); -}; - -cron.schedule("*/5 * * * *", async () => { - try { - await removeClosedMissions(); - await removeConnectedAircrafts(); - } catch (error) { - console.error("Error removing closed missions:", error); - } -}); diff --git a/apps/dispatch-server/modules/redis.ts b/apps/dispatch-server/modules/redis.ts index ff7d7415..a35cadb6 100644 --- a/apps/dispatch-server/modules/redis.ts +++ b/apps/dispatch-server/modules/redis.ts @@ -7,12 +7,6 @@ export const subClient: RedisClientType = pubClient.duplicate(); Promise.all([pubClient.connect(), subClient.connect()]).then(() => { console.log("Redis connected"); - pubClient.keys("dispatchers*").then((keys) => { - if (!keys) return; - keys.forEach(async (key) => { - await pubClient.json.del(key); - }); - }); }); pubClient.on("error", (err) => console.log("Redis Client Error", err)); diff --git a/apps/dispatch-server/routes/mission.ts b/apps/dispatch-server/routes/mission.ts index b08f56ca..5542c64a 100644 --- a/apps/dispatch-server/routes/mission.ts +++ b/apps/dispatch-server/routes/mission.ts @@ -86,7 +86,7 @@ router.patch("/:id", async (req, res) => { where: { id: Number(id) }, data: req.body, }); - io.to("dispatchers").emit("update-mission", updatedMission); + io.to("dispatchers").emit("update-mission", { updatedMission }); res.json(updatedMission); } catch (error) { console.error(error); diff --git a/apps/dispatch-server/socket-events/connect-dispatch.ts b/apps/dispatch-server/socket-events/connect-dispatch.ts index ba41eee5..ddd6528c 100644 --- a/apps/dispatch-server/socket-events/connect-dispatch.ts +++ b/apps/dispatch-server/socket-events/connect-dispatch.ts @@ -51,7 +51,9 @@ export const handleConnectDispatch = data: { publicUser: getPublicUser(user) as any, esimatedLogoutTime: - logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null, + logoffHours !== undefined && logoffMinutes !== undefined + ? getNextDateWithTime(logoffHours, logoffMinutes) + : null, lastHeartbeat: new Date().toISOString(), userId: user.id, zone: selectedZone, diff --git a/apps/dispatch-server/socket-events/connect-pilot.ts b/apps/dispatch-server/socket-events/connect-pilot.ts index efe7582b..0f9b4118 100644 --- a/apps/dispatch-server/socket-events/connect-pilot.ts +++ b/apps/dispatch-server/socket-events/connect-pilot.ts @@ -79,7 +79,9 @@ export const handleConnectPilot = data: { publicUser: getPublicUser(user) as any, esimatedLogoutTime: - logoffHours && logoffMinutes ? getNextDateWithTime(logoffHours, logoffMinutes) : null, + logoffHours !== undefined && logoffMinutes !== undefined + ? getNextDateWithTime(logoffHours, logoffMinutes) + : null, userId: userId, stationId: parseInt(stationId), lastHeartbeat: debug ? nowPlus2h.toISOString() : undefined, diff --git a/apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx b/apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx index dec6274a..09e3283b 100644 --- a/apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx +++ b/apps/dispatch/app/(app)/dispatch/_components/StationSelect.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { HpgState } from "@repo/db"; import { cn } from "@repo/shared-components"; import { useQuery } from "@tanstack/react-query"; @@ -6,7 +7,6 @@ import { getConnectedAircraftsAPI } from "_querys/aircrafts"; import { getStationsAPI } from "_querys/stations"; import { Ambulance, FireExtinguisher, Radio, Siren } from "lucide-react"; import { useEffect, useState } from "react"; -import { FieldValues } from "react-hook-form"; type MissionStationsSelectProps = { selectedStations?: number[]; @@ -54,7 +54,6 @@ export function StationsSelect({ ...(vehicleStates.hpgFireEngineState !== HpgState.NOT_REQUESTED || undefined ? ["FW"] : []), ...(vehicleStates.hpgPoliceState !== HpgState.NOT_REQUESTED || undefined ? ["POL"] : []), ]); - // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedStations, vehicleStates]); // Helper to check if a station is a vehicle and its state is NOT_REQUESTED @@ -108,8 +107,27 @@ export function StationsSelect({ menuPlacement={menuPlacement} isMulti={isMulti} onChange={(v) => { + console.log("Selected values:", v); setValue(v); - if (!isMulti) return onChange?.(v); + if (!isMulti) { + const singleValue = v as string; + const isVehicle = ["RTW", "FW", "POL"].includes(singleValue); + + const hpgAmbulanceState = + singleValue === "RTW" ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; + const hpgFireEngineState = + singleValue === "FW" ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; + const hpgPoliceState = + singleValue === "POL" ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; + + onChange?.({ + selectedStationIds: isVehicle ? [] : [Number(singleValue)], + hpgAmbulanceState, + hpgFireEngineState, + hpgPoliceState, + }); + return; + } const hpgAmbulanceState = v.includes("RTW") ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; const hpgFireEngineState = v.includes("FW") ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; const hpgPoliceState = v.includes("POL") ? HpgState.DISPATCHED : HpgState.NOT_REQUESTED; diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx index 472e71b7..4be4b3c4 100644 --- a/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx +++ b/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx @@ -3,10 +3,10 @@ import { Audio } from "../../../../_components/Audio/Audio"; import { ExitIcon, ExternalLinkIcon } from "@radix-ui/react-icons"; import Link from "next/link"; import { Settings } from "_components/navbar/Settings"; -import ModeSwitchDropdown from "_components/navbar/ModeSwitchDropdown"; import AdminPanel from "_components/navbar/AdminPanel"; import { getServerSession } from "api/auth/[...nextauth]/auth"; import { WarningAlert } from "_components/navbar/PageAlert"; +import { Radar } from "lucide-react"; export default async function Navbar() { const session = await getServerSession(); @@ -14,7 +14,7 @@ export default async function Navbar() { return (
VAR Leitstelle V2
Bitte wähle eine Kategorie aus.
VAR Operations Center
- Lautstärke sollte beim Sprechen in dem Grünen bereich bleiben + Lautstärke sollte beim Sprechen in dem Grünen bereich bleiben. Beachte das scharfe Laute + (z.B. "S" oder "Z") die Anzeige verfälschen können.
{event.message}
+ Designator: {heliport.designator} +
+ + {heliport.info} + +
{heliport.info}
+ {heliport.lat} °N, {heliport.lng} °E +
Melder Lautstärke
+ + Hier + + findest du mehr Informationen! +
Die Leitstelle baut das Gespräch mit dem Luftrettungsmittel auf.
- Die Leistelle kann als übergeordneter Gesprächsteilnehmer den Ruf - wie in - diesem Fall - auch vorzeitig beenden. -
{s.event}
{s.user}
{new Date((s as any).timestamp).toLocaleString()}
{new Date(s.timestamp).toLocaleString()}
+ + Erstellen + +
- - Erstellen - + Erstellen
Benutzer diff --git a/apps/hub/app/(app)/events/_components/EventCard.tsx b/apps/hub/app/(app)/events/_components/EventCard.tsx index de6a56ef..c2097ad3 100644 --- a/apps/hub/app/(app)/events/_components/EventCard.tsx +++ b/apps/hub/app/(app)/events/_components/EventCard.tsx @@ -17,7 +17,9 @@ export const EventCard = ({ Participants: Participant[]; }; selectedAppointments: EventAppointment[]; - appointments: EventAppointment[]; + appointments: (EventAppointment & { + Participants: { userId: string }[]; + })[]; }) => { return (
{form.formState.errors.firstname.message}
{form.formState.errors.lastname?.message}
{ ); }; - -export const PilotForm = ({ user }: { user: User }): React.JSX.Element | null => { - const [isLoading, setIsLoading] = useState(false); - - const form = useForm({ - defaultValues: { - ...user, - emailVerified: user.emailVerified ?? undefined, - }, - resolver: zodResolver(UserOptionalDefaultsSchema), - }); - - if (!user) return null; - return ( - { - form.handleSubmit(async () => { - setIsLoading(true); - }); - await updateUser(values); - setIsLoading(false); - form.reset(values); - toast.success("Deine Änderungen wurden gespeichert!", { - style: { - background: "var(--color-base-100)", - color: "var(--color-base-content)", - }, - }); - })} - > - - Pilot - - - - - NTFY room - - - - - - - Hier - - findest du mehr Informationen! - - - {form.formState.errors.settingsNtfyRoom && ( - {form.formState.errors.settingsNtfyRoom.message} - )} - - - - Speichern - - - - ); -}; diff --git a/apps/hub/app/(app)/settings/page.tsx b/apps/hub/app/(app)/settings/page.tsx index ba0139f2..240e60db 100644 --- a/apps/hub/app/(app)/settings/page.tsx +++ b/apps/hub/app/(app)/settings/page.tsx @@ -1,6 +1,6 @@ import { prisma } from "@repo/db"; import { getServerSession } from "../../api/auth/[...nextauth]/auth"; -import { ProfileForm, SocialForm, PasswordForm, PilotForm, DeleteForm } from "./_components/forms"; +import { ProfileForm, SocialForm, PasswordForm, DeleteForm } from "./_components/forms"; import { GearIcon } from "@radix-ui/react-icons"; import { Error } from "_components/Error"; @@ -48,9 +48,6 @@ export default async function Page() { - - - diff --git a/apps/hub/app/(auth)/oauth/_components/Authorize.tsx b/apps/hub/app/(auth)/oauth/_components/Authorize.tsx index 7c709d71..4a7620ef 100644 --- a/apps/hub/app/(auth)/oauth/_components/Authorize.tsx +++ b/apps/hub/app/(auth)/oauth/_components/Authorize.tsx @@ -4,7 +4,6 @@ import { Service } from "../page"; import { generateToken } from "./action"; import { useSession } from "next-auth/react"; import { useErrorBoundary } from "react-error-boundary"; -import { se } from "date-fns/locale"; import { PERMISSION } from "@repo/db"; export const Authorize = ({ service }: { service: Service }) => { diff --git a/apps/hub/app/(auth)/register/_components/Register.tsx b/apps/hub/app/(auth)/register/_components/Register.tsx index 30d31961..c9aed3e5 100644 --- a/apps/hub/app/(auth)/register/_components/Register.tsx +++ b/apps/hub/app/(auth)/register/_components/Register.tsx @@ -29,10 +29,10 @@ export const Register = () => { firstname: z .string() .min(2, { - message: "Der Vorname muss mindestens 2 Zeichen lang sein", + message: "Gib einen gültigen Vornamen ein", }) .max(30, { - message: "Der Vorname darf maximal 30 Zeichen lang sein", + message: "Gib einen gültigen Vornamen ein", }) .refine((val) => val.length === 0 || /^[A-ZÄÖÜ]/.test(val), { message: "Der Vorname muss mit einem Großbuchstaben beginnen", @@ -40,12 +40,12 @@ export const Register = () => { lastname: z .string() .min(2, { - message: "Der Nachname muss mindestens 2 Zeichen lang sein", + message: "Gib einen gültigen Nachnamen ein", }) .max(30, { - message: "Der Nachname darf maximal 30 Zeichen lang sein", + message: "Gib einen gültigen Nachnamen ein", }) - .refine((val) => val.length === 0 || /^[A-ZÄÖÜ]/.test(val), { + .refine((val) => val.length === 0 || val.includes(" ") || /^[A-ZÄÖÜ]/.test(val), { message: "Der Nachname muss mit einem Großbuchstaben beginnen", }), password: z.string().min(12, { diff --git a/apps/hub/app/(auth)/register/action.ts b/apps/hub/app/(auth)/register/action.ts index 31c4fba8..a15bf2df 100644 --- a/apps/hub/app/(auth)/register/action.ts +++ b/apps/hub/app/(auth)/register/action.ts @@ -1,6 +1,8 @@ "use server"; import { prisma, Prisma } from "@repo/db"; import bcrypt from "bcryptjs"; +import OLD_USER from "../../api/auth/[...nextauth]/var.User.json"; +import { OldUser } from "../../../types/oldUser"; export const register = async ({ password, ...user }: Omit) => { const hashedPassword = await bcrypt.hash(password, 12); @@ -26,12 +28,22 @@ export const register = async ({ password, ...user }: Omit u.email === user.email); + if (existingUser) { return { error: "Ein Nutzer mit dieser E-Mail-Adresse existiert bereits.", }; } + if (existingOldUser) { + return { + error: + "Diese Email existriert bereits in der alten Version von VAR. Bitte melde dich an oder ändere dein Passwort.", + }; + } + const newUser = prisma.user.create({ data: { ...user, diff --git a/apps/hub/app/_components/ErrorBoundary.tsx b/apps/hub/app/_components/ErrorBoundary.tsx index 3b60d886..11082aa7 100644 --- a/apps/hub/app/_components/ErrorBoundary.tsx +++ b/apps/hub/app/_components/ErrorBoundary.tsx @@ -11,10 +11,10 @@ export const CustomErrorBoundary = ({ children }: { children?: React.ReactNode } let errorTest; let errorCode = 500; if ("statusCode" in error) { - errorCode = (error as any).statusCode; + errorCode = error.statusCode; } if ("message" in error || error instanceof Error) { - errorTest = (error as any).message; + errorTest = error.message; } else if (typeof error === "string") { errorTest = error; } else { diff --git a/apps/hub/app/_components/Nav.tsx b/apps/hub/app/_components/Nav.tsx index 3b1afb5f..7db69e03 100644 --- a/apps/hub/app/_components/Nav.tsx +++ b/apps/hub/app/_components/Nav.tsx @@ -12,6 +12,7 @@ import { WarningAlert } from "./ui/PageAlert"; import { getServerSession } from "api/auth/[...nextauth]/auth"; import { Error } from "./Error"; import Image from "next/image"; +import { Plane, Radar, Workflow } from "lucide-react"; export const VerticalNav = async () => { const session = await getServerSession(); @@ -20,7 +21,7 @@ export const VerticalNav = async () => { return p.startsWith("ADMIN"); }); return ( - + Dashboard @@ -73,6 +74,11 @@ export const VerticalNav = async () => { Stichworte )} + {session.user.permissions.includes("ADMIN_HELIPORT") && ( + + Heliports + + )} {session.user.permissions.includes("ADMIN_EVENT") && ( Events @@ -101,41 +107,75 @@ export const VerticalNav = async () => { ); }; -export const HorizontalNav = () => ( - - - - - Virtual Air Rescue - HUB - - +export const HorizontalNav = async () => { + const session = await getServerSession(); + if (!session?.user) return ; + + return ( + + + + + Virtual Air Rescue - HUB + + + + + + + + + + + + + + {session.user.permissions.includes("DISPO") && ( + + + Disponent + + + )} + {session.user.permissions.includes("PILOT") && ( + + + Pilot + + + )} + + + + + Logout + + + + + - - - - - Zur Leitstelle - - - - - - Logout - - - - - - -); + ); +}; diff --git a/apps/hub/app/_components/PaginatedTable.tsx b/apps/hub/app/_components/PaginatedTable.tsx index 688ad0d9..a3af6918 100644 --- a/apps/hub/app/_components/PaginatedTable.tsx +++ b/apps/hub/app/_components/PaginatedTable.tsx @@ -1,8 +1,9 @@ "use client"; -import { useEffect, useState, useCallback, Ref, useImperativeHandle } from "react"; -import SortableTable, { Pagination, SortableTableProps } from "./Table"; +import { useState, Ref, useImperativeHandle, useEffect, useCallback } from "react"; +import SortableTable, { Pagination, RowsPerPage, SortableTableProps } from "./Table"; import { PrismaClient } from "@repo/db"; import { getData } from "./pagiantedTableActions"; +import { cn, useDebounce } from "@repo/shared-components"; export interface PaginatedTableRef { refresh: () => void; @@ -10,9 +11,9 @@ export interface PaginatedTableRef { interface PaginatedTableProps extends Omit, "data"> { prismaModel: keyof PrismaClient; - filter?: Record; - rowsPerPage?: number; - showEditButton?: boolean; + stickyHeaders?: boolean; + filter?: Record; + initialRowsPerPage?: number; searchFields?: string[]; include?: Record; strictQuery?: boolean; @@ -25,13 +26,13 @@ interface PaginatedTableProps extends Omit, "da export function PaginatedTable({ prismaModel, - rowsPerPage = 10, - showEditButton = false, + initialRowsPerPage = 30, searchFields = [], filter, include, ref, strictQuery = false, + stickyHeaders = false, leftOfSearch, rightOfSearch, leftOfPagination, @@ -39,10 +40,10 @@ export function PaginatedTable({ ...restProps }: PaginatedTableProps) { const [data, setData] = useState([]); + const [rowsPerPage, setRowsPerPage] = useState(initialRowsPerPage); const [page, setPage] = useState(0); const [total, setTotal] = useState(0); const [searchTerm, setSearchTerm] = useState(""); - const [debouncedSearchTerm, setDebouncedSearchTerm] = useState(searchTerm); const [orderBy, setOrderBy] = useState>( restProps.initialOrderBy ? restProps.initialOrderBy.reduce( @@ -54,87 +55,105 @@ export function PaginatedTable({ ) : {}, ); + const [loading, setLoading] = useState(false); - const RefreshTableData = async () => { + const refreshTableData = useCallback(async () => { + setLoading(true); getData( prismaModel, rowsPerPage, page * rowsPerPage, - debouncedSearchTerm, + searchTerm, searchFields, filter, include, orderBy, strictQuery ? restProps.columns - .filter((col: any) => "accessorKey" in col) - .map((col: any) => col.accessorKey) - .reduce((acc: Record, key: string) => { + .filter( + (col): col is { accessorKey: string } => + typeof (col as { accessorKey?: unknown }).accessorKey === "string", + ) + .map((col) => col.accessorKey) + .reduce>((acc, key) => { acc[key] = true; return acc; }, {}) : undefined, - ).then((result) => { - if (result) { - setData(result.data); - setTotal(result.total); - } - }); - }; - - useEffect(() => { - RefreshTableData(); - }, [filter, orderBy]); + ) + .then((result) => { + if (result) { + setData(result.data); + setTotal(result.total); + } + }) + .finally(() => { + setLoading(false); + }); + }, [ + prismaModel, + rowsPerPage, + page, + searchTerm, + searchFields, + filter, + include, + orderBy, + strictQuery, + restProps.columns, + ]); useImperativeHandle(ref, () => ({ refresh: () => { - RefreshTableData(); + refreshTableData(); }, })); - const debounce = (func: Function, delay: number) => { - let timer: NodeJS.Timeout; - return (...args: any[]) => { - clearTimeout(timer); - timer = setTimeout(() => func(...args), delay); - }; - }; - - const handleSearchChange = useCallback( - debounce((value: string) => { - setDebouncedSearchTerm(value); - }, 500), - [], - ); - + // useEffect to show loading spinner useEffect(() => { - RefreshTableData(); - }, [page, debouncedSearchTerm]); + setLoading(true); + }, [searchTerm, page, rowsPerPage, orderBy, filter, setLoading]); + + useDebounce( + () => { + refreshTableData(); + }, + 500, + [searchTerm, page, rowsPerPage, orderBy, filter], + ); return ( - - {leftOfSearch} - {searchFields.length > 0 && ( - { - setSearchTerm(e.target.value); - handleSearchChange(e.target.value); - setPage(0); // Reset to first page on search - }} - className="input input-bordered w-full max-w-xs justify-end" - /> - )} - {rightOfSearch} - + {(rightOfSearch || leftOfSearch || searchFields.length > 0) && ( + + + {leftOfSearch} + {loading && } + + {searchFields.length > 0 && ( + { + setSearchTerm(e.target.value); + setPage(0); // Reset to first page on search + }} + className="input input-bordered w-full max-w-xs justify-end" + /> + )} + {rightOfSearch} + + )} {!hide && ( @@ -142,7 +161,10 @@ export function PaginatedTable({ {leftOfPagination} {!hide && ( - + <> + + + > )} diff --git a/apps/hub/app/_components/QueryClient.tsx b/apps/hub/app/_components/QueryClient.tsx index 6f16d695..dc5e9540 100644 --- a/apps/hub/app/_components/QueryClient.tsx +++ b/apps/hub/app/_components/QueryClient.tsx @@ -3,7 +3,7 @@ import { toast } from "react-hot-toast"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactNode, useEffect, useState } from "react"; +import { ReactNode, useState } from "react"; export function QueryProvider({ children }: { children: ReactNode }) { const [queryClient] = useState( diff --git a/apps/hub/app/_components/Table.tsx b/apps/hub/app/_components/Table.tsx index 5eeed382..115dfce4 100644 --- a/apps/hub/app/_components/Table.tsx +++ b/apps/hub/app/_components/Table.tsx @@ -9,13 +9,11 @@ import { flexRender, } from "@tanstack/react-table"; import { ArrowLeft, ArrowRight, ChevronDown, ChevronUp } from "lucide-react"; // Icons for sorting -import Link from "next/link"; import { PrismaClient } from "@repo/db"; export interface SortableTableProps { data: TData[]; columns: ColumnDef[]; - showEditButton?: boolean; prismaModel?: keyof PrismaClient; setOrderBy?: (orderBy: Record) => void; initialOrderBy?: SortingState; @@ -26,28 +24,13 @@ export default function SortableTable({ columns, initialOrderBy = [], prismaModel, - showEditButton, setOrderBy, }: SortableTableProps) { const [sorting, setSorting] = useState(initialOrderBy); const table = useReactTable({ data, - columns: showEditButton - ? [ - ...columns, - { - header: "Actions", - cell: ({ row }) => ( - - - Edit - - - ), - }, - ] - : columns, + columns, getCoreRowModel: getCoreRowModel(), getSortedRowModel: getSortedRowModel(), onSortingChange: setSorting, @@ -103,6 +86,28 @@ export default function SortableTable({ ); } +export const RowsPerPage = ({ + rowsPerPage, + setRowsPerPage, +}: { + rowsPerPage: number; + setRowsPerPage: (rowsPerPage: number) => void; +}) => { + return ( + setRowsPerPage(Number(e.target.value))} + > + 10 + 30 + 50 + 100 + 300 + + ); +}; + export const Pagination = ({ page, totalPages, diff --git a/apps/hub/app/_components/pagiantedTableActions.ts b/apps/hub/app/_components/pagiantedTableActions.ts index ca1a67a8..341e78b6 100644 --- a/apps/hub/app/_components/pagiantedTableActions.ts +++ b/apps/hub/app/_components/pagiantedTableActions.ts @@ -23,9 +23,26 @@ export async function getData( ? { OR: [ formattedId ? { id: formattedId } : undefined, - ...searchFields.map((field) => ({ - [field]: { contains: searchTerm }, - })), + ...searchFields.map((field) => { + if (field.includes(".")) { + const parts: string[] = field.split("."); + + // Helper function to build nested object + const buildNestedFilter = (parts: string[], index = 0): any => { + if (index === parts.length - 1) { + // Reached the last part - add the contains filter + return { [parts[index] as string]: { contains: searchTerm } }; + } + + // For intermediate levels, nest the next level + return { [parts[index] as string]: buildNestedFilter(parts, index + 1) }; + }; + + return buildNestedFilter(parts); + } + + return { [field]: { contains: searchTerm } }; + }), ].filter(Boolean), ...filter, } diff --git a/apps/hub/app/_components/ui/Button.tsx b/apps/hub/app/_components/ui/Button.tsx index 5e4acc6e..dae090f4 100644 --- a/apps/hub/app/_components/ui/Button.tsx +++ b/apps/hub/app/_components/ui/Button.tsx @@ -15,7 +15,7 @@ export const Button = ({ return ( { @@ -27,7 +27,7 @@ export const Button = ({ }} > {isLoadingState && } - {props.children as any} + {props.children} ); }; diff --git a/apps/hub/app/_components/ui/DateInput.tsx b/apps/hub/app/_components/ui/DateInput.tsx index 6558d627..c95bced7 100644 --- a/apps/hub/app/_components/ui/DateInput.tsx +++ b/apps/hub/app/_components/ui/DateInput.tsx @@ -1,32 +1,24 @@ -import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker"; -import { Control, Controller, FieldValues, Path } from "react-hook-form"; -import { de } from "date-fns/locale"; -registerLocale("de", de); +import { formatDate } from "date-fns"; -interface DateInputProps - extends Omit { - control: Control; - name: Path; -} - -export const DateInput = ({ - control, - name, +export const DateInput = ({ + value, + onChange, ...props -}: DateInputProps) => { +}: Omit, "value" | "onChange"> & { + value?: Date | null; + onChange?: (date: Date) => void; +}) => { return ( - ( - field.onChange(date)} - selected={field.value} - {...(props as any)} - /> - )} + { + const date = e.target.value ? new Date(e.target.value) : null; + if (!date) return; + onChange?.(date); + }} + {...props} /> ); }; diff --git a/apps/hub/app/_components/ui/FormTextInput.tsx b/apps/hub/app/_components/ui/FormTextInput.tsx index 05436bbf..433df844 100644 --- a/apps/hub/app/_components/ui/FormTextInput.tsx +++ b/apps/hub/app/_components/ui/FormTextInput.tsx @@ -1,24 +1,18 @@ -import { DetailedHTMLProps, InputHTMLAttributes, ReactNode } from 'react'; +import { InputHTMLAttributes, ReactNode } from "react"; interface FormTextInputProps extends InputHTMLAttributes { - error: any; - Svg: ReactNode; - children?: ReactNode; + error: ReactNode; + children?: ReactNode; } -export const FormTextInput = ({ - error, - Svg, - children, - ...props -}: FormTextInputProps) => { - return ( - <> - - {children} - - - {error} - > - ); +export const FormTextInput = ({ error, children, ...props }: FormTextInputProps) => { + return ( + <> + + {children} + + + {error} + > + ); }; diff --git a/apps/hub/app/_components/ui/List.tsx b/apps/hub/app/_components/ui/List.tsx index afd62856..6e1b875d 100644 --- a/apps/hub/app/_components/ui/List.tsx +++ b/apps/hub/app/_components/ui/List.tsx @@ -1,5 +1,6 @@ -import DatePicker, { DatePickerProps, registerLocale } from "react-datepicker"; -import { Control, Controller, FieldValues, Path, PathValue } from "react-hook-form"; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { DatePickerProps, registerLocale } from "react-datepicker"; +import { Control, Controller, FieldValues, Path } from "react-hook-form"; import { de } from "date-fns/locale"; import { useState } from "react"; import { cn } from "@repo/shared-components"; @@ -44,7 +45,7 @@ export const ListInput = ({ setValue(""); }} type="button" - onSubmit={(e) => false} + onSubmit={() => false} > Hinzufügen diff --git a/apps/hub/app/_components/ui/MDEditor.tsx b/apps/hub/app/_components/ui/MDEditor.tsx index 37dd93d7..a8e1dd27 100644 --- a/apps/hub/app/_components/ui/MDEditor.tsx +++ b/apps/hub/app/_components/ui/MDEditor.tsx @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import MDEditor from "@uiw/react-md-editor"; import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form"; @@ -5,7 +6,7 @@ import { cn } from "@repo/shared-components"; interface MarkdownEditorProps { name: Path; - form: UseFormReturn; + form: UseFormReturn; formOptions?: RegisterOptions; label?: string; placeholder?: string; diff --git a/apps/hub/app/_components/ui/Select.tsx b/apps/hub/app/_components/ui/Select.tsx index 94fa04e4..edbcbd9d 100644 --- a/apps/hub/app/_components/ui/Select.tsx +++ b/apps/hub/app/_components/ui/Select.tsx @@ -1,19 +1,20 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ "use client"; import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form"; import SelectTemplate, { Props as SelectTemplateProps, StylesConfig } from "react-select"; import { cn } from "@repo/shared-components"; import dynamic from "next/dynamic"; -import { CSSProperties } from "react"; interface SelectProps extends Omit { - label?: any; + label?: React.ReactNode; name: Path; - form: UseFormReturn | any; + form: UseFormReturn; formOptions?: RegisterOptions; - // eslint-disable-next-line @typescript-eslint/no-explicit-any } -const customStyles: StylesConfig = { +type OptionType = { label: string; value: string }; + +const customStyles: StylesConfig = { control: (provided) => ({ ...provided, backgroundColor: "var(--color-base-100)", @@ -55,7 +56,6 @@ const SelectCom = ({ label = name, placeholder = label, form, - formOptions, className, ...inputProps }: SelectProps) => { @@ -74,7 +74,6 @@ const SelectCom = ({ }); } form.trigger(name); - form.Dirty; }} value={ (inputProps as any)?.isMulti diff --git a/apps/hub/app/_components/ui/Switch.tsx b/apps/hub/app/_components/ui/Switch.tsx index 094af4bd..4a6d9dc8 100644 --- a/apps/hub/app/_components/ui/Switch.tsx +++ b/apps/hub/app/_components/ui/Switch.tsx @@ -1,10 +1,11 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form"; import { cn } from "@repo/shared-components"; interface InputProps extends Omit, "form"> { name: Path; - form: UseFormReturn; + form: UseFormReturn; formOptions?: RegisterOptions; label?: string; } @@ -13,7 +14,6 @@ export const Switch = ({ name, label = name, form, - formOptions, className, ...inputProps }: InputProps) => { @@ -21,7 +21,12 @@ export const Switch = ({ {label} - + ); diff --git a/apps/hub/app/api/auth/[...nextauth]/auth.ts b/apps/hub/app/api/auth/[...nextauth]/auth.ts index 390faa8f..5344eb86 100644 --- a/apps/hub/app/api/auth/[...nextauth]/auth.ts +++ b/apps/hub/app/api/auth/[...nextauth]/auth.ts @@ -1,7 +1,7 @@ import { AuthOptions, getServerSession as getNextAuthServerSession } from "next-auth"; import { PrismaAdapter } from "@next-auth/prisma-adapter"; import Credentials from "next-auth/providers/credentials"; -import { DiscordAccount, prisma, User } from "@repo/db"; +import { prisma } from "@repo/db"; import bcrypt from "bcryptjs"; import oldUser from "./var.User.json"; import { createNewUserFromOld, OldUser } from "../../../../types/oldUser"; @@ -70,7 +70,7 @@ export const options: AuthOptions = { }, }, }, - adapter: PrismaAdapter(prisma as any), + adapter: PrismaAdapter(prisma), callbacks: { jwt: async ({ token, user }) => { if (user && "firstname" in user) { @@ -88,6 +88,7 @@ export const options: AuthOptions = { }, }); if (!dbUser) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any return null as any; } return { diff --git a/apps/hub/app/api/config/route.ts b/apps/hub/app/api/config/route.ts index 5cd7f85f..0bea3a8b 100644 --- a/apps/hub/app/api/config/route.ts +++ b/apps/hub/app/api/config/route.ts @@ -1,7 +1,7 @@ import { prisma } from "@repo/db"; import { NextResponse } from "next/server"; -export async function GET(request: Request): Promise { +export async function GET(): Promise { try { const config = await prisma.config.findFirst({ orderBy: { diff --git a/apps/hub/app/layout.tsx b/apps/hub/app/layout.tsx index 22b224a3..eb6fb6cb 100644 --- a/apps/hub/app/layout.tsx +++ b/apps/hub/app/layout.tsx @@ -7,7 +7,6 @@ import "./globals.css"; import { QueryProvider } from "_components/QueryClient"; import { prisma } from "@repo/db"; import React from "react"; -import { Error as ErrorComp } from "_components/Error"; import { Maintenance } from "@repo/shared-components"; const geistSans = Geist({ diff --git a/apps/hub/eslint.config.mjs b/apps/hub/eslint.config.mjs index e8759ff5..47794e08 100644 --- a/apps/hub/eslint.config.mjs +++ b/apps/hub/eslint.config.mjs @@ -1,4 +1,4 @@ -import { nextJsConfig } from "@repo/eslint-config/next-js"; +import nextJsConfig from "@repo/eslint-config/next-js"; /** @type {import("eslint").Linter.Config} */ export default nextJsConfig; diff --git a/apps/hub/helper/mail.ts b/apps/hub/helper/mail.ts index 74ac9fad..01c1b66c 100644 --- a/apps/hub/helper/mail.ts +++ b/apps/hub/helper/mail.ts @@ -19,7 +19,7 @@ export const sendMailByTemplate = async ( | "email-verification" | "ban-notice" | "timeban-notice", - data: any, + data: unknown, ) => { try { await fetch(`${process.env.NEXT_PUBLIC_HUB_SERVER_URL}/mail/template/${template}`, { diff --git a/apps/hub/helper/moodle.ts b/apps/hub/helper/moodle.ts index 6ed72609..b2bc9562 100644 --- a/apps/hub/helper/moodle.ts +++ b/apps/hub/helper/moodle.ts @@ -25,7 +25,7 @@ export const enrollUserInCourse = async (courseid: number | string, userid: numb ); return enrollmentResponse; } catch (error) { - return new Error("Failed to enroll user in course"); + return error; } }; diff --git a/apps/hub/next.config.ts b/apps/hub/next.config.ts index 91adb3e6..462b9e72 100644 --- a/apps/hub/next.config.ts +++ b/apps/hub/next.config.ts @@ -1,5 +1,5 @@ /** @type {import('next').NextConfig} */ -const removeImports = require("next-remove-imports")(); +/* const removeImports = require("next-remove-imports")(); */ /* const nextConfig = removeImports({}); */ const nextConfig = {}; diff --git a/apps/hub/package.json b/apps/hub/package.json index 01db4d23..77689001 100644 --- a/apps/hub/package.json +++ b/apps/hub/package.json @@ -31,7 +31,6 @@ "clsx": "^2.1.1", "daisyui": "^5.0.43", "date-fns": "^4.1.0", - "eslint": "^9.30.0", "eslint-config-next": "^15.3.4", "i": "^0.3.7", "jsonwebtoken": "^9.0.2", @@ -52,8 +51,13 @@ "react-select": "^5.10.1", "tailwind-merge": "^3.3.1", "tailwindcss": "^4.1.11", - "typescript": "^5.8.3", "zod": "^3.25.67", "zustand": "^5.0.6" + }, + "devDependencies": { + "@eslint/js": "^9.30.0", + "eslint": "^9.30.0", + "typescript": "^5.8.3", + "typescript-eslint": "^8.33.1" } } diff --git a/apps/hub/types/next-auth.d.ts b/apps/hub/types/next-auth.d.ts index 511b2555..5d6c6cd0 100644 --- a/apps/hub/types/next-auth.d.ts +++ b/apps/hub/types/next-auth.d.ts @@ -1,4 +1,3 @@ -import NextAuth from "next-auth"; import { User as IUser } from "@repo/db"; declare module "next-auth" { diff --git a/apps/hub/types/oldUser.ts b/apps/hub/types/oldUser.ts index 1031edc0..5993d6a4 100644 --- a/apps/hub/types/oldUser.ts +++ b/apps/hub/types/oldUser.ts @@ -1,4 +1,4 @@ -import { prisma } from "@repo/db"; +import { prisma, BADGES } from "@repo/db"; export interface OldUser { firstname: string; @@ -7,6 +7,9 @@ export interface OldUser { lastname: string; email: string; password: string; + permissions: { + permissionList: string[]; + }; settings: { privacyHideLastnameInNickname?: boolean; notifyRoomID?: string; @@ -41,17 +44,21 @@ export const createNewUserFromOld = async (oldUser: OldUser) => { .map((badge) => { switch (badge) { case "day-1-member": - return "DAY1"; + return BADGES.DAY1; case "d-1": - return "D1"; + return BADGES.D1; case "p-1": - return "P1"; + return BADGES.P1; default: return null; } }) .filter((badge) => badge !== null), - "V1Veteran", + BADGES.V1Veteran, + ...(oldUser.permissions.permissionList.includes("connect-dispatch") && + !oldUser.badges.includes("d-1") + ? [BADGES.D1] + : []), ], }, }); diff --git a/apps/hub/types/prisma.d.ts b/apps/hub/types/prisma.d.ts index bc0723f6..f1914bf5 100644 --- a/apps/hub/types/prisma.d.ts +++ b/apps/hub/types/prisma.d.ts @@ -1,14 +1,8 @@ -import { Prisma } from "@prisma/client"; -import { JsonArray, JsonObject } from "@prisma/client/runtime/library"; +/* import { JsonArray, JsonObject } from "@prisma/client/runtime/library"; declare module "@prisma/client" { - export type InputJsonValue = - | string - | number - | boolean - | null - | JsonObject - | JsonArray; + export type InputJsonValue = string | number | boolean | null | JsonObject | JsonArray; export type JsonValue = any; // Erzwingt Flexibilität } + */ diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index ea05b61e..4022fb39 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -161,21 +161,7 @@ services: networks: - postgres_network - core_network - - docs: - restart: unless-stopped - build: - context: . - dockerfile: ./apps/docs/Dockerfile - labels: - - "traefik.enable=true" - - "traefik.http.routers.docs.rule=Host(`docs.virtualairrescue.com`)" - - "traefik.http.routers.docs.entrypoints=websecure" - - "traefik.http.routers.docs.tls.certresolver=le" - - "traefik.http.services.docs.loadbalancer.server.port=80" - - networks: - - traefik + - redis_network postgres: restart: unless-stopped diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index edac13f6..168abd4d 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -160,20 +160,7 @@ services: networks: - postgres_network - core_network - - docs: - build: - context: . - dockerfile: ./apps/docs/Dockerfile - labels: - - "traefik.enable=true" - - "traefik.http.routers.docs.rule=Host(`docs.premiumag.de`)" - - "traefik.http.routers.docs.entrypoints=websecure" - - "traefik.http.routers.docs.tls.certresolver=le" - - "traefik.http.services.docs.loadbalancer.server.port=80" - - networks: - - traefik + - redis_network postgres: image: postgres:13 diff --git a/package.json b/package.json index 43eff148..3069f01e 100644 --- a/package.json +++ b/package.json @@ -24,5 +24,8 @@ "workspaces": [ "apps/*", "packages/*" - ] + ], + "dependencies": { + "eslint": "^9.30.1" + } } diff --git a/packages/database/prisma/json/MissionVehicleLog.ts b/packages/database/prisma/json/MissionVehicleLog.ts index 63646be9..7bceda67 100644 --- a/packages/database/prisma/json/MissionVehicleLog.ts +++ b/packages/database/prisma/json/MissionVehicleLog.ts @@ -73,6 +73,15 @@ export interface MissionCompletedLog { }; } +export interface MissionReopenedLog { + type: "reopened-log"; + auto: false; + timeStamp: string; + data: { + user?: PublicUser; + }; +} + export type MissionLog = | MissionStationLog | MissionMessageLog @@ -80,4 +89,5 @@ export type MissionLog = | MissionAlertLog | MissionAlertLogAuto | MissionCompletedLog - | MissionVehicleLog; + | MissionVehicleLog + | MissionReopenedLog; diff --git a/packages/database/prisma/json/SocketEvents.ts b/packages/database/prisma/json/SocketEvents.ts index 9c524dfe..dc0e6497 100644 --- a/packages/database/prisma/json/SocketEvents.ts +++ b/packages/database/prisma/json/SocketEvents.ts @@ -39,8 +39,19 @@ export interface StationStatus { }; } +export type MissionAutoClose = { + type: "mission-auto-close"; + status: "chron"; + message: string; + data: { + missionId: number; + publicMissionId: string; + }; +}; + export type NotificationPayload = | ValidationFailed | ValidationSuccess | AdminMessage - | StationStatus; + | StationStatus + | MissionAutoClose; diff --git a/packages/database/prisma/schema/heliports.prisma b/packages/database/prisma/schema/heliports.prisma new file mode 100644 index 00000000..c2b5b696 --- /dev/null +++ b/packages/database/prisma/schema/heliports.prisma @@ -0,0 +1,25 @@ +enum HeliportType { + HELIPAD + POI + MOUNTAIN +} + +model Heliport { + id Int @id @default(autoincrement()) + type HeliportType + designator String + designatorSub6 String + fir String + state String + stateShort String + siteName String + siteNameSub26 String + siteNameSub21 String + siteElevation Float? + siteElevationUnit String? + info String? + lat Float + lng Float + country String? + hospital String? +} diff --git a/packages/database/prisma/schema/migrations/20250710172949_change_vatsim_id_to_string/migration.sql b/packages/database/prisma/schema/migrations/20250710172949_change_vatsim_id_to_string/migration.sql new file mode 100644 index 00000000..aa29dc42 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250710172949_change_vatsim_id_to_string/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "users" ALTER COLUMN "vatsim_cid" SET DATA TYPE TEXT; \ No newline at end of file diff --git a/packages/database/prisma/schema/migrations/20250713055334_add_heliport/migration.sql b/packages/database/prisma/schema/migrations/20250713055334_add_heliport/migration.sql new file mode 100644 index 00000000..6a29b2db --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713055334_add_heliport/migration.sql @@ -0,0 +1,18 @@ +-- CreateTable +CREATE TABLE "Heliport" ( + "id" SERIAL NOT NULL, + "state" TEXT NOT NULL, + "stateShort" TEXT NOT NULL, + "siteName" TEXT NOT NULL, + "siteNameSub26" TEXT NOT NULL, + "siteNameSub21" TEXT NOT NULL, + "siteElevation" DOUBLE PRECISION NOT NULL, + "siteElevationUnit" TEXT NOT NULL, + "info" TEXT NOT NULL, + "lat" DOUBLE PRECISION NOT NULL, + "lng" DOUBLE PRECISION NOT NULL, + "country" TEXT NOT NULL, + "hospital" TEXT NOT NULL, + + CONSTRAINT "Heliport_pkey" PRIMARY KEY ("id") +); diff --git a/packages/database/prisma/schema/migrations/20250713055611_add_heliport_permission/migration.sql b/packages/database/prisma/schema/migrations/20250713055611_add_heliport_permission/migration.sql new file mode 100644 index 00000000..c6594a97 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713055611_add_heliport_permission/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "PERMISSION" ADD VALUE 'ADMIN_HELIPORT'; diff --git a/packages/database/prisma/schema/migrations/20250713060749_add_heliport_type/migration.sql b/packages/database/prisma/schema/migrations/20250713060749_add_heliport_type/migration.sql new file mode 100644 index 00000000..56c79423 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713060749_add_heliport_type/migration.sql @@ -0,0 +1,11 @@ +/* + Warnings: + + - Added the required column `type` to the `Heliport` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateEnum +CREATE TYPE "HeliportType" AS ENUM ('HELIPORT', 'POI'); + +-- AlterTable +ALTER TABLE "Heliport" ADD COLUMN "type" "HeliportType" NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250713061133_missing_heliport_fields/migration.sql b/packages/database/prisma/schema/migrations/20250713061133_missing_heliport_fields/migration.sql new file mode 100644 index 00000000..2acf040f --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713061133_missing_heliport_fields/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Added the required column `designator` to the `Heliport` table without a default value. This is not possible if the table is not empty. + - Added the required column `designator_sub6` to the `Heliport` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Heliport" ADD COLUMN "designator" TEXT NOT NULL, +ADD COLUMN "designator_sub6" TEXT NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250713061423_unify_heliport_name/migration.sql b/packages/database/prisma/schema/migrations/20250713061423_unify_heliport_name/migration.sql new file mode 100644 index 00000000..74e9c13c --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713061423_unify_heliport_name/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - You are about to drop the column `designator_sub6` on the `Heliport` table. All the data in the column will be lost. + - Added the required column `designatorSub6` to the `Heliport` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Heliport" DROP COLUMN "designator_sub6", +ADD COLUMN "designatorSub6" TEXT NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250713061644_add_fir/migration.sql b/packages/database/prisma/schema/migrations/20250713061644_add_fir/migration.sql new file mode 100644 index 00000000..d0b13bd4 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713061644_add_fir/migration.sql @@ -0,0 +1,8 @@ +/* + Warnings: + + - Added the required column `fir` to the `Heliport` table without a default value. This is not possible if the table is not empty. + +*/ +-- AlterTable +ALTER TABLE "Heliport" ADD COLUMN "fir" TEXT NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250713062014_rebaname_helipad_enum/migration.sql b/packages/database/prisma/schema/migrations/20250713062014_rebaname_helipad_enum/migration.sql new file mode 100644 index 00000000..7a2da0d6 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713062014_rebaname_helipad_enum/migration.sql @@ -0,0 +1,14 @@ +/* + Warnings: + + - The values [HELIPORT] on the enum `HeliportType` will be removed. If these variants are still used in the database, this will fail. + +*/ +-- AlterEnum +BEGIN; +CREATE TYPE "HeliportType_new" AS ENUM ('HELIPAD', 'POI'); +ALTER TABLE "Heliport" ALTER COLUMN "type" TYPE "HeliportType_new" USING ("type"::text::"HeliportType_new"); +ALTER TYPE "HeliportType" RENAME TO "HeliportType_old"; +ALTER TYPE "HeliportType_new" RENAME TO "HeliportType"; +DROP TYPE "HeliportType_old"; +COMMIT; diff --git a/packages/database/prisma/schema/migrations/20250713062559_helipad_fields_optional/migration.sql b/packages/database/prisma/schema/migrations/20250713062559_helipad_fields_optional/migration.sql new file mode 100644 index 00000000..12546401 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713062559_helipad_fields_optional/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "Heliport" ALTER COLUMN "siteElevation" DROP NOT NULL, +ALTER COLUMN "siteElevationUnit" DROP NOT NULL, +ALTER COLUMN "info" DROP NOT NULL, +ALTER COLUMN "hospital" DROP NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250713063038_mountain_enum_type/migration.sql b/packages/database/prisma/schema/migrations/20250713063038_mountain_enum_type/migration.sql new file mode 100644 index 00000000..d59ad67b --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250713063038_mountain_enum_type/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "HeliportType" ADD VALUE 'MOUNTAIN'; diff --git a/packages/database/prisma/schema/migrations/20250714174333_/migration.sql b/packages/database/prisma/schema/migrations/20250714174333_/migration.sql new file mode 100644 index 00000000..c9b05545 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250714174333_/migration.sql @@ -0,0 +1,10 @@ +/* + Warnings: + + - Changed the type of `country` on the `Heliport` table. No cast exists, the column would be dropped and recreated, which cannot be done if there is data, since the column is required. + +*/ +-- AlterTable + +ALTER TABLE "Heliport" DROP COLUMN "country", +ADD COLUMN "country" "Country" NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250714214301_add_agl_height_to_position_log/migration.sql b/packages/database/prisma/schema/migrations/20250714214301_add_agl_height_to_position_log/migration.sql new file mode 100644 index 00000000..ecfe779c --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250714214301_add_agl_height_to_position_log/migration.sql @@ -0,0 +1,16 @@ +/* + Warnings: + + - The `country` column on the `Heliport` table would be dropped and recreated. This will lead to data loss if there is data in the column. + - Added the required column `altAGL` to the `PositionLog` table without a default value. This is not possible if the table is not empty. + +*/ + +DELETE FROM "PositionLog"; + +-- AlterTable +ALTER TABLE "Heliport" DROP COLUMN "country", +ADD COLUMN "country" TEXT; + +-- AlterTable +ALTER TABLE "PositionLog" ADD COLUMN "altAGL" INTEGER NOT NULL; diff --git a/packages/database/prisma/schema/migrations/20250714214908_alt_agl_optional/migration.sql b/packages/database/prisma/schema/migrations/20250714214908_alt_agl_optional/migration.sql new file mode 100644 index 00000000..335f8bd6 --- /dev/null +++ b/packages/database/prisma/schema/migrations/20250714214908_alt_agl_optional/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "PositionLog" ALTER COLUMN "altAGL" DROP NOT NULL; diff --git a/packages/database/prisma/schema/positionLog.prisma b/packages/database/prisma/schema/positionLog.prisma index 593fefdc..b190c495 100644 --- a/packages/database/prisma/schema/positionLog.prisma +++ b/packages/database/prisma/schema/positionLog.prisma @@ -6,6 +6,7 @@ model PositionLog { lat Float lng Float alt Int + altAGL Int? speed Int heading Int timestamp DateTime @default(now()) diff --git a/packages/database/prisma/schema/user.prisma b/packages/database/prisma/schema/user.prisma index ee9ae70e..94a6e952 100644 --- a/packages/database/prisma/schema/user.prisma +++ b/packages/database/prisma/schema/user.prisma @@ -17,6 +17,7 @@ enum PERMISSION { ADMIN_KEYWORD ADMIN_MESSAGE ADMIN_KICK + ADMIN_HELIPORT AUDIO PILOT DISPO @@ -24,14 +25,14 @@ enum PERMISSION { } model User { - id String @id @default(uuid()) - publicId String @unique + id String @id @default(uuid()) + publicId String @unique firstname String lastname String - email String @unique + email String @unique password String - vatsimCid Int? @map(name: "vatsim_cid") - moodleId Int? @map(name: "moodle_id") + vatsimCid String? @map(name: "vatsim_cid") + moodleId Int? @map(name: "moodle_id") // Settings: pathSelected Boolean @default(false) diff --git a/packages/eslint-config/next.js b/packages/eslint-config/next.js index 13f96eee..271aa4b2 100644 --- a/packages/eslint-config/next.js +++ b/packages/eslint-config/next.js @@ -1,49 +1,37 @@ +import { defineConfig } from "eslint/config"; import js from "@eslint/js"; import eslintConfigPrettier from "eslint-config-prettier"; import tseslint from "typescript-eslint"; import pluginReactHooks from "eslint-plugin-react-hooks"; -import pluginReact from "eslint-plugin-react"; import globals from "globals"; import pluginNext from "@next/eslint-plugin-next"; -import { config as baseConfig } from "./base.js"; -/** - * A custom ESLint configuration for libraries that use Next.js. - * - * @type {import("eslint").Linter.Config} - * */ -export const nextJsConfig = [ - ...baseConfig, - js.configs.recommended, - eslintConfigPrettier, - ...tseslint.configs.recommended, +export default defineConfig([ { - ...pluginReact.configs.flat.recommended, - languageOptions: { - ...pluginReact.configs.flat.recommended.languageOptions, - globals: { - ...globals.serviceworker, - }, - }, + ignores: ["node_modules/*", "dist/*", ".next/*", "out/*"], }, { + files: ["**/*.{js,ts,jsx,tsx}"], + languageOptions: { + globals: { + ...globals.browser, + ...globals.node, + }, + }, plugins: { + "react-hooks": pluginReactHooks, "@next/next": pluginNext, }, - rules: { - ...pluginNext.configs.recommended.rules, - ...pluginNext.configs["core-web-vitals"].rules, - }, - }, - { - plugins: { - "react-hooks": pluginReactHooks, - }, - settings: { react: { version: "detect" } }, rules: { ...pluginReactHooks.configs.recommended.rules, - // React scope no longer necessary with new JSX transform. + ...pluginNext.configs.recommended.rules, "react/react-in-jsx-scope": "off", }, }, -]; + ...tseslint.config(js.configs.recommended, tseslint.configs.recommended, eslintConfigPrettier), + { + rules: { + "@typescript-eslint/no-unused-vars": "warn", + }, + }, +]); diff --git a/packages/eslint-config/package.json b/packages/eslint-config/package.json index bef55051..b2d551cd 100644 --- a/packages/eslint-config/package.json +++ b/packages/eslint-config/package.json @@ -9,15 +9,18 @@ }, "devDependencies": { "@eslint/js": "^9.17.0", - "@next/eslint-plugin-next": "^15.1.0", + "@next/eslint-plugin-next": "^15.3.3", "eslint": "^9.15.0", "eslint-config-prettier": "^9.1.0", "eslint-plugin-only-warn": "^1.1.0", - "eslint-plugin-react": "^7.37.2", - "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react": "^7.37.5", + "eslint-plugin-react-hooks": "^5.2.0", "eslint-plugin-turbo": "^2.3.0", "globals": "^15.12.0", "typescript": "^5.8.3", "typescript-eslint": "^8.15.0" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": "^8.36.0" } } diff --git a/packages/shared-components/components/Button.tsx b/packages/shared-components/components/Button.tsx new file mode 100644 index 00000000..99d9ee42 --- /dev/null +++ b/packages/shared-components/components/Button.tsx @@ -0,0 +1,43 @@ +"use client"; +import React, { + ButtonHTMLAttributes, + DetailedHTMLProps, + useEffect, + useState, + forwardRef, +} from "react"; +import { cn } from "@repo/shared-components"; + +export const Button = forwardRef< + HTMLButtonElement, + DetailedHTMLProps, HTMLButtonElement> & { + isLoading?: boolean; + } +>(({ isLoading, ...props }, ref) => { + const [isLoadingState, setIsLoadingState] = useState(isLoading); + + useEffect(() => { + setIsLoadingState(isLoading); + }, [isLoading]); + + return ( + { + if (props.onClick) { + setIsLoadingState(true); + await props.onClick(e); + setIsLoadingState(false); + } + }} + > + {isLoadingState && } + {props.children} + + ); +}); + +Button.displayName = "Button"; diff --git a/packages/shared-components/components/index.ts b/packages/shared-components/components/index.ts index 8f59c043..77206c39 100644 --- a/packages/shared-components/components/index.ts +++ b/packages/shared-components/components/index.ts @@ -1,3 +1,4 @@ export * from "./Badge"; export * from "./PenaltyDropdown"; export * from "./Maintenance"; +export * from "./Button"; diff --git a/packages/shared-components/helper/index.ts b/packages/shared-components/helper/index.ts index 6b04f846..072e7e0b 100644 --- a/packages/shared-components/helper/index.ts +++ b/packages/shared-components/helper/index.ts @@ -2,3 +2,5 @@ export * from "./cn"; export * from "./event"; export * from "./dates"; export * from "./simulatorConnected"; +export * from "./useDebounce"; +export * from "./useTimeout"; diff --git a/apps/dispatch/app/_helpers/useDebounce.ts b/packages/shared-components/helper/useDebounce.ts similarity index 95% rename from apps/dispatch/app/_helpers/useDebounce.ts rename to packages/shared-components/helper/useDebounce.ts index f6d35a8b..80e4ef2b 100644 --- a/apps/dispatch/app/_helpers/useDebounce.ts +++ b/packages/shared-components/helper/useDebounce.ts @@ -1,3 +1,4 @@ +"use client"; import { DependencyList, useEffect } from "react"; import useTimeout from "./useTimeout"; diff --git a/apps/dispatch/app/_helpers/useTimeout.ts b/packages/shared-components/helper/useTimeout.ts similarity index 98% rename from apps/dispatch/app/_helpers/useTimeout.ts rename to packages/shared-components/helper/useTimeout.ts index b71734d9..8319fb48 100644 --- a/apps/dispatch/app/_helpers/useTimeout.ts +++ b/packages/shared-components/helper/useTimeout.ts @@ -1,3 +1,4 @@ +"use client"; import { useCallback, useEffect, useRef } from "react"; export default function useTimeout(callback: () => void, delay: number) { diff --git a/packages/shared-components/package.json b/packages/shared-components/package.json index 574e5fab..193155fd 100644 --- a/packages/shared-components/package.json +++ b/packages/shared-components/package.json @@ -15,7 +15,7 @@ "tailwind-merge": "^3.3.0" }, "devDependencies": { - "@types/react": "^19.1.6", + "@types/react": "^19.1.8", "@types/react-dom": "^19.1.5", "react": "^19.1.0", "react-dom": "^19.1.0" diff --git a/packages/typescript-config/base.json b/packages/typescript-config/base.json index edc9c160..a20c637c 100644 --- a/packages/typescript-config/base.json +++ b/packages/typescript-config/base.json @@ -16,5 +16,6 @@ "skipLibCheck": true, "strict": true, "target": "ES2022" - } + }, + "exclude": ["node_modules", "dist", ".next", "out"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9c1c1e4..8f79610a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,6 +7,10 @@ settings: importers: .: + dependencies: + eslint: + specifier: ^9.30.1 + version: 9.30.1(jiti@2.4.2) devDependencies: prettier: specifier: ^3.5.3 @@ -20,6 +24,9 @@ importers: apps/core-server: dependencies: + '@socket.io/redis-adapter': + specifier: ^8.3.0 + version: 8.3.0(socket.io-adapter@2.5.5) axios: specifier: ^1.9.0 version: 1.9.0 @@ -50,6 +57,12 @@ importers: react: specifier: ^19.1.0 version: 19.1.0 + redis: + specifier: ^5.1.1 + version: 5.1.1 + socket.io: + specifier: ^4.8.1 + version: 4.8.1 tsx: specifier: ^4.19.4 version: 4.19.4 @@ -81,6 +94,9 @@ importers: apps/dispatch: dependencies: + '@eslint/eslintrc': + specifier: ^3.3.1 + version: 3.3.1 '@hookform/resolvers': specifier: ^5.1.1 version: 5.1.1(react-hook-form@7.59.0(react@19.1.0)) @@ -95,7 +111,7 @@ importers: version: 0.5.7(@types/dom-mediacapture-transform@0.1.11)(livekit-client@2.14.0(@types/dom-mediacapture-record@1.0.22)) '@next-auth/prisma-adapter': specifier: ^1.0.7 - version: 1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.1.0) @@ -147,6 +163,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + eslint-config-next: + specifier: ^15.3.4 + version: 15.3.4(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) geojson: specifier: ^0.5.0 version: 0.5.0 @@ -176,7 +195,7 @@ importers: version: 15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-auth: specifier: ^4.24.11 - version: 4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) npm: specifier: ^11.4.2 version: 11.4.2 @@ -325,9 +344,6 @@ importers: '@catppuccin/vitepress': specifier: ^0.1.2 version: 0.1.2(typescript@5.8.3) - '@repo/typescript-config': - specifier: workspace:* - version: link:../../packages/typescript-config devDependencies: vitepress: specifier: ^1.6.3 @@ -343,7 +359,7 @@ importers: version: 5.1.1(react-hook-form@7.59.0(react@19.1.0)) '@next-auth/prisma-adapter': specifier: ^1.0.7 - version: 1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) + version: 1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)) '@radix-ui/react-icons': specifier: ^1.3.2 version: 1.3.2(react@19.1.0) @@ -398,9 +414,6 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 - eslint: - specifier: ^9.30.0 - version: 9.30.0(jiti@2.4.2) eslint-config-next: specifier: ^15.3.4 version: 15.3.4(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) @@ -421,7 +434,7 @@ importers: version: 15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-auth: specifier: ^4.24.11 - version: 4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + version: 4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) next-remove-imports: specifier: ^1.0.12 version: 1.0.12(webpack@5.99.9) @@ -461,15 +474,25 @@ importers: tailwindcss: specifier: ^4.1.11 version: 4.1.11 - typescript: - specifier: ^5.8.3 - version: 5.8.3 zod: specifier: ^3.25.67 version: 3.25.67 zustand: specifier: ^5.0.6 version: 5.0.6(@types/react@19.1.8)(react@19.1.0) + devDependencies: + '@eslint/js': + specifier: ^9.30.0 + version: 9.30.0 + eslint: + specifier: ^9.30.0 + version: 9.30.0(jiti@2.4.2) + typescript: + specifier: ^5.8.3 + version: 5.8.3 + typescript-eslint: + specifier: ^8.33.1 + version: 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) apps/hub-server: dependencies: @@ -555,12 +578,16 @@ importers: version: 6.8.2(typescript@5.8.3) packages/eslint-config: + dependencies: + '@typescript-eslint/eslint-plugin': + specifier: ^8.36.0 + version: 8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) devDependencies: '@eslint/js': specifier: ^9.17.0 version: 9.28.0 '@next/eslint-plugin-next': - specifier: ^15.1.0 + specifier: ^15.3.3 version: 15.3.3 eslint: specifier: ^9.15.0 @@ -572,10 +599,10 @@ importers: specifier: ^1.1.0 version: 1.1.0 eslint-plugin-react: - specifier: ^7.37.2 + specifier: ^7.37.5 version: 7.37.5(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-react-hooks: - specifier: ^5.0.0 + specifier: ^5.2.0 version: 5.2.0(eslint@9.28.0(jiti@2.4.2)) eslint-plugin-turbo: specifier: ^2.3.0 @@ -612,11 +639,11 @@ importers: version: 3.3.0 devDependencies: '@types/react': - specifier: ^19.1.6 - version: 19.1.6 + specifier: ^19.1.8 + version: 19.1.8 '@types/react-dom': specifier: ^19.1.5 - version: 19.1.5(@types/react@19.1.6) + version: 19.1.5(@types/react@19.1.8) react: specifier: ^19.1.0 version: 19.1.0 @@ -1226,6 +1253,10 @@ packages: resolution: {integrity: sha512-Wzw3wQwPvc9sHM+NjakWTcPx11mbZyiYHuwWa/QfZ7cIRX7WK54PSk7bdyXDaoaopUcMatv1zaQvOAAO8hCdww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/js@9.30.1': + resolution: {integrity: sha512-zXhuECFlyep42KZUhWjfvsmXGX39W8K8LFb8AWXM9gSV9dQB+MrJGLKvW6Zw0Ggnbpw0VHTtrhFXYe3Gym18jg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@2.1.6': resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2561,11 +2592,11 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/eslint-plugin@8.35.0': - resolution: {integrity: sha512-ijItUYaiWuce0N1SoSMrEd0b6b6lYkYt99pqCPfybd+HKVXtEvYhICfLdwp42MhiI5mp0oq7PKEL+g1cNiz/Eg==} + '@typescript-eslint/eslint-plugin@8.36.0': + resolution: {integrity: sha512-lZNihHUVB6ZZiPBNgOQGSxUASI7UJWhT8nHyUGCnaQ28XFCw98IfrMCG3rUl1uwUWoAvodJQby2KTs79UTcrAg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.35.0 + '@typescript-eslint/parser': ^8.36.0 eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' @@ -2595,6 +2626,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/project-service@8.36.0': + resolution: {integrity: sha512-JAhQFIABkWccQYeLMrHadu/fhpzmSQ1F1KXkpzqiVxA/iYI6UnRt2trqXHt1sYEcw1mxLnB9rKMsOxXPxowN/g==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/scope-manager@8.33.1': resolution: {integrity: sha512-dM4UBtgmzHR9bS0Rv09JST0RcHYearoEoo3pG5B6GoTR9XcyeqX87FEhPo+5kTvVfKCvfHaHrcgeJQc6mrDKrA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2603,6 +2640,10 @@ packages: resolution: {integrity: sha512-+AgL5+mcoLxl1vGjwNfiWq5fLDZM1TmTPYs2UkyHfFhgERxBbqHlNjRzhThJqz+ktBqTChRYY6zwbMwy0591AA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/scope-manager@8.36.0': + resolution: {integrity: sha512-wCnapIKnDkN62fYtTGv2+RY8FlnBYA3tNm0fm91kc2BjPhV2vIjwwozJ7LToaLAyb1ca8BxrS7vT+Pvvf7RvqA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/tsconfig-utils@8.33.1': resolution: {integrity: sha512-STAQsGYbHCF0/e+ShUQ4EatXQ7ceh3fBCXkNU7/MZVKulrlq1usH7t2FhxvCpuCi5O5oi1vmVaAjrGeL71OK1g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2615,6 +2656,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/tsconfig-utils@8.36.0': + resolution: {integrity: sha512-Nhh3TIEgN18mNbdXpd5Q8mSCBnrZQeY9V7Ca3dqYvNDStNIGRmJA6dmrIPMJ0kow3C7gcQbpsG2rPzy1Ks/AnA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/type-utils@8.33.1': resolution: {integrity: sha512-1cG37d9xOkhlykom55WVwG2QRNC7YXlxMaMzqw2uPeJixBFfKWZgaP/hjAObqMN/u3fr5BrTwTnc31/L9jQ2ww==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2622,8 +2669,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/type-utils@8.35.0': - resolution: {integrity: sha512-ceNNttjfmSEoM9PW87bWLDEIaLAyR+E6BoYJQ5PfaDau37UGca9Nyq3lBk8Bw2ad0AKvYabz6wxc7DMTO2jnNA==} + '@typescript-eslint/type-utils@8.36.0': + resolution: {integrity: sha512-5aaGYG8cVDd6cxfk/ynpYzxBRZJk7w/ymto6uiyUFtdCozQIsQWh7M28/6r57Fwkbweng8qAzoMCPwSJfWlmsg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2637,6 +2684,10 @@ packages: resolution: {integrity: sha512-0mYH3emanku0vHw2aRLNGqe7EXh9WHEhi7kZzscrMDf6IIRUQ5Jk4wp1QrledE/36KtdZrVfKnE32eZCf/vaVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/types@8.36.0': + resolution: {integrity: sha512-xGms6l5cTJKQPZOKM75Dl9yBfNdGeLRsIyufewnxT4vZTrjC0ImQT4fj8QmtJK84F58uSh5HVBSANwcfiXxABQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/typescript-estree@8.33.1': resolution: {integrity: sha512-+s9LYcT8LWjdYWu7IWs7FvUxpQ/DGkdjZeE/GGulHvv8rvYwQvVaUZ6DE+j5x/prADUgSbbCWZ2nPI3usuVeOA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2649,6 +2700,12 @@ packages: peerDependencies: typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/typescript-estree@8.36.0': + resolution: {integrity: sha512-JaS8bDVrfVJX4av0jLpe4ye0BpAaUW7+tnS4Y4ETa3q7NoZgzYbN9zDQTJ8kPb5fQ4n0hliAt9tA4Pfs2zA2Hg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <5.9.0' + '@typescript-eslint/utils@8.33.1': resolution: {integrity: sha512-52HaBiEQUaRYqAXpfzWSR2U3gxk92Kw006+xZpElaPMg3C4PgM+A5LqwoQI1f9E5aZ/qlxAZxzm42WX+vn92SQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -2656,8 +2713,8 @@ packages: eslint: ^8.57.0 || ^9.0.0 typescript: '>=4.8.4 <5.9.0' - '@typescript-eslint/utils@8.35.0': - resolution: {integrity: sha512-nqoMu7WWM7ki5tPgLVsmPM8CkqtoPUG6xXGeefM5t4x3XumOEKMoUZPdi+7F+/EotukN4R9OWdmDxN80fqoZeg==} + '@typescript-eslint/utils@8.36.0': + resolution: {integrity: sha512-VOqmHu42aEMT+P2qYjylw6zP/3E/HvptRwdn/PZxyV27KhZg2IOszXod4NcXisWzPAGSS4trE/g4moNj6XmH2g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 @@ -2671,6 +2728,10 @@ packages: resolution: {integrity: sha512-zTh2+1Y8ZpmeQaQVIc/ZZxsx8UzgKJyNg1PTvjzC7WMhPSVS8bfDX34k1SrwOf016qd5RU3az2UxUNue3IfQ5g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@typescript-eslint/visitor-keys@8.36.0': + resolution: {integrity: sha512-vZrhV2lRPWDuGoxcmrzRZyxAggPL+qp3WzUrlZD+slFueDiYHxeBa34dUXPuC0RmGKzl4lS5kFJYvKCq9cnNDA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@uiw/copy-to-clipboard@1.0.17': resolution: {integrity: sha512-O2GUHV90Iw2VrSLVLK0OmNIMdZ5fgEg4NhvtwINsX+eZ/Wf6DWD0TdsK9xwV7dNRnK/UI2mQtl0a2/kRgm1m1A==} @@ -3694,6 +3755,16 @@ packages: jiti: optional: true + eslint@9.30.1: + resolution: {integrity: sha512-zmxXPNMOXmwm9E0yQLi5uqXHs7uq2UIiqEKo3Gq+3fwo1XrJ+hijAZImyF7hclW3E6oHz43Yk3RP8at6OTKflQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4394,6 +4465,7 @@ packages: livekit-client@2.14.0: resolution: {integrity: sha512-+ryoX3bFUNVWTjXsPLnPTW8O9wKUo/ZDPxCPLBeE72Ny0JVIK8QRIW0J/CZbcGCK5VRpYf+jMojKmjlztbSuOg==} + deprecated: This release is deprecated, update to @latest peerDependencies: '@types/dom-mediacapture-record': ^1 @@ -6614,6 +6686,11 @@ snapshots: eslint: 9.30.0(jiti@2.4.2) eslint-visitor-keys: 3.4.3 + '@eslint-community/eslint-utils@4.7.0(eslint@9.30.1(jiti@2.4.2))': + dependencies: + eslint: 9.30.1(jiti@2.4.2) + eslint-visitor-keys: 3.4.3 + '@eslint-community/regexpp@4.12.1': {} '@eslint/config-array@0.20.0': @@ -6662,6 +6739,8 @@ snapshots: '@eslint/js@9.30.0': {} + '@eslint/js@9.30.1': {} + '@eslint/object-schema@2.1.6': {} '@eslint/plugin-kit@0.3.1': @@ -6884,10 +6963,10 @@ snapshots: '@tybys/wasm-util': 0.9.0 optional: true - '@next-auth/prisma-adapter@1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': + '@next-auth/prisma-adapter@1.0.7(@prisma/client@6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3))(next-auth@4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0))': dependencies: '@prisma/client': 6.8.2(prisma@6.8.2(typescript@5.8.3))(typescript@5.8.3) - next-auth: 4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-auth: 4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@next/env@15.3.4': {} @@ -8598,9 +8677,9 @@ snapshots: '@types/range-parser@1.2.7': {} - '@types/react-dom@19.1.5(@types/react@19.1.6)': + '@types/react-dom@19.1.5(@types/react@19.1.8)': dependencies: - '@types/react': 19.1.6 + '@types/react': 19.1.8 '@types/react-dom@19.1.6(@types/react@19.1.8)': dependencies: @@ -8656,14 +8735,48 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/eslint-plugin@8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.33.1 + '@typescript-eslint/type-utils': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.33.1 + eslint: 9.30.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/type-utils': 8.36.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 + eslint: 9.28.0(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/regexpp': 4.12.1 '@typescript-eslint/parser': 8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/scope-manager': 8.35.0 - '@typescript-eslint/type-utils': 8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) - '@typescript-eslint/visitor-keys': 8.35.0 + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 eslint: 9.30.0(jiti@2.4.2) graphemer: 1.4.0 ignore: 7.0.5 @@ -8673,6 +8786,23 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/eslint-plugin@8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/regexpp': 4.12.1 + '@typescript-eslint/parser': 8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/type-utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.36.0 + eslint: 9.30.1(jiti@2.4.2) + graphemer: 1.4.0 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.33.1 @@ -8685,6 +8815,30 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.33.1 + '@typescript-eslint/types': 8.33.1 + '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.33.1 + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.30.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.35.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.35.0 + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.0 + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.28.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/scope-manager': 8.35.0 @@ -8697,10 +8851,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.35.0 + '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + '@typescript-eslint/visitor-keys': 8.35.0 + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/project-service@8.33.1(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.33.1(typescript@5.8.3) - '@typescript-eslint/types': 8.33.1 + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 debug: 4.4.1(supports-color@5.5.0) typescript: 5.8.3 transitivePeerDependencies: @@ -8708,8 +8874,17 @@ snapshots: '@typescript-eslint/project-service@8.35.0(typescript@5.8.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.35.0(typescript@5.8.3) - '@typescript-eslint/types': 8.35.0 + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 + debug: 4.4.1(supports-color@5.5.0) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.36.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 debug: 4.4.1(supports-color@5.5.0) typescript: 5.8.3 transitivePeerDependencies: @@ -8725,6 +8900,11 @@ snapshots: '@typescript-eslint/types': 8.35.0 '@typescript-eslint/visitor-keys': 8.35.0 + '@typescript-eslint/scope-manager@8.36.0': + dependencies: + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 + '@typescript-eslint/tsconfig-utils@8.33.1(typescript@5.8.3)': dependencies: typescript: 5.8.3 @@ -8733,6 +8913,10 @@ snapshots: dependencies: typescript: 5.8.3 + '@typescript-eslint/tsconfig-utils@8.36.0(typescript@5.8.3)': + dependencies: + typescript: 5.8.3 + '@typescript-eslint/type-utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) @@ -8744,10 +8928,10 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/type-utils@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: - '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) - '@typescript-eslint/utils': 8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) debug: 4.4.1(supports-color@5.5.0) eslint: 9.30.0(jiti@2.4.2) ts-api-utils: 2.1.0(typescript@5.8.3) @@ -8755,10 +8939,45 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/type-utils@8.36.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.28.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.36.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.30.0(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/type-utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + '@typescript-eslint/utils': 8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.30.1(jiti@2.4.2) + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/types@8.33.1': {} '@typescript-eslint/types@8.35.0': {} + '@typescript-eslint/types@8.36.0': {} + '@typescript-eslint/typescript-estree@8.33.1(typescript@5.8.3)': dependencies: '@typescript-eslint/project-service': 8.33.1(typescript@5.8.3) @@ -8791,6 +9010,22 @@ snapshots: transitivePeerDependencies: - supports-color + '@typescript-eslint/typescript-estree@8.36.0(typescript@5.8.3)': + dependencies: + '@typescript-eslint/project-service': 8.36.0(typescript@5.8.3) + '@typescript-eslint/tsconfig-utils': 8.36.0(typescript@5.8.3) + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/visitor-keys': 8.36.0 + debug: 4.4.1(supports-color@5.5.0) + fast-glob: 3.3.3 + is-glob: 4.0.3 + minimatch: 9.0.5 + semver: 7.7.2 + ts-api-utils: 2.1.0(typescript@5.8.3) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/utils@8.33.1(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) @@ -8802,17 +9037,50 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + '@typescript-eslint/utils@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': dependencies: '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) - '@typescript-eslint/scope-manager': 8.35.0 - '@typescript-eslint/types': 8.35.0 - '@typescript-eslint/typescript-estree': 8.35.0(typescript@5.8.3) + '@typescript-eslint/scope-manager': 8.33.1 + '@typescript-eslint/types': 8.33.1 + '@typescript-eslint/typescript-estree': 8.33.1(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) typescript: 5.8.3 transitivePeerDependencies: - supports-color + '@typescript-eslint/utils@8.36.0(eslint@9.28.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.28.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + eslint: 9.28.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.36.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.0(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + eslint: 9.30.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.36.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3)': + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@typescript-eslint/scope-manager': 8.36.0 + '@typescript-eslint/types': 8.36.0 + '@typescript-eslint/typescript-estree': 8.36.0(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + '@typescript-eslint/visitor-keys@8.33.1': dependencies: '@typescript-eslint/types': 8.33.1 @@ -8823,6 +9091,11 @@ snapshots: '@typescript-eslint/types': 8.35.0 eslint-visitor-keys: 4.2.1 + '@typescript-eslint/visitor-keys@8.36.0': + dependencies: + '@typescript-eslint/types': 8.36.0 + eslint-visitor-keys: 4.2.1 + '@uiw/copy-to-clipboard@1.0.17': {} '@uiw/react-markdown-preview@5.1.4(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': @@ -9710,7 +9983,7 @@ snapshots: engine.io@6.6.4: dependencies: '@types/cors': 2.8.18 - '@types/node': 22.15.29 + '@types/node': 22.15.34 accepts: 1.3.8 base64id: 2.0.0 cookie: 0.7.2 @@ -9905,7 +10178,7 @@ snapshots: dependencies: '@next/eslint-plugin-next': 15.3.4 '@rushstack/eslint-patch': 1.12.0 - '@typescript-eslint/eslint-plugin': 8.35.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) '@typescript-eslint/parser': 8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) eslint: 9.30.0(jiti@2.4.2) eslint-import-resolver-node: 0.3.9 @@ -9921,6 +10194,26 @@ snapshots: - eslint-plugin-import-x - supports-color + eslint-config-next@15.3.4(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@next/eslint-plugin-next': 15.3.4 + '@rushstack/eslint-patch': 1.12.0 + '@typescript-eslint/eslint-plugin': 8.36.0(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-react: 7.37.5(eslint@9.30.1(jiti@2.4.2)) + eslint-plugin-react-hooks: 5.2.0(eslint@9.30.1(jiti@2.4.2)) + optionalDependencies: + typescript: 5.8.3 + transitivePeerDependencies: + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + eslint-config-prettier@9.1.0(eslint@9.28.0(jiti@2.4.2)): dependencies: eslint: 9.28.0(jiti@2.4.2) @@ -9948,6 +10241,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.4.2)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.1(supports-color@5.5.0) + eslint: 9.30.1(jiti@2.4.2) + get-tsconfig: 4.10.1 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.14 + unrs-resolver: 1.9.2 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.0(jiti@2.4.2)): dependencies: debug: 3.2.7 @@ -9959,6 +10267,17 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)): + dependencies: + debug: 3.2.7 + optionalDependencies: + '@typescript-eslint/parser': 8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.30.1(jiti@2.4.2)) + transitivePeerDependencies: + - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.0(jiti@2.4.2)): dependencies: '@rtsao/scc': 1.1.0 @@ -9988,6 +10307,35 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.30.1(jiti@2.4.2) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.30.1(jiti@2.4.2)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 8.35.0(eslint@9.30.1(jiti@2.4.2))(typescript@5.8.3) + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@9.30.0(jiti@2.4.2)): dependencies: aria-query: 5.3.2 @@ -10007,6 +10355,25 @@ snapshots: safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 + eslint-plugin-jsx-a11y@6.10.2(eslint@9.30.1(jiti@2.4.2)): + dependencies: + aria-query: 5.3.2 + array-includes: 3.1.9 + array.prototype.flatmap: 1.3.3 + ast-types-flow: 0.0.8 + axe-core: 4.10.3 + axobject-query: 4.1.0 + damerau-levenshtein: 1.0.8 + emoji-regex: 9.2.2 + eslint: 9.30.1(jiti@2.4.2) + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + language-tags: 1.0.9 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + safe-regex-test: 1.1.0 + string.prototype.includes: 2.0.1 + eslint-plugin-only-warn@1.1.0: {} eslint-plugin-react-hooks@5.2.0(eslint@9.28.0(jiti@2.4.2)): @@ -10017,6 +10384,10 @@ snapshots: dependencies: eslint: 9.30.0(jiti@2.4.2) + eslint-plugin-react-hooks@5.2.0(eslint@9.30.1(jiti@2.4.2)): + dependencies: + eslint: 9.30.1(jiti@2.4.2) + eslint-plugin-react@7.37.5(eslint@9.28.0(jiti@2.4.2)): dependencies: array-includes: 3.1.9 @@ -10061,6 +10432,28 @@ snapshots: string.prototype.matchall: 4.0.12 string.prototype.repeat: 1.0.0 + eslint-plugin-react@7.37.5(eslint@9.30.1(jiti@2.4.2)): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.1 + eslint: 9.30.1(jiti@2.4.2) + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.2 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.5 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + eslint-plugin-turbo@2.5.4(eslint@9.28.0(jiti@2.4.2))(turbo@2.5.4): dependencies: dotenv: 16.0.3 @@ -10172,6 +10565,48 @@ snapshots: transitivePeerDependencies: - supports-color + eslint@9.30.1(jiti@2.4.2): + dependencies: + '@eslint-community/eslint-utils': 4.7.0(eslint@9.30.1(jiti@2.4.2)) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.21.0 + '@eslint/config-helpers': 0.3.0 + '@eslint/core': 0.14.0 + '@eslint/eslintrc': 3.3.1 + '@eslint/js': 9.30.1 + '@eslint/plugin-kit': 0.3.3 + '@humanfs/node': 0.16.6 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + ajv: 6.12.6 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.1(supports-color@5.5.0) + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.6.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.2 + natural-compare: 1.4.0 + optionator: 0.9.4 + optionalDependencies: + jiti: 2.4.2 + transitivePeerDependencies: + - supports-color + espree@10.3.0: dependencies: acorn: 8.14.1 @@ -11472,7 +11907,7 @@ snapshots: neo-async@2.6.2: {} - next-auth@4.24.11(next@15.3.4(@babel/core@7.27.7)(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): + next-auth@4.24.11(next@15.3.4(@opentelemetry/api@1.9.0)(@playwright/test@1.52.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: '@babel/runtime': 7.27.6 '@panva/hkdf': 1.2.1 @@ -12710,6 +13145,16 @@ snapshots: transitivePeerDependencies: - supports-color + typescript-eslint@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.33.1(@typescript-eslint/parser@8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3))(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/parser': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + '@typescript-eslint/utils': 8.33.1(eslint@9.30.0(jiti@2.4.2))(typescript@5.8.3) + eslint: 9.30.0(jiti@2.4.2) + typescript: 5.8.3 + transitivePeerDependencies: + - supports-color + typescript@5.8.3: {} uid2@1.0.0: {}
- - Hier - - findest du mehr Informationen! -
{form.formState.errors.settingsNtfyRoom.message}
{error}