Ban Message Design

This commit is contained in:
Nicolas
2025-06-23 14:30:20 +02:00
parent 93962a9ce4
commit c8cf7eae63
6 changed files with 110 additions and 29 deletions

View File

@@ -171,7 +171,7 @@ router.delete("/:id", async (req, res) => {
data: { data: {
userId: aircraft.userId, userId: aircraft.userId,
type: bann ? (until ? "TIME_BAN" : "BAN") : "KICK", type: bann ? (until ? "TIME_BAN" : "BAN") : "KICK",
until: until ? new Date(until) : null, until: until ? new Date(until) : new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 50),
reason: reason, reason: reason,
createdUserId: req.user.id, createdUserId: req.user.id,
}, },

View File

@@ -88,7 +88,7 @@ router.delete("/:id", async (req, res) => {
data: { data: {
userId: dispatcher.userId, userId: dispatcher.userId,
type: bann ? (until ? "TIME_BAN" : "BAN") : "KICK", type: bann ? (until ? "TIME_BAN" : "BAN") : "KICK",
until: until ? new Date(until) : null, until: until ? new Date(until) : new Date(Date.now() + 1000 * 60 * 60 * 24 * 365 * 50),
reason: reason, reason: reason,
createdUserId: req.user.id, createdUserId: req.user.id,
}, },

View File

@@ -22,7 +22,7 @@ export default async function RootLayout({
until: { until: {
gte: new Date(), gte: new Date(),
}, },
type: "TIME_BAN", type: { in: ["TIME_BAN", "BAN"] },
}, },
}); });
@@ -30,13 +30,16 @@ export default async function RootLayout({
redirect("/login"); redirect("/login");
} }
if (!session.user.emailVerified)
return <Error title="E-Mail-Adresse nicht verifiziert" statusCode={403} />;
if (!session.user.permissions.includes("DISPO"))
return <Error title="Zugriff verweigert" statusCode={403} />;
if (openPenaltys[0]) { if (openPenaltys[0]) {
if (openPenaltys[0].type === "BAN") {
return (
<Error
title="Du wurdest permanent ausgeschlossen"
statusCode={403}
description={`Dein Fehlverhalten war so schwerwiegend, dass du dauerhaft von VirtualAirRescue ausgeschlossen wurdest.`}
/>
);
}
return ( return (
<Error <Error
title="Du hast eine aktive Strafe" title="Du hast eine aktive Strafe"
@@ -46,6 +49,12 @@ export default async function RootLayout({
); );
} }
if (!session.user.emailVerified)
return <Error title="E-Mail-Adresse nicht verifiziert" statusCode={403} />;
if (!session.user.permissions.includes("DISPO"))
return <Error title="Zugriff verweigert" statusCode={403} />;
return ( return (
<> <>
<Navbar /> <Navbar />

View File

@@ -22,21 +22,24 @@ export default async function RootLayout({
until: { until: {
gte: new Date(), gte: new Date(),
}, },
type: "TIME_BAN", type: { in: ["TIME_BAN", "BAN"] },
}, },
}); });
if (!session || !session.user.firstname) { if (!session || !session.user.firstname) {
redirect("/login"); redirect("/login");
} }
if (!session.user.emailVerified) {
return <Error title="E-Mail-Adresse nicht verifiziert" statusCode={403} />;
}
if (!session.user.permissions.includes("PILOT"))
return <Error title="Zugriff verweigert" statusCode={403} />;
if (openPenaltys[0]) { if (openPenaltys[0]) {
if (openPenaltys[0].type === "BAN") {
return (
<Error
title="Du wurdest permanent ausgeschlossen"
statusCode={403}
description={`Dein Fehlverhalten war so schwerwiegend, dass du dauerhaft von VirtualAirRescue ausgeschlossen wurdest.`}
/>
);
}
return ( return (
<Error <Error
title="Du hast eine aktive Strafe" title="Du hast eine aktive Strafe"
@@ -46,6 +49,13 @@ export default async function RootLayout({
); );
} }
if (!session.user.emailVerified) {
return <Error title="E-Mail-Adresse nicht verifiziert" statusCode={403} />;
}
if (!session.user.permissions.includes("PILOT"))
return <Error title="Zugriff verweigert" statusCode={403} />;
return ( return (
<> <>
<Navbar /> <Navbar />

View File

@@ -1,6 +1,7 @@
import { prisma } from "@repo/db"; import { prisma } from "@repo/db";
import { TriangleAlert } from "lucide-react"; import { TriangleAlert } from "lucide-react";
import { getServerSession } from "next-auth"; import { getServerSession } from "next-auth";
import { PenaltyCountdown } from "./PenaltyCountdown";
export const Penalty = async () => { export const Penalty = async () => {
const session = await getServerSession(); const session = await getServerSession();
@@ -10,7 +11,7 @@ export const Penalty = async () => {
until: { until: {
gte: new Date(), gte: new Date(),
}, },
type: "TIME_BAN", type: { in: ["TIME_BAN", "BAN"] },
}, },
}); });
if (!openPenaltys[0]) { if (!openPenaltys[0]) {
@@ -18,18 +19,33 @@ export const Penalty = async () => {
} }
return ( return (
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3"> <div className="card bg-error shadow-xl mb-4 col-span-6 xl:col-span-3">
<div className="card-body"> {openPenaltys[0].type === "TIME_BAN" && (
<h2 className="card-title text-3xl text-center text-error"> <div className="card-body text-base-300">
<TriangleAlert /> <h2 className="card-title text-3xl">
Aktive Strafe <TriangleAlert />
</h2> Aktive Strafe - <PenaltyCountdown until={openPenaltys[0].until ?? new Date()} />{" "}
<p>Du hast eine aktive Strafe, die dich daran hindert, an Flügen teilzunehmen.</p> verbleibend
<p>Strafe: {openPenaltys[0].reason}</p> </h2>
{openPenaltys[0].until && ( <p className="text-left font-bold">
<p>Bis: {new Date(openPenaltys[0].until).toLocaleDateString()}</p> Du hast eine aktive Strafe und kannst dich deshalb nicht mit dem Netzwerk verbinden.
)} </p>
</div> <p className="text-left font-bold">Grund: {openPenaltys[0].reason}</p>
</div>
)}
{openPenaltys[0].type === "BAN" && (
<div className="card-body text-base-300">
<h2 className="card-title text-3xl">
<TriangleAlert />
Du wurdest permanent von VirtualAirRescue ausgeschlossen.
</h2>
<p className="text-left font-bold">
Dein Fehlverhalten war so schwerwiegend, dass du dauerhaft von VirtualAirRescue
ausgeschlossen wurdest. Du kannst dich nicht mehr mit dem Netzwerk verbinden.
</p>
<p className="text-left font-bold">Grund: {openPenaltys[0].reason}</p>
</div>
)}
</div> </div>
); );
}; };

View File

@@ -0,0 +1,46 @@
"use client";
import React, { useEffect, useState } from "react";
interface PenaltyCountdownProps {
until: string | Date;
}
function getTimeLeft(until: string | Date) {
const untilDate = new Date(until).getTime();
const now = Date.now();
let diff = Math.max(0, untilDate - now);
const hours = Math.floor(diff / (1000 * 60 * 60));
diff -= hours * 1000 * 60 * 60;
const minutes = Math.floor(diff / (1000 * 60));
diff -= minutes * 1000 * 60;
const seconds = Math.floor(diff / 1000);
return { hours, minutes, seconds };
}
export const PenaltyCountdown: React.FC<PenaltyCountdownProps> = ({ until }) => {
const [timeLeft, setTimeLeft] = useState(() => getTimeLeft(until));
useEffect(() => {
const interval = setInterval(() => {
setTimeLeft(getTimeLeft(until));
}, 1000);
return () => clearInterval(interval);
}, [until]);
return (
<span className="countdown text-3xl">
<span style={{ "--value": timeLeft.hours } as React.CSSProperties} aria-live="polite">
{timeLeft.hours}
</span>
h
<span style={{ "--value": timeLeft.minutes } as React.CSSProperties} aria-live="polite">
{timeLeft.minutes}
</span>
m
<span style={{ "--value": timeLeft.seconds } as React.CSSProperties} aria-live="polite">
{timeLeft.seconds}
</span>
s
</span>
);
};