Discord account Linkage, penalty update
This commit is contained in:
@@ -24,7 +24,7 @@ export const RecentFlights = () => {
|
||||
({
|
||||
User: { id: session.data?.user.id },
|
||||
Mission: {
|
||||
state: { in: ["finished", "archived"] },
|
||||
state: { in: ["finished"] },
|
||||
},
|
||||
}) as Prisma.MissionOnStationUsersWhereInput
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
ConnectedAircraft,
|
||||
ConnectedDispatcher,
|
||||
DiscordAccount,
|
||||
FormerDiscordAccount,
|
||||
Penalty,
|
||||
PERMISSION,
|
||||
Prisma,
|
||||
@@ -60,6 +61,7 @@ import { penaltyColumns } from "(app)/admin/penalty/columns";
|
||||
import { addPenalty, editPenaltys } from "(app)/admin/penalty/actions";
|
||||
import { reportColumns } from "(app)/admin/report/columns";
|
||||
import { sendMailByTemplate } from "../../../../../../helper/mail";
|
||||
import Image from "next/image";
|
||||
|
||||
interface ProfileFormProps {
|
||||
user: User;
|
||||
@@ -566,6 +568,7 @@ interface AdminFormProps {
|
||||
minutes: number;
|
||||
lastLogin?: Date;
|
||||
};
|
||||
formerDiscordAccounts: (FormerDiscordAccount & { DiscordAccount: DiscordAccount | null })[];
|
||||
reports: {
|
||||
total: number;
|
||||
open: number;
|
||||
@@ -585,6 +588,7 @@ export const AdminForm = ({
|
||||
pilotTime,
|
||||
reports,
|
||||
discordAccount,
|
||||
formerDiscordAccounts,
|
||||
openBans,
|
||||
openTimebans,
|
||||
}: AdminFormProps) => {
|
||||
@@ -755,6 +759,64 @@ export const AdminForm = ({
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
<h2 className="card-title mt-2">
|
||||
<DiscordLogoIcon className="h-5 w-5" /> Frühere Discord Accounts
|
||||
</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="table-sm table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Avatar</th>
|
||||
<th>Benutzername</th>
|
||||
<th>Discord ID</th>
|
||||
<th>getrennt am</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{discordAccount && (
|
||||
<tr>
|
||||
<td>
|
||||
<Image
|
||||
src={`https://cdn.discordapp.com/avatars/${discordAccount.discordId}/${discordAccount.avatar}.png`}
|
||||
alt="Discord Avatar"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-10 w-10 rounded-full"
|
||||
/>
|
||||
</td>
|
||||
<td>{discordAccount.username}</td>
|
||||
<td>{discordAccount.discordId}</td>
|
||||
<td>N/A (Aktuell verbunden)</td>
|
||||
</tr>
|
||||
)}
|
||||
{formerDiscordAccounts.map((account) => (
|
||||
<tr key={account.discordId}>
|
||||
<td>
|
||||
{account.DiscordAccount && (
|
||||
<Image
|
||||
src={`https://cdn.discordapp.com/avatars/${account.DiscordAccount.discordId}/${account.DiscordAccount.avatar}.png`}
|
||||
alt="Discord Avatar"
|
||||
width={40}
|
||||
height={40}
|
||||
className="h-10 w-10 rounded-full"
|
||||
/>
|
||||
)}
|
||||
</td>
|
||||
<td>{account.DiscordAccount?.username || "Unbekannt"}</td>
|
||||
<td>{account.DiscordAccount?.discordId || "Unbekannt"}</td>
|
||||
<td>{new Date(account.removedAt).toLocaleDateString()}</td>
|
||||
</tr>
|
||||
))}
|
||||
{!discordAccount && formerDiscordAccounts.length === 0 && (
|
||||
<tr>
|
||||
<td colSpan={3} className="text-center text-gray-400">
|
||||
Keine Discord Accounts verknüpft
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h2 className="card-title">
|
||||
<ChartBarBigIcon className="h-5 w-5" /> Aktivität
|
||||
|
||||
@@ -12,16 +12,37 @@ import { getUserPenaltys } from "@repo/shared-components";
|
||||
|
||||
export default async function Page({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const user = await prisma.user.findUnique({
|
||||
let user = await prisma.user.findUnique({
|
||||
where: {
|
||||
id: id,
|
||||
},
|
||||
include: {
|
||||
discordAccounts: true,
|
||||
DiscordAccount: true,
|
||||
CanonicalUser: true,
|
||||
Duplicates: true,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
user = await prisma.user.findFirst({
|
||||
where: {
|
||||
publicId: id,
|
||||
},
|
||||
include: {
|
||||
DiscordAccount: true,
|
||||
CanonicalUser: true,
|
||||
Duplicates: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const formerDiscordAccounts = await prisma.formerDiscordAccount.findMany({
|
||||
where: {
|
||||
userId: user?.id,
|
||||
},
|
||||
include: {
|
||||
DiscordAccount: true,
|
||||
},
|
||||
});
|
||||
if (!user) return <Error statusCode={404} title="User not found" />;
|
||||
|
||||
const dispoSessions = await prisma.connectedDispatcher.findMany({
|
||||
@@ -121,11 +142,12 @@ export default async function Page({ params }: { params: Promise<{ id: string }>
|
||||
</div>
|
||||
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||
<AdminForm
|
||||
formerDiscordAccounts={formerDiscordAccounts}
|
||||
user={user}
|
||||
dispoTime={dispoTime}
|
||||
pilotTime={pilotTime}
|
||||
reports={reports}
|
||||
discordAccount={user.discordAccounts[0]}
|
||||
discordAccount={user.DiscordAccount ?? undefined}
|
||||
openBans={openBans}
|
||||
openTimebans={openTimeban}
|
||||
/>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { User2 } from "lucide-react";
|
||||
import { PaginatedTable } from "../../../_components/PaginatedTable";
|
||||
import Link from "next/link";
|
||||
import { ColumnDef } from "@tanstack/react-table";
|
||||
import { DiscordAccount, Prisma, User } from "@repo/db";
|
||||
import { DiscordAccount, Penalty, Prisma, User } from "@repo/db";
|
||||
import { useSession } from "next-auth/react";
|
||||
|
||||
const AdminUserPage = () => {
|
||||
@@ -21,16 +21,15 @@ const AdminUserPage = () => {
|
||||
{ firstname: { contains: searchTerm, mode: "insensitive" } },
|
||||
{ lastname: { contains: searchTerm, mode: "insensitive" } },
|
||||
{ email: { contains: searchTerm, mode: "insensitive" } },
|
||||
{
|
||||
discordAccounts: {
|
||||
some: { username: { contains: searchTerm, mode: "insensitive" } },
|
||||
},
|
||||
},
|
||||
{ publicId: { contains: searchTerm, mode: "insensitive" } },
|
||||
{ DiscordAccount: { username: { contains: searchTerm, mode: "insensitive" } } },
|
||||
],
|
||||
} as Prisma.UserWhereInput;
|
||||
}}
|
||||
include={{
|
||||
discordAccounts: true,
|
||||
DiscordAccount: true,
|
||||
ReceivedReports: true,
|
||||
Penaltys: true,
|
||||
}}
|
||||
initialOrderBy={[
|
||||
{
|
||||
@@ -55,6 +54,15 @@ const AdminUserPage = () => {
|
||||
{
|
||||
header: "Berechtigungen",
|
||||
cell(props) {
|
||||
const activePenaltys = props.row.original.Penaltys.filter(
|
||||
(penalty) =>
|
||||
!penalty.suspended &&
|
||||
(penalty.type === "BAN" ||
|
||||
(penalty.type === "TIME_BAN" && penalty!.until! > new Date())),
|
||||
);
|
||||
if (activePenaltys.length > 0) {
|
||||
return <span className="font-bold text-red-600">AKTIVE STRAFE</span>;
|
||||
}
|
||||
if (props.row.original.permissions.length === 0) {
|
||||
return <span className="text-gray-700">Keine</span>;
|
||||
} else if (props.row.original.permissions.includes("ADMIN_USER_ADVANCED")) {
|
||||
@@ -69,14 +77,26 @@ const AdminUserPage = () => {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Strafen / Reports",
|
||||
cell(props) {
|
||||
const penaltyCount = props.row.original.Penaltys.length;
|
||||
const reportCount = props.row.original.ReceivedReports.length;
|
||||
return (
|
||||
<span className="w-full text-center">
|
||||
{penaltyCount} / {reportCount}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
header: "Discord",
|
||||
cell(props) {
|
||||
const discord = props.row.original.discordAccounts;
|
||||
if (discord.length === 0) {
|
||||
const discord = props.row.original.DiscordAccount;
|
||||
if (!discord) {
|
||||
return <span className="text-gray-700">Nicht verbunden</span>;
|
||||
}
|
||||
return <span>{discord.map((d) => d.username).join(", ")}</span>;
|
||||
return <span>{discord.username}</span>;
|
||||
},
|
||||
},
|
||||
...(session?.user.permissions.includes("ADMIN_USER_ADVANCED")
|
||||
@@ -97,7 +117,13 @@ const AdminUserPage = () => {
|
||||
</div>
|
||||
),
|
||||
},
|
||||
] as ColumnDef<User & { discordAccounts: DiscordAccount[] }>[]
|
||||
] as ColumnDef<
|
||||
User & {
|
||||
DiscordAccount: DiscordAccount;
|
||||
ReceivedReports: Report[];
|
||||
Penaltys: Penalty[];
|
||||
}
|
||||
>[]
|
||||
} // Define the columns for the user table
|
||||
leftOfSearch={
|
||||
<p className="flex items-center gap-2 text-left text-2xl font-semibold">
|
||||
|
||||
@@ -31,7 +31,7 @@ export const ProfileForm = ({
|
||||
}: {
|
||||
user: User;
|
||||
penaltys: Penalty[];
|
||||
discordAccount?: DiscordAccount;
|
||||
discordAccount: DiscordAccount | null;
|
||||
}): React.JSX.Element => {
|
||||
const canEdit = penaltys.length === 0 && !user.isBanned;
|
||||
|
||||
@@ -215,9 +215,11 @@ export const ProfileForm = ({
|
||||
export const SocialForm = ({
|
||||
discordAccount,
|
||||
user,
|
||||
penaltys,
|
||||
}: {
|
||||
discordAccount?: DiscordAccount;
|
||||
discordAccount: DiscordAccount | null;
|
||||
user: User;
|
||||
penaltys: Penalty[];
|
||||
}): React.JSX.Element | null => {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [vatsimLoading, setVatsimLoading] = useState(false);
|
||||
@@ -235,6 +237,7 @@ export const SocialForm = ({
|
||||
},
|
||||
resolver: zodResolver(schema),
|
||||
});
|
||||
const canUnlinkDiscord = !user.isBanned && penaltys.length === 0;
|
||||
|
||||
if (!user) return null;
|
||||
return (
|
||||
@@ -262,7 +265,7 @@ export const SocialForm = ({
|
||||
</h2>
|
||||
<div>
|
||||
<div>
|
||||
{discordAccount ? (
|
||||
{discordAccount && canUnlinkDiscord ? (
|
||||
<Button
|
||||
className="btn-success btn-block btn-outline hover:btn-error group transition-all duration-0"
|
||||
isLoading={isLoading}
|
||||
@@ -329,14 +332,13 @@ export const SocialForm = ({
|
||||
export const DeleteForm = ({
|
||||
user,
|
||||
penaltys,
|
||||
reports,
|
||||
}: {
|
||||
user: User;
|
||||
penaltys: Penalty[];
|
||||
reports: Report[];
|
||||
}) => {
|
||||
const router = useRouter();
|
||||
const userCanDelete = penaltys.length === 0 && !user.isBanned && reports.length === 0;
|
||||
const userCanDelete = penaltys.length === 0 && !user.isBanned;
|
||||
return (
|
||||
<div className="card-body">
|
||||
<h2 className="card-title mb-5">
|
||||
@@ -344,11 +346,11 @@ export const DeleteForm = ({
|
||||
</h2>
|
||||
{!userCanDelete && (
|
||||
<div className="text-left">
|
||||
<h2 className="text-warning text-lg">Du kannst dein Konto zurzeit nicht löschen!</h2>
|
||||
<h2 className="text-warning text-lg">Du kannst dein Konto nicht löschen!</h2>
|
||||
<p className="text-sm text-gray-400">
|
||||
Scheinbar hast du Strafen oder Reports in deinem Profil hinterlegt. Um unsere Community
|
||||
zu schützen kannst du deinen Account nicht löschen. Bitte erstelle ein Support-Ticket,
|
||||
wenn du Fragen dazu hast.
|
||||
Da du Strafen hast oder hattest, kannst du deinen Account nicht löschen. Um unsere
|
||||
Community zu schützen kannst du deinen Account nicht löschen. Bitte erstelle ein
|
||||
Support-Ticket, wenn du Fragen dazu hast.
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -4,9 +4,19 @@ import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export const unlinkDiscord = async (userId: string) => {
|
||||
await prisma.discordAccount.deleteMany({
|
||||
const discordAccount = await prisma.discordAccount.update({
|
||||
where: {
|
||||
userId: userId,
|
||||
},
|
||||
data: {
|
||||
userId: null,
|
||||
},
|
||||
});
|
||||
|
||||
await prisma.formerDiscordAccount.create({
|
||||
data: {
|
||||
userId,
|
||||
discordId: discordAccount.discordId,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@ import { getServerSession } from "../../api/auth/[...nextauth]/auth";
|
||||
import { ProfileForm, SocialForm, PasswordForm, DeleteForm } from "./_components/forms";
|
||||
import { GearIcon } from "@radix-ui/react-icons";
|
||||
import { Error } from "_components/Error";
|
||||
import { getUserPenaltys } from "@repo/shared-components";
|
||||
|
||||
export default async function Page() {
|
||||
const session = await getServerSession();
|
||||
@@ -13,15 +14,17 @@ export default async function Page() {
|
||||
id: session.user.id,
|
||||
},
|
||||
include: {
|
||||
discordAccounts: true,
|
||||
DiscordAccount: true,
|
||||
Penaltys: true,
|
||||
},
|
||||
});
|
||||
const userPenaltys = await prisma.penalty.findMany({
|
||||
where: {
|
||||
userId: session.user.id,
|
||||
suspended: false,
|
||||
},
|
||||
});
|
||||
const activePenaltys = await getUserPenaltys(session.user.id);
|
||||
|
||||
const userReports = await prisma.report.findMany({
|
||||
where: {
|
||||
@@ -30,7 +33,7 @@ export default async function Page() {
|
||||
});
|
||||
|
||||
if (!user) return <Error statusCode={401} title="Dein Account wurde nicht gefunden" />;
|
||||
const discordAccount = user?.discordAccounts[0];
|
||||
const discordAccount = user?.DiscordAccount;
|
||||
return (
|
||||
<div className="grid grid-cols-6 gap-4">
|
||||
<div className="col-span-full">
|
||||
@@ -39,16 +42,24 @@ export default async function Page() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||
<ProfileForm user={user} penaltys={userPenaltys} discordAccount={discordAccount} />
|
||||
<ProfileForm
|
||||
user={user}
|
||||
discordAccount={discordAccount}
|
||||
penaltys={[...activePenaltys.openBans, ...activePenaltys.openTimeban]}
|
||||
/>
|
||||
</div>
|
||||
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||
<SocialForm discordAccount={discordAccount} user={user} />
|
||||
<SocialForm
|
||||
user={user}
|
||||
discordAccount={discordAccount}
|
||||
penaltys={[...activePenaltys.openBans, ...activePenaltys.openTimeban]}
|
||||
/>
|
||||
</div>
|
||||
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||
<PasswordForm />
|
||||
</div>
|
||||
<div className="card bg-base-200 col-span-6 mb-4 shadow-xl xl:col-span-3">
|
||||
<DeleteForm user={user} penaltys={userPenaltys} reports={userReports} />
|
||||
<DeleteForm user={user} reports={userReports} penaltys={userPenaltys} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user