diff --git a/apps/web/src/actions/admin/user-actions.ts b/apps/web/src/actions/admin/user-actions.ts index 919a9320..78e769d0 100644 --- a/apps/web/src/actions/admin/user-actions.ts +++ b/apps/web/src/actions/admin/user-actions.ts @@ -4,7 +4,7 @@ import { adminAction } from "@/lib/safe-action"; import { returnValidationErrors } from "next-safe-action"; import { z } from "zod"; import { perms } from "config"; -import { userCommonData } from "db/schema"; +import { userCommonData, bannedUsers } from "db/schema"; import { db } from "db"; import { eq } from "db/drizzle"; import { revalidatePath } from "next/cache"; @@ -60,3 +60,43 @@ export const setUserApproval = adminAction return { success: true }; }, ); + +export const banUser = adminAction + .schema( + z.object({ + userIDToUpdate: z.string(), + reason: z.string(), + }), + ) + .action( + async ({ + parsedInput: { userIDToUpdate, reason }, + ctx: { user, userId }, + }) => { + //TODO: Validate Permission + + await db.insert(bannedUsers).values({ + userID: userIDToUpdate, + reason: reason, + bannedByID: user.clerkID, + }); + revalidatePath(`/admin/users/${userIDToUpdate}`); + return { success: true }; + }, + ); + +export const removeUserBan = adminAction + .schema( + z.object({ + userIDToUpdate: z.string(), + }), + ) + .action(async ({ parsedInput: { userIDToUpdate }, ctx: { user } }) => { + //TODO: Validate Permission + + await db + .delete(bannedUsers) + .where(eq(bannedUsers.userID, userIDToUpdate)); + revalidatePath(`/admin/users/${userIDToUpdate}`); + return { success: true }; + }); diff --git a/apps/web/src/app/admin/users/[slug]/page.tsx b/apps/web/src/app/admin/users/[slug]/page.tsx index a3a64ef4..58c263a8 100644 --- a/apps/web/src/app/admin/users/[slug]/page.tsx +++ b/apps/web/src/app/admin/users/[slug]/page.tsx @@ -15,6 +15,10 @@ import { isUserAdmin } from "@/lib/utils/server/admin"; import ApproveUserButton from "@/components/admin/users/ApproveUserButton"; import c from "config"; import { getHacker, getUser } from "db/functions"; +import BanUserDialog from "@/components/admin/users/BanUserDialog"; +import { db, eq } from "db"; +import { bannedUsers } from "db/schema"; +import RemoveUserBanDialog from "@/components/admin/users/RemoveUserBanDialog"; export default async function Page({ params }: { params: { slug: string } }) { const { userId } = await auth(); @@ -30,8 +34,20 @@ export default async function Page({ params }: { params: { slug: string } }) { return

User Not Found

; } + const banInstance = await db.query.bannedUsers.findFirst({ + where: eq(bannedUsers.userID, user.clerkID), + }); + return (
+ {!!banInstance && ( +
+ + This user has been suspended, reason for suspenssion:{" "} + + {banInstance.reason} +
+ )}
@@ -57,6 +73,20 @@ export default async function Page({ params }: { params: { slug: string } }) { currPermision={user.role} userID={user.clerkID} /> + + {!!banInstance ? ( + + ) : ( + + )} + {(c.featureFlags.core.requireUsersApproval as boolean) && ( +
+

+ Account Suspended +

+

+ Dear {user.firstName} {user.lastName}, +

+

+ {" "} + Your account was suspended +

+

+ Reason: + {banInstance.reason} +

+
+ Contact administration for further assistance. +
+
+
+ ); +} + +export const runtime = "edge"; diff --git a/apps/web/src/components/admin/users/BanUserDialog.tsx b/apps/web/src/components/admin/users/BanUserDialog.tsx new file mode 100644 index 00000000..bf986f98 --- /dev/null +++ b/apps/web/src/components/admin/users/BanUserDialog.tsx @@ -0,0 +1,79 @@ +"use client"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/shadcn/ui/dialog"; +import { Button } from "@/components/shadcn/ui/button"; +import { toast } from "sonner"; +import { useAction } from "next-safe-action/hooks"; +import { banUser } from "@/actions/admin/user-actions"; +import { useState } from "react"; +import { Textarea } from "@/components/shadcn/ui/textarea"; + +interface BanUserDialogProps { + userID: string; + name: string; +} + +export default function BanUserDialog({ userID, name }: BanUserDialogProps) { + const [reason, setReason] = useState(""); + const [open, setOpen] = useState(false); + + const { execute } = useAction(banUser, { + async onSuccess() { + toast.dismiss(); + toast.success("Successfully Banned!"); + }, + async onError(e) { + toast.dismiss(); + toast.error("An error occurred while banning this user."); + console.error(e); + }, + }); + + return ( + + + + + + + Ban {name}. + + Ban this user (not permament action). + + +
+
+