diff --git a/apps/core-server/modules/chron.ts b/apps/core-server/modules/chron.ts index 6e8e5f37..48692a50 100644 --- a/apps/core-server/modules/chron.ts +++ b/apps/core-server/modules/chron.ts @@ -1,6 +1,7 @@ import { DISCORD_ROLES, MissionLog, NotificationPayload, prisma } from "@repo/db"; import { io } from "index"; import cron from "node-cron"; +import { setUserStandardNamePermissions } from "routes/helper"; import { changeMemberRoles } from "routes/member"; const removeMission = async (id: number, reason: string) => { @@ -141,21 +142,12 @@ const removeConnectedAircrafts = async () => { }); }; const removePermissionsForBannedUsers = async () => { - const activePenalties = await prisma.penalty.findMany({ + const removePermissionsPenaltys = await prisma.penalty.findMany({ where: { - OR: [ - { - type: "BAN", - suspended: false, - }, - { - type: "TIME_BAN", - suspended: false, - until: { - gt: new Date().toISOString(), - }, - }, - ], + removePermissionApplied: false, + User: { + DiscordAccount: { isNot: null }, + }, }, include: { User: { @@ -167,16 +159,33 @@ const removePermissionsForBannedUsers = async () => { }, }); - for (const penalty of activePenalties) { - const user = penalty.User; + const addPermissionsPenaltys = await prisma.penalty.findMany({ + where: { + addPermissionApplied: false, + User: { + DiscordAccount: { isNot: null }, + }, + OR: [{ suspended: true }, { until: { lt: new Date().toISOString() } }], + }, + include: { + User: { + include: { + DiscordAccount: true, + FormerDiscordAccounts: true, + }, + }, + }, + }); - if (user.DiscordAccount) { - await changeMemberRoles( - user.DiscordAccount.discordId, - [DISCORD_ROLES.PILOT, DISCORD_ROLES.DISPATCHER], - "remove", - ); - } + for (const penalty of removePermissionsPenaltys) { + const user = penalty.User; + console.log(`Removing roles for user ${user.id} due to penalty ${penalty.id}`); + + await changeMemberRoles( + user.DiscordAccount!.discordId, + [DISCORD_ROLES.PILOT, DISCORD_ROLES.DISPATCHER], + "remove", + ); for (const formerAccount of user.FormerDiscordAccounts) { await changeMemberRoles( @@ -185,15 +194,29 @@ const removePermissionsForBannedUsers = async () => { "remove", ); } + await prisma.penalty.update({ + where: { id: penalty.id }, + data: { removePermissionApplied: true }, + }); + } + for (const penalty of addPermissionsPenaltys) { + console.log(`Restoring roles for user ${penalty.userId} due to penalty ${penalty.id}`); + await setUserStandardNamePermissions({ + memberId: penalty.User.DiscordAccount!.discordId, + userId: penalty.userId, + }); + await prisma.penalty.update({ + where: { id: penalty.id }, + data: { addPermissionApplied: true }, + }); } }; -cron.schedule("*/5 * * * *", async () => { - await removePermissionsForBannedUsers(); -}); +removePermissionsForBannedUsers(); cron.schedule("*/1 * * * *", async () => { try { + await removePermissionsForBannedUsers(); await removeClosedMissions(); await removeConnectedAircrafts(); } catch (error) { diff --git a/apps/core-server/routes/helper.ts b/apps/core-server/routes/helper.ts index d712ee86..b452b450 100644 --- a/apps/core-server/routes/helper.ts +++ b/apps/core-server/routes/helper.ts @@ -7,22 +7,25 @@ const router: Router = Router(); export const eventCompleted = (event: Event, participant?: Participant) => { if (!participant) return false; if (event.finisherMoodleCourseId && !participant.finisherMoodleCurseCompleted) return false; - if (event.hasPresenceEvents && !participant.attended) return false; return true; }; -router.post("/set-standard-name", async (req, res) => { - const { memberId, userId } = req.body; - +export const setUserStandardNamePermissions = async ({ + memberId, + userId, +}: { + memberId: string; + userId: string; +}) => { const user = await prisma.user.findUnique({ where: { id: userId, }, }); if (!user) { - res.status(404).json({ error: "User not found" }); return; } + const participant = await prisma.participant.findMany({ where: { userId: user.id, @@ -73,6 +76,13 @@ router.post("/set-standard-name", async (req, res) => { await changeMemberRoles(memberId, [DISCORD_ROLES.PILOT], isPilot ? "add" : "remove"); await changeMemberRoles(memberId, [DISCORD_ROLES.DISPATCHER], isDispatcher ? "add" : "remove"); } +}; + +router.post("/set-standard-name", async (req, res) => { + const { memberId, userId } = req.body; + + await setUserStandardNamePermissions({ memberId, userId }); + res.status(200).json({ message: "Standard name and permissions set" }); }); export default router; diff --git a/apps/core-server/routes/member.ts b/apps/core-server/routes/member.ts index 3625daef..61bc376c 100644 --- a/apps/core-server/routes/member.ts +++ b/apps/core-server/routes/member.ts @@ -12,7 +12,11 @@ export const getMember = async (memberId: string) => { const guild = client.guilds.cache.get(GUILD_ID); if (!guild) throw new Error("Guild not found"); try { - return guild.members.cache.get(memberId) ?? (await guild.members.fetch(memberId)); + let member = guild.members.cache.get(memberId); + if (!member) { + member = await guild.members.fetch(memberId); + } + return member; } catch (error) { console.error("Error fetching member:", error); throw new Error("Member not found"); diff --git a/apps/dispatch/app/(app)/_components/Navbar.tsx b/apps/dispatch/app/(app)/_components/Navbar.tsx new file mode 100644 index 00000000..0bcb448c --- /dev/null +++ b/apps/dispatch/app/(app)/_components/Navbar.tsx @@ -0,0 +1,32 @@ +import { ExitIcon } from "@radix-ui/react-icons"; +import Link from "next/link"; +import { prisma } from "@repo/db"; +import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper"; +import ModeSwitchDropdown from "_components/navbar/ModeSwitchDropdown"; + +export default async function Navbar({ children }: { children: React.ReactNode }) { + const latestChangelog = await prisma.changelog.findFirst({ + orderBy: { + createdAt: "desc", + }, + }); + return ( +
+
+
+

VAR Operations Center

+ +
+
+
+ {children} + + + + +
+
+ ); +} diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/Connection.tsx similarity index 98% rename from apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx rename to apps/dispatch/app/(app)/dispatch/_components/navbar/Connection.tsx index 039d7ac2..c09dec31 100644 --- a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Connection.tsx +++ b/apps/dispatch/app/(app)/dispatch/_components/navbar/Connection.tsx @@ -1,7 +1,7 @@ /* eslint-disable react-hooks/exhaustive-deps */ "use client"; import { useSession } from "next-auth/react"; -import { useDispatchConnectionStore } from "../../../../../_store/dispatch/connectionStore"; +import { useDispatchConnectionStore } from "../../../../_store/dispatch/connectionStore"; import { useEffect, useRef, useState } from "react"; import { useMutation } from "@tanstack/react-query"; import { Prisma } from "@repo/db"; diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx deleted file mode 100644 index d1bd729e..00000000 --- a/apps/dispatch/app/(app)/dispatch/_components/navbar/Navbar.tsx +++ /dev/null @@ -1,63 +0,0 @@ -import { Connection } from "./_components/Connection"; -import { Audio } from "../../../../_components/Audio/Audio"; -import { ExitIcon, ExternalLinkIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; -import { Settings } from "./_components/Settings"; -import AdminPanel from "_components/navbar/AdminPanel"; -import { getServerSession } from "api/auth/[...nextauth]/auth"; -import { WarningAlert } from "_components/navbar/PageAlert"; -import { Radar } from "lucide-react"; -import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper"; -import { prisma } from "@repo/db"; - -export default async function Navbar() { - const session = await getServerSession(); - const latestChangelog = await prisma.changelog.findFirst({ - orderBy: { - createdAt: "desc", - }, - }); - - return ( -
-
-
-

VAR Leitstelle

- -
- {session?.user.permissions.includes("ADMIN_KICK") && } -
- -
-
-
-
- -
-
- - - - - - - - - - -
-
-
- ); -} diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Settings.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/Settings.tsx similarity index 100% rename from apps/dispatch/app/(app)/dispatch/_components/navbar/_components/Settings.tsx rename to apps/dispatch/app/(app)/dispatch/_components/navbar/Settings.tsx diff --git a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/ThemeSwap.tsx b/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/ThemeSwap.tsx deleted file mode 100644 index dec2a6fd..00000000 --- a/apps/dispatch/app/(app)/dispatch/_components/navbar/_components/ThemeSwap.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; - -interface ThemeSwapProps { - isDark: boolean; - toggleTheme: () => void; -} - -export const ThemeSwap: React.FC = ({ - isDark, - toggleTheme, -}) => { - return ( - - ); -}; diff --git a/apps/dispatch/app/(app)/dispatch/layout.tsx b/apps/dispatch/app/(app)/dispatch/layout.tsx index bade8d8d..3c1da056 100644 --- a/apps/dispatch/app/(app)/dispatch/layout.tsx +++ b/apps/dispatch/app/(app)/dispatch/layout.tsx @@ -1,7 +1,10 @@ import type { Metadata } from "next"; -import Navbar from "./_components/navbar/Navbar"; import { getServerSession } from "api/auth/[...nextauth]/auth"; import { Error } from "_components/Error"; +import Navbar from "(app)/_components/Navbar"; +import { Audio } from "_components/Audio/Audio"; +import { Connection } from "./_components/navbar/Connection"; +import { Settings } from "./_components/navbar/Settings"; export const metadata: Metadata = { title: "VAR: Disponent", @@ -26,7 +29,11 @@ export default async function RootLayout({ return ( <> - + + {children} ); diff --git a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx b/apps/dispatch/app/(app)/pilot/_components/navbar/Connection.tsx similarity index 100% rename from apps/dispatch/app/(app)/pilot/_components/navbar/_components/Connection.tsx rename to apps/dispatch/app/(app)/pilot/_components/navbar/Connection.tsx diff --git a/apps/dispatch/app/(app)/pilot/_components/navbar/Navbar.tsx b/apps/dispatch/app/(app)/pilot/_components/navbar/Navbar.tsx deleted file mode 100644 index 39013bc1..00000000 --- a/apps/dispatch/app/(app)/pilot/_components/navbar/Navbar.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Connection } from "./_components/Connection"; -import { Audio } from "_components/Audio/Audio"; -import { ExitIcon, ExternalLinkIcon } from "@radix-ui/react-icons"; -import Link from "next/link"; -import { Settings } from "./_components/Settings"; -import { WarningAlert } from "_components/navbar/PageAlert"; -import { Radar } from "lucide-react"; -import { prisma } from "@repo/db"; -import { ChangelogWrapper } from "_components/navbar/ChangelogWrapper"; - -export default async function Navbar() { - const latestChangelog = await prisma.changelog.findFirst({ - orderBy: { - createdAt: "desc", - }, - }); - return ( -
-
-
-

VAR Operations Center

- -
-
- -
-
-
-
- -
-
- - - - - - - - - - -
-
-
- ); -} diff --git a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/Settings.tsx b/apps/dispatch/app/(app)/pilot/_components/navbar/Settings.tsx similarity index 100% rename from apps/dispatch/app/(app)/pilot/_components/navbar/_components/Settings.tsx rename to apps/dispatch/app/(app)/pilot/_components/navbar/Settings.tsx diff --git a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/ThemeSwap.tsx b/apps/dispatch/app/(app)/pilot/_components/navbar/_components/ThemeSwap.tsx deleted file mode 100644 index dec2a6fd..00000000 --- a/apps/dispatch/app/(app)/pilot/_components/navbar/_components/ThemeSwap.tsx +++ /dev/null @@ -1,26 +0,0 @@ -"use client"; - -import { MoonIcon, SunIcon } from "@radix-ui/react-icons"; - -interface ThemeSwapProps { - isDark: boolean; - toggleTheme: () => void; -} - -export const ThemeSwap: React.FC = ({ - isDark, - toggleTheme, -}) => { - return ( - - ); -}; diff --git a/apps/dispatch/app/(app)/pilot/layout.tsx b/apps/dispatch/app/(app)/pilot/layout.tsx index ab71548f..12029fe7 100644 --- a/apps/dispatch/app/(app)/pilot/layout.tsx +++ b/apps/dispatch/app/(app)/pilot/layout.tsx @@ -1,7 +1,10 @@ import type { Metadata } from "next"; -import Navbar from "./_components/navbar/Navbar"; import { getServerSession } from "api/auth/[...nextauth]/auth"; import { Error } from "_components/Error"; +import Navbar from "(app)/_components/Navbar"; +import { Audio } from "_components/Audio/Audio"; +import { Connection } from "./_components/navbar/Connection"; +import { Settings } from "./_components/navbar/Settings"; export const metadata: Metadata = { title: "VAR: Pilot", @@ -26,7 +29,11 @@ export default async function RootLayout({ return ( <> - + + {children} ); diff --git a/apps/dispatch/app/_components/navbar/ModeSwitchDropdown.tsx b/apps/dispatch/app/_components/navbar/ModeSwitchDropdown.tsx index 24b0448f..372f3853 100644 --- a/apps/dispatch/app/_components/navbar/ModeSwitchDropdown.tsx +++ b/apps/dispatch/app/_components/navbar/ModeSwitchDropdown.tsx @@ -1,18 +1,24 @@ "use client"; import { cn } from "@repo/shared-components"; -import { ArrowLeftRight, Plane, Radar, Workflow } from "lucide-react"; +import { ArrowLeftRight, ExternalLinkIcon, Plane, Radar, Workflow } from "lucide-react"; import { useSession } from "next-auth/react"; import Link from "next/link"; import { usePathname } from "next/navigation"; -export default function ModeSwitchDropdown({ className }: { className?: string }) { +export default function ModeSwitchDropdown({ + className, + btnClassName, +}: { + className?: string; + btnClassName?: string; +}) { const path = usePathname(); const session = useSession(); return (
-
+
{path.includes("pilot") && "Pilot"} {path.includes("dispatch") && "Leitstelle"}
@@ -39,6 +45,15 @@ export default function ModeSwitchDropdown({ className }: { className?: string } Tracker +
  • + + HUB + +
  • ); diff --git a/apps/hub-server/modules/discord.ts b/apps/hub-server/modules/discord.ts index e36f3430..7cff86b4 100644 --- a/apps/hub-server/modules/discord.ts +++ b/apps/hub-server/modules/discord.ts @@ -36,6 +36,7 @@ export const removeRolesFromMember = async (memberId: string, roleIds: string[]) console.error("Error removing roles from member:", error); }); }; + export const setStandardName = async ({ memberId, userId, diff --git a/apps/hub/app/(app)/_components/FeaturedEvents.tsx b/apps/hub/app/(app)/_components/FeaturedEvents.tsx index b3233bd5..0aa5782f 100644 --- a/apps/hub/app/(app)/_components/FeaturedEvents.tsx +++ b/apps/hub/app/(app)/_components/FeaturedEvents.tsx @@ -23,15 +23,6 @@ const page = async () => { userId: user.id, }, }, - Appointments: { - include: { - Participants: { - where: { - appointmentCancelled: false, - }, - }, - }, - }, }, }); @@ -47,23 +38,13 @@ const page = async () => { return (
    -

    - Laufende Events & Kurse +

    + Laufende Events & Kurse

    {filteredEvents.map((event) => { - return ( - - a.Participants.find((p) => p.userId == user.id), - )} - user={user} - event={event} - key={event.id} - /> - ); + return ; })}
    diff --git a/apps/hub/app/(app)/_components/FirstPath.tsx b/apps/hub/app/(app)/_components/FirstPath.tsx index b12301c5..3bda43c3 100644 --- a/apps/hub/app/(app)/_components/FirstPath.tsx +++ b/apps/hub/app/(app)/_components/FirstPath.tsx @@ -20,18 +20,18 @@ const PathsOptions = ({
    {/* Disponent Card */}
    setSelected("disponent")} role="button" tabIndex={0} aria-pressed={selected === "disponent"} > -

    +

    Disponent

    -
    +
    Denkt sich realistische Einsatzszenarien aus, koordiniert deren Ablauf und ist die zentrale Schnittstelle zwischen Piloten und bodengebundenen Rettungsmitteln. Er trägt die Verantwortung für einen reibungslosen Ablauf und der erfolgreichen Durchführung der @@ -43,18 +43,18 @@ const PathsOptions = ({
    {/* Pilot Card */}
    setSelected("pilot")} role="button" tabIndex={0} aria-pressed={selected === "pilot"} > -

    +

    Pilot

    -
    +
    Fliegt die vom Disponenten erstellten Einsätze und transportiert die Med-Crew sicher zum Einsatzort. Er übernimmt die navigatorische Vorbereitung, achtet auf Wetterentwicklungen und sorgt für die Sicherheit seiner Crew im Flug. @@ -76,17 +76,7 @@ const EventSelect = ({ pathSelected }: { pathSelected: "disponent" | "pilot" }) const user = useSession().data?.user; if (!user) return null; return events?.map((event) => { - return ( - - a.Participants.find((p) => p.userId == user.id), - )} - user={user} - event={event} - key={event.id} - /> - ); + return ; }); }; @@ -107,14 +97,14 @@ export const FirstPath = () => { return (
    -

    +

    {session?.user.migratedFromV1 ? "Hallo, hier hat sich einiges geändert!" : "Wähle deinen Einstieg!"}

    -

    Willkommen bei Virtual Air Rescue!

    +

    Willkommen bei Virtual Air Rescue!

    {session?.user.migratedFromV1 ? ( -

    +

    Dein Account wurde erfolgreich auf das neue System migriert. Herzlich Willkommen im neuen HUB! Um die Erfahrung für alle Nutzer zu steigern haben wir uns dazu entschlossen, dass alle Nutzer einen Test absolvieren müssen:{" "} @@ -129,12 +119,12 @@ export const FirstPath = () => { ausprobieren, wenn du möchtest.

    )} -
    +
    {page === "path" && } {page === "event-select" && ( -
    +
    -

    Wähle dein Einführungs-Event aus:

    +

    Wähle dein Einführungs-Event aus:

    diff --git a/apps/hub/app/(app)/_components/Footer.tsx b/apps/hub/app/(app)/_components/Footer.tsx index 1c2a03db..1ef446a9 100644 --- a/apps/hub/app/(app)/_components/Footer.tsx +++ b/apps/hub/app/(app)/_components/Footer.tsx @@ -2,23 +2,17 @@ import Image from "next/image"; import { DiscordLogoIcon, InstagramLogoIcon, ReaderIcon } from "@radix-ui/react-icons"; import YoutubeSvg from "./youtube_wider.svg"; import FacebookSvg from "./facebook.svg"; -import { ChangelogModalBtn } from "@repo/shared-components"; -import { getServerSession } from "api/auth/[...nextauth]/auth"; -import { updateUser } from "(app)/settings/actions"; -import toast from "react-hot-toast"; + import { ChangelogWrapper } from "(app)/_components/ChangelogWrapper"; import { prisma } from "@repo/db"; export const Footer = async () => { - const session = await getServerSession(); const latestChangelog = await prisma.changelog.findFirst({ orderBy: { createdAt: "desc", }, }); - const autoOpen = !session?.user.changelogAck && !!latestChangelog; - return (