dev
This commit is contained in:
@@ -267,7 +267,7 @@ export default function AdminPanel() {
|
|||||||
/>
|
/>
|
||||||
<PenaltyDropdown
|
<PenaltyDropdown
|
||||||
btnClassName="btn-error"
|
btnClassName="btn-error"
|
||||||
btnTip="Ban"
|
btnTip="Kick + Berechtigungen entfernen"
|
||||||
showDatePicker
|
showDatePicker
|
||||||
Icon={<LockKeyhole size={15} />}
|
Icon={<LockKeyhole size={15} />}
|
||||||
onClick={({ reason, until }) =>
|
onClick={({ reason, until }) =>
|
||||||
@@ -320,7 +320,7 @@ export default function AdminPanel() {
|
|||||||
/>
|
/>
|
||||||
<PenaltyDropdown
|
<PenaltyDropdown
|
||||||
btnClassName="btn-error"
|
btnClassName="btn-error"
|
||||||
btnTip="Ban"
|
btnTip="Kick + Berechtigungen entfernen"
|
||||||
showDatePicker
|
showDatePicker
|
||||||
Icon={<LockKeyhole size={15} />}
|
Icon={<LockKeyhole size={15} />}
|
||||||
onClick={({ reason, until }) =>
|
onClick={({ reason, until }) =>
|
||||||
@@ -374,7 +374,7 @@ export default function AdminPanel() {
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
className="btn btn-xs btn-square btn-error btn-soft tooltip tooltip-bottom tooltip-error"
|
className="btn btn-xs btn-square btn-error btn-soft tooltip tooltip-bottom tooltip-error"
|
||||||
data-tip="Ban"
|
data-tip="Kick + Berechtigungen entfernen"
|
||||||
>
|
>
|
||||||
<LockKeyhole size={15} />
|
<LockKeyhole size={15} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { prisma } from "@repo/db";
|
import { getPublicUser, 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";
|
import { PenaltyCountdown } from "./PenaltyCountdown";
|
||||||
@@ -11,8 +11,13 @@ export const Penalty = async () => {
|
|||||||
until: {
|
until: {
|
||||||
gte: new Date(),
|
gte: new Date(),
|
||||||
},
|
},
|
||||||
|
suspended: false,
|
||||||
|
|
||||||
type: { in: ["TIME_BAN", "BAN"] },
|
type: { in: ["TIME_BAN", "BAN"] },
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
CreatedUser: true,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!openPenaltys[0]) {
|
if (!openPenaltys[0]) {
|
||||||
return null;
|
return null;
|
||||||
@@ -30,10 +35,16 @@ export const Penalty = async () => {
|
|||||||
<p className="text-left font-bold">
|
<p className="text-left font-bold">
|
||||||
Du hast eine aktive Strafe und kannst dich deshalb nicht mit dem Netzwerk verbinden.
|
Du hast eine aktive Strafe und kannst dich deshalb nicht mit dem Netzwerk verbinden.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-left font-bold">Grund: {openPenaltys[0].reason}</p>
|
<p className="text-left">
|
||||||
|
<span className="font-bold">Grund:</span> {openPenaltys[0].reason}
|
||||||
|
</p>
|
||||||
|
<p className="text-left">
|
||||||
|
<span className="font-bold">Admin:</span>{" "}
|
||||||
|
{getPublicUser(openPenaltys[0].CreatedUser).fullName}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{openPenaltys[0].type === "BAN" && (
|
{session?.user.isBanned && (
|
||||||
<div className="card-body text-base-300">
|
<div className="card-body text-base-300">
|
||||||
<h2 className="card-title text-3xl">
|
<h2 className="card-title text-3xl">
|
||||||
<TriangleAlert />
|
<TriangleAlert />
|
||||||
@@ -44,6 +55,10 @@ export const Penalty = async () => {
|
|||||||
ausgeschlossen wurdest. Du kannst dich nicht mehr mit dem Netzwerk verbinden.
|
ausgeschlossen wurdest. Du kannst dich nicht mehr mit dem Netzwerk verbinden.
|
||||||
</p>
|
</p>
|
||||||
<p className="text-left font-bold">Grund: {openPenaltys[0].reason}</p>
|
<p className="text-left font-bold">Grund: {openPenaltys[0].reason}</p>
|
||||||
|
<p className="text-left">
|
||||||
|
<span className="font-bold">Admin:</span>{" "}
|
||||||
|
{getPublicUser(openPenaltys[0].CreatedUser).fullName}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ExclamationTriangleIcon } from "@radix-ui/react-icons";
|
import { ReasonForm } from "(app)/admin/penalty/_components/form";
|
||||||
import { prisma } from "@repo/db";
|
import { prisma } from "@repo/db";
|
||||||
import { Error } from "_components/Error";
|
import { Error } from "_components/Error";
|
||||||
|
import { Shield } from "lucide-react";
|
||||||
|
|
||||||
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;
|
||||||
@@ -15,16 +16,62 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!penalty) return <Error statusCode={404} title="User not found" />;
|
const userReports = await prisma.report.findMany({
|
||||||
|
where: {
|
||||||
|
reportedUserId: penalty?.User.id,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Reported: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!penalty) return <Error statusCode={404} title="Penalty not found" />;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-6 gap-4">
|
<div className="grid grid-cols-6 gap-4">
|
||||||
<div className="col-span-full">
|
<div className="col-span-full">
|
||||||
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
<p className="text-2xl font-semibold text-left flex items-center gap-2">
|
||||||
<ExclamationTriangleIcon className="w-5 h-5" />
|
<Shield className="w-5 h-5" />
|
||||||
Strafe #{penalty.id}
|
Strafe #{penalty.id}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-span-6 md:col-span-3">
|
||||||
|
<div className="card bg-base-200 shadow-md p-4">
|
||||||
|
<p className="text-lg font-semibold mb-2">Details</p>
|
||||||
|
<hr className="mb-4" />
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Benutzer:</span> {penalty.User.firstname}{" "}
|
||||||
|
{penalty.User.lastname} ({penalty.User.publicId})
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Erstellt von:</span> {penalty.CreatedUser.firstname}{" "}
|
||||||
|
{penalty.CreatedUser.lastname} ({penalty.CreatedUser.publicId})
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Typ:</span> {penalty.type}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Erstellt am:</span>{" "}
|
||||||
|
{new Date(penalty.timestamp).toLocaleString("de-DE", {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
{penalty.until && (
|
||||||
|
<p>
|
||||||
|
<span className="font-semibold">Gültig bis:</span>{" "}
|
||||||
|
{new Date(penalty.until).toLocaleString("de-DE", {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "short",
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card bg-base-200 shadow-xl mb-4 col-span-6 xl:col-span-3">
|
||||||
|
<ReasonForm penalty={penalty} userReports={userReports} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
"use client";
|
||||||
|
import { editPenalty } from "(app)/admin/penalty/actions";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { Penalty, Report, User } from "@repo/db";
|
||||||
|
import { PenaltyOptionalDefaults, PenaltyOptionalDefaultsSchema } from "@repo/db/zod";
|
||||||
|
import { Button } from "_components/ui/Button";
|
||||||
|
import { Switch } from "_components/ui/Switch";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
|
||||||
|
export const ReasonForm = ({
|
||||||
|
penalty,
|
||||||
|
userReports: suerReportsOfDay,
|
||||||
|
}: {
|
||||||
|
penalty: Penalty;
|
||||||
|
userReports: (Report & { Reported: User })[];
|
||||||
|
}) => {
|
||||||
|
const form = useForm<PenaltyOptionalDefaults>({
|
||||||
|
defaultValues: penalty,
|
||||||
|
resolver: zodResolver(PenaltyOptionalDefaultsSchema),
|
||||||
|
});
|
||||||
|
const isLoading = form.formState.isSubmitting;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<form
|
||||||
|
className="card-body"
|
||||||
|
onSubmit={form.handleSubmit(async (penalty) => {
|
||||||
|
if (!penalty.id) return;
|
||||||
|
const newPenalty = await editPenalty(penalty.id, penalty);
|
||||||
|
form.reset(newPenalty);
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<h2 className="card-title">Begründung</h2>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<textarea className="textarea textarea-bordered w-full" {...form.register("reason")} />
|
||||||
|
{form.formState.errors.reason && (
|
||||||
|
<p className="text-error">{form.formState.errors.reason.message}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="tooltip tooltip-bottom"
|
||||||
|
data-tip="Wenn diese Option aktiviert ist, wird die Strafe so behandelt, als wäre sie abgelaufen."
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
form={form}
|
||||||
|
name="suspended"
|
||||||
|
label="Strafe aussetzen"
|
||||||
|
className={form.watch("suspended") ? "toggle-error text-error" : ""}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<fieldset className="fieldset">
|
||||||
|
<legend className="fieldset-legend text-left">Zugeordneter Report</legend>
|
||||||
|
<select
|
||||||
|
className="select select-bordered w-full"
|
||||||
|
{...form.register("reportId", {
|
||||||
|
valueAsNumber: true,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
<option value="">Kein Report</option>
|
||||||
|
{suerReportsOfDay.map((report) => (
|
||||||
|
<option key={report.id} value={report.id}>
|
||||||
|
Report #{report.id} - {report.reportedUserRole} - {report.Reported.firstname}{" "}
|
||||||
|
{report.Reported.lastname} ({report.Reported.publicId})
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
<p className="label">
|
||||||
|
Bitte währe ein Report aus damit diese Maßname bei dem Report angezeigt wird
|
||||||
|
</p>
|
||||||
|
</fieldset>
|
||||||
|
<Button
|
||||||
|
className="btn-primary"
|
||||||
|
type="submit"
|
||||||
|
disabled={isLoading || !form.formState.isDirty}
|
||||||
|
isLoading={isLoading}
|
||||||
|
>
|
||||||
|
Speichern
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -6,3 +6,12 @@ export const addPenalty = async (data: Prisma.PenaltyCreateInput) => {
|
|||||||
data,
|
data,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const editPenalty = async (id: number, data: Prisma.PenaltyUpdateInput) => {
|
||||||
|
return await prisma.penalty.update({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,11 +1,92 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { Eye, LockKeyhole, RedoDot, Timer } from "lucide-react";
|
import { LockKeyhole, RedoDot, Shield, Timer, TriangleAlert } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { PaginatedTable } from "_components/PaginatedTable";
|
import { PaginatedTable } from "_components/PaginatedTable";
|
||||||
import { Penalty, PenaltyType, Report, User } from "@repo/db";
|
import { Penalty, PenaltyType, Report, User } from "@repo/db";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { formatDistance } from "date-fns";
|
import { formatDistance } from "date-fns";
|
||||||
import { de } from "date-fns/locale";
|
import { de } from "date-fns/locale";
|
||||||
|
import { cn } from "../../../../helper/cn";
|
||||||
|
|
||||||
|
export const penaltyColumns: ColumnDef<Penalty & { Report: Report }>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "type",
|
||||||
|
header: "Typ",
|
||||||
|
|
||||||
|
cell: ({ row }) => {
|
||||||
|
switch (row.getValue("type") as PenaltyType) {
|
||||||
|
case "KICK":
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("text-warning flex gap-3", row.original.suspended && "text-gray-400")}
|
||||||
|
>
|
||||||
|
<RedoDot />
|
||||||
|
Kick {row.original.suspended && "(ausgesetzt)"}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
case "TIME_BAN": {
|
||||||
|
const length = formatDistance(
|
||||||
|
new Date(row.original.timestamp),
|
||||||
|
new Date(row.original.until || Date.now()),
|
||||||
|
{ locale: de },
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={cn("text-warning flex gap-3", row.original.suspended && "text-gray-400")}
|
||||||
|
>
|
||||||
|
<Timer />
|
||||||
|
Zeit Sperre ({length}) {row.original.suspended && "(ausgesetzt)"}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "BAN":
|
||||||
|
return (
|
||||||
|
<div className={cn("text-error flex gap-3", row.original.suspended && "text-gray-400")}>
|
||||||
|
<LockKeyhole /> Bann {row.original.suspended && "(ausgesetzt)"}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "CreatedUser",
|
||||||
|
header: "Bestraft durch",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const user = row.getValue("CreatedUser") as User;
|
||||||
|
return `${user.firstname} ${user.lastname} (${user.publicId})`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "timestamp",
|
||||||
|
header: "Time",
|
||||||
|
cell: ({ row }) => new Date(row.getValue("timestamp")).toLocaleString(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: "actions",
|
||||||
|
header: "Actions",
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const report = row.original.Report;
|
||||||
|
return (
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Link href={`/admin/penalty/${row.original.id}`}>
|
||||||
|
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
||||||
|
<Shield className="w-4 h-4" />
|
||||||
|
Anzeigen
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
{report && (
|
||||||
|
<Link href={`/admin/report/${report.id}`}>
|
||||||
|
<button className="btn btn-sm btn-outliney flex items-center gap-2">
|
||||||
|
<TriangleAlert className="w-4 h-4" />
|
||||||
|
Report Anzeigen
|
||||||
|
</button>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export default function ReportPage() {
|
export default function ReportPage() {
|
||||||
return (
|
return (
|
||||||
@@ -15,74 +96,7 @@ export default function ReportPage() {
|
|||||||
CreatedUser: true,
|
CreatedUser: true,
|
||||||
Report: true,
|
Report: true,
|
||||||
}}
|
}}
|
||||||
columns={
|
columns={penaltyColumns}
|
||||||
[
|
|
||||||
{
|
|
||||||
accessorKey: "type",
|
|
||||||
header: "Typ",
|
|
||||||
|
|
||||||
cell: ({ row }) => {
|
|
||||||
switch (row.getValue("type") as PenaltyType) {
|
|
||||||
case "KICK":
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<RedoDot />
|
|
||||||
Kick
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "TIME_BAN": {
|
|
||||||
const length = formatDistance(
|
|
||||||
new Date(row.original.timestamp),
|
|
||||||
new Date(row.original.until || Date.now()),
|
|
||||||
{ locale: de },
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<Timer />
|
|
||||||
Zeit Sperre ({length})
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "BAN":
|
|
||||||
return (
|
|
||||||
<div className="text-error flex gap-3">
|
|
||||||
<LockKeyhole /> Bann
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "CreatedUser",
|
|
||||||
header: "Bestraft durch",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const user = row.getValue("CreatedUser") as User;
|
|
||||||
return `${user.firstname} ${user.lastname} (${user.publicId})`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "timestamp",
|
|
||||||
header: "Time",
|
|
||||||
cell: ({ row }) => new Date(row.getValue("timestamp")).toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "actions",
|
|
||||||
header: "Actions",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const report = row.original.Report;
|
|
||||||
if (!report[0]) return null;
|
|
||||||
return (
|
|
||||||
<Link href={`/admin/report/${report[0].id}`}>
|
|
||||||
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
Report Anzeigen
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as ColumnDef<Penalty & { Report: Report[] }>[]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
import { penaltyColumns as penaltyColumns } from "(app)/admin/penalty/page";
|
||||||
import { editReport } from "(app)/admin/report/actions";
|
import { editReport } from "(app)/admin/report/actions";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { Report as IReport, Penalty, PenaltyType, Report, User } from "@repo/db";
|
import { Report as IReport, User } from "@repo/db";
|
||||||
import { ReportSchema, Report as IReportZod } from "@repo/db/zod";
|
import { ReportSchema, Report as IReportZod } from "@repo/db/zod";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
|
||||||
import { PaginatedTable } from "_components/PaginatedTable";
|
import { PaginatedTable } from "_components/PaginatedTable";
|
||||||
import { Button } from "_components/ui/Button";
|
import { Button } from "_components/ui/Button";
|
||||||
import { Switch } from "_components/ui/Switch";
|
import { Switch } from "_components/ui/Switch";
|
||||||
import { formatDistance } from "date-fns";
|
import { Shield, Trash } from "lucide-react";
|
||||||
import { de } from "date-fns/locale";
|
|
||||||
import { Eye, LockKeyhole, RedoDot, Shield, Timer, Trash } from "lucide-react";
|
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
@@ -142,16 +140,6 @@ export const ReportPenalties = ({
|
|||||||
Reviewer?: User | null;
|
Reviewer?: User | null;
|
||||||
};
|
};
|
||||||
}) => {
|
}) => {
|
||||||
if (!report.penaltyId)
|
|
||||||
return (
|
|
||||||
<div className="card-body">
|
|
||||||
<h2 className="card-title">
|
|
||||||
<Shield className="w-5 h-5" /> Strafen zu diesem Report
|
|
||||||
</h2>
|
|
||||||
<p className="text-sm text-gray-600">Es wurden keine Strafen zu diesem Report erfasst.</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
<h2 className="card-title">
|
<h2 className="card-title">
|
||||||
@@ -164,76 +152,9 @@ export const ReportPenalties = ({
|
|||||||
Report: true,
|
Report: true,
|
||||||
}}
|
}}
|
||||||
filter={{
|
filter={{
|
||||||
id: report.penaltyId,
|
reportId: report.id,
|
||||||
}}
|
}}
|
||||||
columns={
|
columns={penaltyColumns}
|
||||||
[
|
|
||||||
{
|
|
||||||
accessorKey: "type",
|
|
||||||
header: "Typ",
|
|
||||||
|
|
||||||
cell: ({ row }) => {
|
|
||||||
switch (row.getValue("type") as PenaltyType) {
|
|
||||||
case "KICK":
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<RedoDot />
|
|
||||||
Kick
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "TIME_BAN": {
|
|
||||||
const length = formatDistance(
|
|
||||||
new Date(row.original.timestamp),
|
|
||||||
new Date(row.original.until || Date.now()),
|
|
||||||
{ locale: de },
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<Timer />
|
|
||||||
Zeit Sperre ({length})
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "BAN":
|
|
||||||
return (
|
|
||||||
<div className="text-error flex gap-3">
|
|
||||||
<LockKeyhole /> Bann
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "CreatedUser",
|
|
||||||
header: "Bestraft durch",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const user = row.getValue("CreatedUser") as User;
|
|
||||||
return `${user.firstname} ${user.lastname} (${user.publicId})`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "timestamp",
|
|
||||||
header: "Time",
|
|
||||||
cell: ({ row }) => new Date(row.getValue("timestamp")).toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "actions",
|
|
||||||
header: "Actions",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const report = row.original.Report;
|
|
||||||
if (!report[0]) return null;
|
|
||||||
return (
|
|
||||||
<Link href={`/admin/report/${report[0].id}`}>
|
|
||||||
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
Report Anzeigen
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as ColumnDef<Penalty & { Report: Report[] }>[]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,23 +33,13 @@ import { UserOptionalDefaults, UserOptionalDefaultsSchema } from "@repo/db/zod";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
import { PaginatedTable, PaginatedTableRef } from "_components/PaginatedTable";
|
||||||
import { cn } from "../../../../../../helper/cn";
|
import { cn } from "../../../../../../helper/cn";
|
||||||
import {
|
import { ChartBarBigIcon, Check, Eye, PlaneIcon, Timer, X } from "lucide-react";
|
||||||
ChartBarBigIcon,
|
|
||||||
Check,
|
|
||||||
Eye,
|
|
||||||
LockKeyhole,
|
|
||||||
PlaneIcon,
|
|
||||||
RedoDot,
|
|
||||||
Timer,
|
|
||||||
X,
|
|
||||||
} from "lucide-react";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { ColumnDef } from "@tanstack/react-table";
|
import { ColumnDef } from "@tanstack/react-table";
|
||||||
import { Error } from "_components/Error";
|
import { Error } from "_components/Error";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { setStandardName } from "../../../../../../helper/discord";
|
import { setStandardName } from "../../../../../../helper/discord";
|
||||||
import { de } from "date-fns/locale";
|
import { penaltyColumns } from "(app)/admin/penalty/page";
|
||||||
import { formatDistance } from "date-fns";
|
|
||||||
|
|
||||||
interface ProfileFormProps {
|
interface ProfileFormProps {
|
||||||
user: User;
|
user: User;
|
||||||
@@ -328,74 +318,7 @@ export const UserPenalties = ({ user }: { user: User }) => {
|
|||||||
filter={{
|
filter={{
|
||||||
userId: user.id,
|
userId: user.id,
|
||||||
}}
|
}}
|
||||||
columns={
|
columns={penaltyColumns}
|
||||||
[
|
|
||||||
{
|
|
||||||
accessorKey: "type",
|
|
||||||
header: "Typ",
|
|
||||||
|
|
||||||
cell: ({ row }) => {
|
|
||||||
switch (row.getValue("type") as PenaltyType) {
|
|
||||||
case "KICK":
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<RedoDot />
|
|
||||||
Kick
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
case "TIME_BAN": {
|
|
||||||
const length = formatDistance(
|
|
||||||
new Date(row.original.timestamp),
|
|
||||||
new Date(row.original.until || Date.now()),
|
|
||||||
{ locale: de },
|
|
||||||
);
|
|
||||||
return (
|
|
||||||
<div className="text-warning flex gap-3">
|
|
||||||
<Timer />
|
|
||||||
Zeit Sperre ({length})
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
case "BAN":
|
|
||||||
return (
|
|
||||||
<div className="text-error flex gap-3">
|
|
||||||
<LockKeyhole /> Bann
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "CreatedUser",
|
|
||||||
header: "Bestraft durch",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const user = row.getValue("CreatedUser") as User;
|
|
||||||
return `${user.firstname} ${user.lastname} (${user.publicId})`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "timestamp",
|
|
||||||
header: "Time",
|
|
||||||
cell: ({ row }) => new Date(row.getValue("timestamp")).toLocaleString(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
accessorKey: "actions",
|
|
||||||
header: "Actions",
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const report = row.original.Report;
|
|
||||||
if (!report[0]) return null;
|
|
||||||
return (
|
|
||||||
<Link href={`/admin/report/${report[0].id}`}>
|
|
||||||
<button className="btn btn-sm btn-outline btn-info flex items-center gap-2">
|
|
||||||
<Eye className="w-4 h-4" />
|
|
||||||
Report Anzeigen
|
|
||||||
</button>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
|
||||||
] as ColumnDef<Penalty & { Report: Report[] }>[]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -500,7 +423,7 @@ export const AdminForm = ({
|
|||||||
<LightningBoltIcon className="w-5 h-5" /> Administration
|
<LightningBoltIcon className="w-5 h-5" /> Administration
|
||||||
</h2>
|
</h2>
|
||||||
<div className="text-left">
|
<div className="text-left">
|
||||||
<div className="card-actions pt-6">
|
<div className="card-actions pt-6 flex flex-wrap gap-2">
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const { password } = await resetPassword(user.id);
|
const { password } = await resetPassword(user.id);
|
||||||
@@ -512,10 +435,11 @@ export const AdminForm = ({
|
|||||||
background: "var(--color-base-100)",
|
background: "var(--color-base-100)",
|
||||||
color: "var(--color-base-content)",
|
color: "var(--color-base-content)",
|
||||||
},
|
},
|
||||||
|
duration: 10000,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
className="btn-sm btn-wide btn-outline btn-success"
|
className="btn-sm flex-1 min-w-[250px] btn-outline btn-success"
|
||||||
>
|
>
|
||||||
<LockOpen1Icon /> Passwort zurücksetzen
|
<LockOpen1Icon /> Passwort zurücksetzen
|
||||||
</Button>
|
</Button>
|
||||||
@@ -532,7 +456,7 @@ export const AdminForm = ({
|
|||||||
router.refresh();
|
router.refresh();
|
||||||
}}
|
}}
|
||||||
role="submit"
|
role="submit"
|
||||||
className="btn-sm btn-wide btn-outline btn-error"
|
className="btn-sm flex-1 min-w-[250px] btn-outline btn-error"
|
||||||
>
|
>
|
||||||
<HobbyKnifeIcon /> HUB zugang sperren
|
<HobbyKnifeIcon /> HUB zugang sperren
|
||||||
</Button>
|
</Button>
|
||||||
@@ -550,13 +474,16 @@ export const AdminForm = ({
|
|||||||
router.refresh();
|
router.refresh();
|
||||||
}}
|
}}
|
||||||
role="submit"
|
role="submit"
|
||||||
className="btn-sm btn-wide btn-outline btn-warning"
|
className="btn-sm flex-1 min-w-[250px] btn-outline btn-warning"
|
||||||
>
|
>
|
||||||
<HobbyKnifeIcon /> HUB zugang entsperren
|
<HobbyKnifeIcon /> HUB zugang entsperren
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{discordAccount && (
|
{discordAccount && (
|
||||||
<div className="tooltip flex-1" data-tip={`Name: ${discordAccount.username}`}>
|
<div
|
||||||
|
className="tooltip flex-1 min-w-[250px]"
|
||||||
|
data-tip={`Name: ${discordAccount.username}`}
|
||||||
|
>
|
||||||
<Button
|
<Button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await setStandardName({
|
await setStandardName({
|
||||||
|
|||||||
@@ -12,12 +12,6 @@ export const editUser = async (id: string, data: Prisma.UserUpdateInput) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const addPenalty = async (data: Prisma.PenaltyCreateInput) => {
|
|
||||||
return await prisma.penalty.create({
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const resetPassword = async (id: string) => {
|
export const resetPassword = async (id: string) => {
|
||||||
const array = new Uint8Array(8);
|
const array = new Uint8Array(8);
|
||||||
crypto.getRandomValues(array);
|
crypto.getRandomValues(array);
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ export default async function RootLayout({
|
|||||||
}>) {
|
}>) {
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
|
|
||||||
if (session?.user.isBanned)
|
|
||||||
return <Error title="Dein Account wurde gesperrt!" statusCode={403} />;
|
|
||||||
|
|
||||||
if (!session) redirect(`/login`);
|
if (!session) redirect(`/login`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,35 +1,28 @@
|
|||||||
import {
|
import { FieldValues, Path, RegisterOptions, UseFormReturn } from "react-hook-form";
|
||||||
FieldValues,
|
import { cn } from "../../../helper/cn";
|
||||||
Path,
|
|
||||||
RegisterOptions,
|
|
||||||
UseFormReturn,
|
|
||||||
} from 'react-hook-form';
|
|
||||||
import { cn } from '../../../helper/cn';
|
|
||||||
|
|
||||||
interface InputProps<T extends FieldValues>
|
interface InputProps<T extends FieldValues>
|
||||||
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'form'> {
|
extends Omit<React.InputHTMLAttributes<HTMLInputElement>, "form"> {
|
||||||
name: Path<T>;
|
name: Path<T>;
|
||||||
form: UseFormReturn<T>;
|
form: UseFormReturn<T>;
|
||||||
formOptions?: RegisterOptions<T>;
|
formOptions?: RegisterOptions<T>;
|
||||||
label?: string;
|
label?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Switch = <T extends FieldValues>({
|
export const Switch = <T extends FieldValues>({
|
||||||
name,
|
name,
|
||||||
label = name,
|
label = name,
|
||||||
form,
|
form,
|
||||||
formOptions,
|
formOptions,
|
||||||
className,
|
className,
|
||||||
...inputProps
|
...inputProps
|
||||||
}: InputProps<T>) => {
|
}: InputProps<T>) => {
|
||||||
return (
|
return (
|
||||||
<div className="form-control ">
|
<div className="form-control ">
|
||||||
<label className="label cursor-pointer w-full">
|
<label className="label cursor-pointer w-full">
|
||||||
<span className={cn('label-text text-left w-full', className)}>
|
<span className={cn("label-text text-left w-full", className)}>{label}</span>
|
||||||
{label}
|
<input type="checkbox" className={cn("toggle", className)} {...form.register(name)} />
|
||||||
</span>
|
</label>
|
||||||
<input type="checkbox" className="toggle" {...form.register(name)} />
|
</div>
|
||||||
</label>
|
);
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,17 +2,20 @@ model Penalty {
|
|||||||
id Int @id @default(autoincrement())
|
id Int @id @default(autoincrement())
|
||||||
userId String
|
userId String
|
||||||
createdUserId String
|
createdUserId String
|
||||||
|
reportId Int?
|
||||||
|
|
||||||
type PenaltyType
|
type PenaltyType
|
||||||
reason String
|
reason String
|
||||||
until DateTime?
|
until DateTime?
|
||||||
|
|
||||||
|
suspended Boolean @default(false)
|
||||||
|
|
||||||
timestamp DateTime @default(now())
|
timestamp DateTime @default(now())
|
||||||
|
|
||||||
// relations:
|
// relations:
|
||||||
User User @relation(fields: [userId], references: [id])
|
User User @relation(fields: [userId], references: [id])
|
||||||
CreatedUser User @relation("CreatedPenalties", fields: [createdUserId], references: [id])
|
CreatedUser User @relation("CreatedPenalties", fields: [createdUserId], references: [id])
|
||||||
Report Report[]
|
Report Report? @relation(fields: [reportId], references: [id])
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PenaltyType {
|
enum PenaltyType {
|
||||||
|
|||||||
@@ -8,11 +8,10 @@ model Report {
|
|||||||
reviewerComment String?
|
reviewerComment String?
|
||||||
reviewed Boolean @default(false)
|
reviewed Boolean @default(false)
|
||||||
reviewerUserId String?
|
reviewerUserId String?
|
||||||
penaltyId Int?
|
|
||||||
|
|
||||||
// relations:
|
// relations:
|
||||||
Penalty Penalty? @relation(fields: [penaltyId], references: [id])
|
Sender User @relation("SentReports", fields: [senderUserId], references: [id])
|
||||||
Sender User @relation("SentReports", fields: [senderUserId], references: [id])
|
Reported User @relation("ReceivedReports", fields: [reportedUserId], references: [id])
|
||||||
Reported User @relation("ReceivedReports", fields: [reportedUserId], references: [id])
|
Reviewer User? @relation("ReviewedReports", fields: [reviewerUserId], references: [id])
|
||||||
Reviewer User? @relation("ReviewedReports", fields: [reviewerUserId], references: [id])
|
Penalty Penalty[]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user