This commit is contained in:
PxlLoewe
2025-06-23 19:33:00 -07:00
parent 65ea4640c3
commit dabcad2525
13 changed files with 294 additions and 292 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>
); );
} }

View File

@@ -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>
</>
);
};

View File

@@ -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,
});
};

View File

@@ -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[] }>[]
}
/> />
); );
} }

View File

@@ -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>
); );

View File

@@ -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({

View File

@@ -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);

View File

@@ -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 (

View File

@@ -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>
);
}; };

View File

@@ -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 {

View File

@@ -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[]
} }