Merge pull request #133 from VAR-Virtual-Air-Rescue/staging

v2.0.3
This commit was merged in pull request #133.
This commit is contained in:
PxlLoewe
2025-07-29 16:33:11 -07:00
committed by GitHub
15 changed files with 179 additions and 53 deletions

View File

@@ -1,6 +1,6 @@
import { getPublicUser, prisma, User } from "@repo/db"; import { getPublicUser, prisma, User } from "@repo/db";
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord"; import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
import { getNextDateWithTime } from "@repo/shared-components"; import { getNextDateWithTime, getUserPenaltys } from "@repo/shared-components";
import { DISCORD_ROLES } from "@repo/db"; import { DISCORD_ROLES } from "@repo/db";
import { Server, Socket } from "socket.io"; import { Server, Socket } from "socket.io";
@@ -28,8 +28,17 @@ export const handleConnectDispatch =
return; return;
} }
if (!user.permissions?.includes("DISPO")) { const userPenaltys = await getUserPenaltys(user.id);
socket.emit("error", "You do not have permission to connect to the dispatch server.");
if (
userPenaltys.openTimeban.length > 0 ||
user.isBanned ||
userPenaltys.openBans.length > 0
) {
socket.emit("connect-message", {
message: "Du hast eine aktive Strafe und kannst dich deshalb nicht verbinden.",
});
socket.disconnect();
return; return;
} }

View File

@@ -1,8 +1,8 @@
import { getPublicUser, prisma, User } from "@repo/db"; import { getPublicUser, prisma, User } from "@repo/db";
import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord"; import { addRolesToMember, removeRolesFromMember, renameMember } from "modules/discord";
import { getNextDateWithTime } from "@repo/shared-components";
import { DISCORD_ROLES } from "@repo/db"; import { DISCORD_ROLES } from "@repo/db";
import { Server, Socket } from "socket.io"; import { Server, Socket } from "socket.io";
import { getUserPenaltys } from "@repo/shared-components";
export const handleConnectPilot = export const handleConnectPilot =
(socket: Socket, io: Server) => (socket: Socket, io: Server) =>
@@ -34,6 +34,19 @@ export const handleConnectPilot =
socket.disconnect(); socket.disconnect();
return; return;
} }
const userPenaltys = await getUserPenaltys(userId);
if (
userPenaltys.openTimeban.length > 0 ||
user.isBanned ||
userPenaltys.openBans.length > 0
) {
socket.emit("connect-message", {
message: "Du hast eine aktive Strafe und kannst dich deshalb nicht verbinden.",
});
socket.disconnect();
return;
}
if (!user) return Error("User not found"); if (!user) return Error("User not found");

View File

@@ -5,7 +5,7 @@ import { Server, Socket } from "socket.io";
export const handleSendMessage = export const handleSendMessage =
(socket: Socket, io: Server) => (socket: Socket, io: Server) =>
async ( async (
{ userId, message }: { userId: string; message: string }, { userId, message, role }: { userId: string; message: string; role: string },
cb: (err: { error?: string }) => void, cb: (err: { error?: string }) => void,
) => { ) => {
const senderId = socket.data.user.id; const senderId = socket.data.user.id;
@@ -24,7 +24,7 @@ export const handleSendMessage =
receiverId: userId, receiverId: userId,
senderId, senderId,
receiverName: `${receiverUser?.firstname} ${receiverUser?.lastname[0]}. - ${receiverUser?.publicId}`, receiverName: `${receiverUser?.firstname} ${receiverUser?.lastname[0]}. - ${receiverUser?.publicId}`,
senderName: `${senderUser?.firstname} ${senderUser?.lastname[0]}. - ${senderUser?.publicId}`, senderName: `${senderUser?.firstname} ${senderUser?.lastname[0]}. - ${role ?? senderUser?.publicId}`,
}, },
}); });

View File

@@ -17,7 +17,6 @@ import { getConnectedAircraftPositionLogAPI, getConnectedAircraftsAPI } from "_q
import { getMissionsAPI } from "_querys/missions"; import { getMissionsAPI } from "_querys/missions";
import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors"; import { FMS_STATUS_COLORS, FMS_STATUS_TEXT_COLORS } from "_helpers/fmsStatusColors";
import { usePilotConnectionStore } from "_store/pilot/connectionStore"; import { usePilotConnectionStore } from "_store/pilot/connectionStore";
import { useSession } from "next-auth/react";
const AircraftPopupContent = ({ const AircraftPopupContent = ({
aircraft, aircraft,
@@ -73,7 +72,7 @@ const AircraftPopupContent = ({
} }
}, [currentTab, aircraft, mission]); }, [currentTab, aircraft, mission]);
const { setOpenAircraftMarker, setMap, openAircraftMarker } = useMapStore((state) => state); const { setOpenAircraftMarker, setMap } = useMapStore((state) => state);
const { anchor } = useSmartPopup(); const { anchor } = useSmartPopup();
return ( return (
<> <>
@@ -435,6 +434,9 @@ export const AircraftLayer = () => {
} }
}, [pilotConnectionStatus, followOwnAircraft, ownAircraft, setMap, map]); }, [pilotConnectionStatus, followOwnAircraft, ownAircraft, setMap, map]);
console.debug("Hubschrauber auf Karte:", filteredAircrafts.length, filteredAircrafts);
console.debug("Daten vom Server:", aircrafts?.length, aircrafts);
return ( return (
<> <>
{filteredAircrafts?.map((aircraft) => { {filteredAircrafts?.map((aircraft) => {

View File

@@ -92,11 +92,6 @@ export default function AdminPanel() {
const modalRef = useRef<HTMLDialogElement>(null); const modalRef = useRef<HTMLDialogElement>(null);
console.debug("piloten von API", {
anzahl: pilots?.length,
pilots,
});
return ( return (
<div> <div>
<button <button

View File

@@ -2,6 +2,8 @@ import { create } from "zustand";
import { ChatMessage } from "@repo/db"; import { ChatMessage } from "@repo/db";
import { dispatchSocket } from "(app)/dispatch/socket"; import { dispatchSocket } from "(app)/dispatch/socket";
import { pilotSocket } from "(app)/pilot/socket"; import { pilotSocket } from "(app)/pilot/socket";
import { useDispatchConnectionStore } from "_store/dispatch/connectionStore";
import { usePilotConnectionStore } from "_store/pilot/connectionStore";
interface ChatStore { interface ChatStore {
situationTabOpen: boolean; situationTabOpen: boolean;
@@ -16,7 +18,12 @@ interface ChatStore {
setOwnId: (id: string) => void; setOwnId: (id: string) => void;
chats: Record<string, { name: string; notification: boolean; messages: ChatMessage[] }>; chats: Record<string, { name: string; notification: boolean; messages: ChatMessage[] }>;
setChatNotification: (userId: string, notification: boolean) => void; setChatNotification: (userId: string, notification: boolean) => void;
sendMessage: (userId: string, message: string) => Promise<void>; sendMessage: (
userId: string,
message: string,
senderName?: string,
receiverName?: string,
) => Promise<void>;
addChat: (userId: string, name: string) => void; addChat: (userId: string, name: string) => void;
addMessage: (userId: string, message: ChatMessage) => void; addMessage: (userId: string, message: ChatMessage) => void;
removeChat: (userId: string) => void; removeChat: (userId: string) => void;
@@ -49,12 +56,13 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
}, },
setOwnId: (id: string) => set({ ownId: id }), setOwnId: (id: string) => set({ ownId: id }),
chats: {}, chats: {},
sendMessage: (userId: string, message: string) => { sendMessage: (userId, message) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (dispatchSocket.connected) { if (dispatchSocket.connected) {
const zone = useDispatchConnectionStore.getState().selectedZone;
dispatchSocket.emit( dispatchSocket.emit(
"send-message", "send-message",
{ userId, message }, { userId, message, role: zone },
({ error }: { error?: string }) => { ({ error }: { error?: string }) => {
if (error) { if (error) {
reject(error); reject(error);
@@ -64,13 +72,19 @@ export const useLeftMenuStore = create<ChatStore>((set, get) => ({
}, },
); );
} else if (pilotSocket.connected) { } else if (pilotSocket.connected) {
pilotSocket.emit("send-message", { userId, message }, ({ error }: { error?: string }) => { const bosCallsign = usePilotConnectionStore.getState().selectedStation?.bosCallsignShort;
pilotSocket.emit(
"send-message",
{ userId, message, role: bosCallsign },
({ error }: { error?: string }) => {
if (error) { if (error) {
reject(error); reject(error);
} else { } else {
resolve(); resolve();
} }
}); },
);
} }
}); });
}, },

View File

@@ -30,12 +30,17 @@ export const penaltyColumns: ColumnDef<Penalty & { Report: Report; CreatedUser:
new Date(row.original.until || Date.now()), new Date(row.original.until || Date.now()),
{ locale: de }, { locale: de },
); );
const isExpired = new Date(row.original.until || Date.now()) < new Date();
return ( return (
<div <div
className={cn("text-warning flex gap-3", row.original.suspended && "text-gray-400")} className={cn(
"text-warning flex gap-3",
(row.original.suspended || isExpired) && "text-gray-400",
)}
> >
<Timer /> <Timer />
Zeit Sperre ({length}) {row.original.suspended && "(ausgesetzt)"} Zeit Sperre ({length}) {row.original.suspended && "(ausgesetzt)"}{" "}
{isExpired && !row.original.suspended && "(abgelaufen)"}
</div> </div>
); );
} }
@@ -78,14 +83,14 @@ export const penaltyColumns: ColumnDef<Penalty & { Report: Report; CreatedUser:
<div className="flex gap-2"> <div className="flex gap-2">
<Link href={`/admin/penalty/${row.original.id}`}> <Link href={`/admin/penalty/${row.original.id}`}>
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2"> <button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
<Shield className="w-4 h-4" /> <Shield className="h-4 w-4" />
Anzeigen Anzeigen
</button> </button>
</Link> </Link>
{report && ( {report && (
<Link href={`/admin/report/${report.id}`}> <Link href={`/admin/report/${report.id}`}>
<button className="btn btn-sm btn-outliney flex items-center gap-2"> <button className="btn btn-sm btn-outliney flex items-center gap-2">
<TriangleAlert className="w-4 h-4" /> <TriangleAlert className="h-4 w-4" />
Report Anzeigen Report Anzeigen
</button> </button>
</Link> </Link>

View File

@@ -10,6 +10,12 @@ export default function ReportPage() {
CreatedUser: true, CreatedUser: true,
Report: true, Report: true,
}} }}
initialOrderBy={[
{
id: "timestamp",
desc: true,
},
]}
columns={penaltyColumns} columns={penaltyColumns}
/> />
); );

View File

@@ -44,7 +44,7 @@ export const NewReportForm = ({
reviewerUserId: null, reviewerUserId: null,
}, },
}); });
console.log(form.formState.errors);
return ( return (
<form <form
className="flex flex-wrap gap-3" className="flex flex-wrap gap-3"

View File

@@ -6,6 +6,7 @@ import {
ConnectedAircraft, ConnectedAircraft,
ConnectedDispatcher, ConnectedDispatcher,
DiscordAccount, DiscordAccount,
Penalty,
PERMISSION, PERMISSION,
Station, Station,
User, User,
@@ -46,6 +47,7 @@ import {
ShieldUser, ShieldUser,
Timer, Timer,
Trash2, Trash2,
TriangleAlert,
Users, Users,
} from "lucide-react"; } from "lucide-react";
import Link from "next/link"; import Link from "next/link";
@@ -255,6 +257,7 @@ export const ProfileForm: React.FC<ProfileFormProps> = ({ user }: ProfileFormPro
export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: User }) => { export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: User }) => {
const dispoTableRef = useRef<PaginatedTableRef>(null); const dispoTableRef = useRef<PaginatedTableRef>(null);
const pilotTableRef = useRef<PaginatedTableRef>(null);
return ( return (
<div className="card-body flex-row flex-wrap"> <div className="card-body flex-row flex-wrap">
<div className="flex-1"> <div className="flex-1">
@@ -302,7 +305,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<div> <div>
<button <Button
className="btn btn-sm btn-error" className="btn btn-sm btn-error"
onClick={async () => { onClick={async () => {
await deleteDispoHistory(row.original.id); await deleteDispoHistory(row.original.id);
@@ -310,7 +313,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
}} }}
> >
löschen löschen
</button> </Button>
</div> </div>
); );
}, },
@@ -324,7 +327,7 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
<PlaneIcon className="h-5 w-5" /> Pilot-Verbindungs Historie <PlaneIcon className="h-5 w-5" /> Pilot-Verbindungs Historie
</h2> </h2>
<PaginatedTable <PaginatedTable
ref={dispoTableRef} ref={pilotTableRef}
filter={{ filter={{
userId: user.id, userId: user.id,
}} }}
@@ -375,15 +378,15 @@ export const ConnectionHistory: React.FC<{ user: User }> = ({ user }: { user: Us
cell: ({ row }) => { cell: ({ row }) => {
return ( return (
<div> <div>
<button <Button
className="btn btn-sm btn-error" className="btn btn-sm btn-error"
onClick={async () => { onClick={async () => {
await deletePilotHistory(row.original.id); await deletePilotHistory(row.original.id);
dispoTableRef.current?.refresh(); pilotTableRef.current?.refresh();
}} }}
> >
löschen löschen
</button> </Button>
</div> </div>
); );
}, },
@@ -408,6 +411,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
</span> </span>
<div className="flex gap-2"> <div className="flex gap-2">
<PenaltyDropdown <PenaltyDropdown
showBtnName
btnName="Zeitstrafe hinzufügen" btnName="Zeitstrafe hinzufügen"
Icon={<Timer size={15} />} Icon={<Timer size={15} />}
onClick={async ({ reason, until }) => { onClick={async ({ reason, until }) => {
@@ -437,6 +441,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
/> />
{session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && ( {session.data?.user.permissions.includes("ADMIN_USER_ADVANCED") && (
<PenaltyDropdown <PenaltyDropdown
showBtnName
btnName="Bannen" btnName="Bannen"
Icon={<LockKeyhole size={15} />} Icon={<LockKeyhole size={15} />}
onClick={async ({ reason }) => { onClick={async ({ reason }) => {
@@ -528,6 +533,12 @@ interface AdminFormProps {
open: number; open: number;
total60Days: number; total60Days: number;
}; };
openBans: (Penalty & {
CreatedUser: User | null;
})[];
openTimebans: (Penalty & {
CreatedUser: User | null;
})[];
} }
export const AdminForm = ({ export const AdminForm = ({
@@ -536,6 +547,8 @@ export const AdminForm = ({
pilotTime, pilotTime,
reports, reports,
discordAccount, discordAccount,
openBans,
openTimebans,
}: AdminFormProps) => { }: AdminFormProps) => {
const router = useRouter(); const router = useRouter();
const { data: session } = useSession(); const { data: session } = useSession();
@@ -627,6 +640,33 @@ export const AdminForm = ({
)} )}
</div> </div>
</div> </div>
{(!!openBans.length || !!openTimebans.length) && (
<div role="alert" className="alert alert-warning alert-outline flex flex-col">
<div className="flex items-center gap-2">
<TriangleAlert />
{openBans.map((ban) => (
<div key={ban.id}>
<h3 className="text-lg font-semibold">Permanent ausgeschlossen</h3>
{ban.reason} (von {ban.CreatedUser?.firstname} {ban.CreatedUser?.lastname} -{" "}
{ban.CreatedUser?.publicId})
</div>
))}
{openTimebans.map((timeban) => (
<div key={timeban.id}>
<h3 className="text-lg font-semibold">
Zeitstrafe bis{" "}
{timeban.until ? new Date(timeban.until).toLocaleString("de-DE") : "unbekannt"}
</h3>
{timeban.reason} ({timeban.CreatedUser?.firstname} {timeban.CreatedUser?.lastname} -{" "}
{timeban.CreatedUser?.publicId})
</div>
))}
</div>
<p className="text-sm text-gray-400">
Achtung! Die Strafe(n) sind aktiv, die Rechte des Nutzers müssen nicht angepasst werden!
</p>
</div>
)}
<h2 className="card-title"> <h2 className="card-title">
<ChartBarBigIcon className="h-5 w-5" /> Aktivität <ChartBarBigIcon className="h-5 w-5" /> Aktivität
</h2> </h2>

View File

@@ -8,10 +8,10 @@ import {
UserReports, UserReports,
} from "./_components/forms"; } from "./_components/forms";
import { Error } from "../../../../_components/Error"; import { Error } from "../../../../_components/Error";
import { getUserPenaltys } from "@repo/shared-components";
export default async function Page({ params }: { params: Promise<{ id: string }> }) { export default async function Page({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params; const { id } = await params;
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
id: id, id: id,
@@ -20,6 +20,7 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
discordAccounts: true, discordAccounts: true,
}, },
}); });
if (!user) return <Error statusCode={404} title="User not found" />;
const dispoSessions = await prisma.connectedDispatcher.findMany({ const dispoSessions = await prisma.connectedDispatcher.findMany({
where: { where: {
@@ -97,41 +98,43 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
open: totalReportsOpen, open: totalReportsOpen,
total60Days: totalReports60Days, total60Days: totalReports60Days,
}; };
if (!user) return <Error statusCode={404} title="User not found" />; const { openBans, openTimeban } = await getUserPenaltys(user?.id);
return ( return (
<div className="grid grid-cols-6 gap-4"> <div className="grid grid-cols-6 gap-4">
<div className="col-span-full flex justify-between items-center"> <div className="col-span-full flex items-center justify-between">
<p className="text-2xl font-semibold text-left flex items-center gap-2"> <p className="flex items-center gap-2 text-left text-2xl font-semibold">
<PersonIcon className="w-5 h-5" /> <PersonIcon className="h-5 w-5" />
{user?.firstname} {user?.lastname} #{user?.publicId} {user?.firstname} {user?.lastname} #{user?.publicId}
</p> </p>
<p <p
className="text-sm text-gray-400 font-thin tooltip tooltip-left" className="tooltip tooltip-left text-sm font-thin text-gray-400"
data-tip="Account erstellt am" data-tip="Account erstellt am"
> >
{new Date(user.createdAt).toLocaleString("de-DE")} {new Date(user.createdAt).toLocaleString("de-DE")}
</p> </p>
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3"> <div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
<ProfileForm user={user} /> <ProfileForm user={user} />
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3"> <div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
<AdminForm <AdminForm
user={user} user={user}
dispoTime={dispoTime} dispoTime={dispoTime}
pilotTime={pilotTime} pilotTime={pilotTime}
reports={reports} reports={reports}
discordAccount={user.discordAccounts[0]} discordAccount={user.discordAccounts[0]}
openBans={openBans}
openTimebans={openTimeban}
/> />
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6"> <div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
<UserReports user={user} /> <UserReports user={user} />
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6"> <div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
<UserPenalties user={user} /> <UserPenalties user={user} />
</div> </div>
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-6"> <div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-6">
<ConnectionHistory user={user} /> <ConnectionHistory user={user} />
</div> </div>
</div> </div>

View File

@@ -39,7 +39,7 @@ export const ChangelogModal = ({
)} )}
</div> </div>
<div className="text-base-content/80 mb-2 mt-4 text-left"> <div className="text-base-content/80 mb-2 mt-4 text-left" data-color-mode="dark">
<MDEditor.Markdown <MDEditor.Markdown
source={latestChangelog.text} source={latestChangelog.text}
style={{ style={{

View File

@@ -9,12 +9,14 @@ export const PenaltyDropdown = ({
btnTip, btnTip,
btnName, btnName,
Icon, Icon,
showBtnName = false,
}: { }: {
onClick: (data: { reason: string; until: Date | null }) => void; onClick: (data: { reason: string; until: Date | null }) => void;
showDatePicker?: boolean; showDatePicker?: boolean;
btnClassName?: string; btnClassName?: string;
btnName: string; btnName: string;
btnTip?: string; btnTip?: string;
showBtnName?: boolean;
Icon: ReactNode; Icon: ReactNode;
}) => { }) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
@@ -25,25 +27,29 @@ export const PenaltyDropdown = ({
<div tabIndex={0} role="button"></div> <div tabIndex={0} role="button"></div>
<div className="indicator"> <div className="indicator">
<button <button
className={cn("btn btn-xs btn-square btn-soft cursor-pointer", btnClassName)} className={cn(
"btn btn-xs btn-soft cursor-pointer",
!showBtnName && "btn-square",
btnClassName,
)}
onClick={() => setOpen(!open)} onClick={() => setOpen(!open)}
> >
{Icon} {Icon} {showBtnName && <span className="hidden md:inline-block">{btnName}</span>}
</button> </button>
</div> </div>
{open && ( {open && (
<div <div
className="dropdown-content bg-base-100 rounded-box z-1 p-4 shadow-sm space-y-4 shadow-md" className="dropdown-content bg-base-100 rounded-box z-1 space-y-4 p-4 shadow-md shadow-sm"
style={{ minWidth: "500px", right: "40px" }} style={{ minWidth: "500px", right: "40px" }}
> >
<button <button
className="absolute top-2 right-2 btn btn-xs btn-circle btn-ghost" className="btn btn-xs btn-circle btn-ghost absolute right-2 top-2"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
type="button" type="button"
> >
<span className="text-xl leading-none">&times;</span> <span className="text-xl leading-none">&times;</span>
</button> </button>
<h2 className="text-xl font-bold text-center">{btnName}</h2> <h2 className="text-center text-xl font-bold">{btnName}</h2>
<textarea <textarea
value={reason} value={reason}
onChange={(e) => setReason(e.target.value)} onChange={(e) => setReason(e.target.value)}
@@ -53,7 +59,7 @@ export const PenaltyDropdown = ({
/> />
{showDatePicker && ( {showDatePicker && (
<select <select
className="select w-full select-bordered" className="select select-bordered w-full"
value={until} value={until}
onChange={(e) => setUntil(e.target.value)} onChange={(e) => setUntil(e.target.value)}
> >
@@ -74,7 +80,7 @@ export const PenaltyDropdown = ({
</select> </select>
)} )}
<button <button
className={cn("btn w-full btn-square btn-soft tooltip tooltip-bottom", btnClassName)} className={cn("btn btn-square btn-soft tooltip tooltip-bottom w-full", btnClassName)}
data-tip={btnTip} data-tip={btnTip}
onClick={() => { onClick={() => {
let untilDate: Date | null = null; let untilDate: Date | null = null;

View File

@@ -4,3 +4,4 @@ export * from "./dates";
export * from "./simulatorConnected"; export * from "./simulatorConnected";
export * from "./useDebounce"; export * from "./useDebounce";
export * from "./useTimeout"; export * from "./useTimeout";
export * from "./penaltys";

View File

@@ -0,0 +1,32 @@
import { prisma } from "@repo/db";
export const getUserPenaltys = async (userId: string) => {
const openTimeban = await prisma.penalty.findMany({
where: {
userId: userId,
until: {
gte: new Date(),
},
suspended: false,
type: "TIME_BAN",
},
include: {
CreatedUser: true,
},
});
const openBans = await prisma.penalty.findMany({
where: {
userId: userId,
suspended: false,
type: "BAN",
},
include: {
CreatedUser: true,
},
});
return {
openTimeban,
openBans,
};
};