Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Admin delete #215

Merged
merged 19 commits into from
Feb 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
dfa58b7
feat: frontend support for admin delete button
blwelsh Jan 23, 2025
78719e3
feat: added trash can icon for delete user
blwelsh Jan 23, 2025
4481563
feat: deletecomponentmodal will close the user modal when 'yes' is cl…
blwelsh Jan 23, 2025
aefa538
feat: changed delete button to be component
blwelsh Jan 24, 2025
ef12fa2
fix: adjusted DeleteConfirmation.tsx props to follow TS conventions
blwelsh Jan 24, 2025
f31d40f
feat: delete user button now centered beneath account box
blwelsh Jan 30, 2025
25091c5
feat: userData is now passed as a prop to DeleteConfirmation
blwelsh Jan 30, 2025
3ab63f0
feat: basic deletion now works (only in mongodb not clerk)
blwelsh Feb 3, 2025
1461e2e
feat: deletion now works with clerk
blwelsh Feb 3, 2025
92e4e95
feat: redirected to homepage on self-delete
blwelsh Feb 4, 2025
b1de452
fix: admin delete now correctly deletes user in clerk
blwelsh Feb 10, 2025
ca0bb51
feat: on admin delete, table now updates
blwelsh Feb 10, 2025
17fdae2
fix: update make admin button color to match site theme
blwelsh Feb 10, 2025
88b0543
fix: changed delete user to delete accout on button
blwelsh Feb 10, 2025
91b71a0
feat: group and event updates on user deletion
blwelsh Feb 10, 2025
76d11af
Merge branch 'main' into admin-delete
SilveerDusk Feb 16, 2025
a2f4781
Deletes newlines to test new deployment
SilveerDusk Feb 16, 2025
d8c386e
formatting changes to clean up code and test new deployment
SilveerDusk Feb 16, 2025
fabab20
fix: removeFunction is now an optional member of SingleVisitorCompone…
blwelsh Feb 16, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
688 changes: 688 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

53 changes: 31 additions & 22 deletions src/app/admin/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ const capitalizeFirstLetter = (str: string): string => {
const UserList = () => {
// states
const [customUser, setUsers] = useState<IUserWithHours[]>([]);
const [filteredUsers, setFilteredUsers] = useState<IUserWithHours[]>([]);
const {users, isLoading, isError} = useUsers()
const [searchTerm, setSearchTerm] = useState("");
const [sortOrder, setSortOrder] = useState<{ value: string; label: string }>({
Expand Down Expand Up @@ -123,7 +124,6 @@ const UserList = () => {
const eventsAttendedNames = await Promise.all(
user.eventsAttended.map((event) => fetchEventName(event.eventId))
);

return {
...user,
totalHoursFormatted: formatHours(
Expand All @@ -149,7 +149,8 @@ const UserList = () => {
fetchUsers();
}, [isError, isLoading]);

const filteredUsers = customUser
useEffect(() => {
setFilteredUsers(customUser
.filter((user) =>
`${user.firstName.toLowerCase()} ${user.lastName.toLowerCase()}`.includes(
searchTerm.toLowerCase()
Expand All @@ -159,13 +160,21 @@ const UserList = () => {
sortOrder.value === "firstName"
? a.firstName.localeCompare(b.firstName)
: a.lastName.localeCompare(b.lastName)
);
));
}, [customUser]);



const sortOptions = [
{ value: "firstName", label: "First Name" },
{ value: "lastName", label: "Last Name" },
];

const removeUser = (userId: string) => {
const newUsers = customUser.filter((user) => user._id != userId);
setUsers(newUsers);
}


const csvData = customUser.map((user) => ({
firstName: user.firstName,
Expand Down Expand Up @@ -287,22 +296,22 @@ const UserList = () => {
{/* {isLoading && !users && !isError && <div>Loading...</div>}
{isError && <div>Error occurred.</div>}
*/}
<Box>
<Box>
<Table
variant="striped"
size={tableSize}
className={style.customTable}
variant="striped"
size={tableSize}
className={style.customTable}
>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Email</Th>
<Th>Total Hours</Th>
<Th>Role</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
<Thead>
<Tr>
<Th>Name</Th>
<Th>Email</Th>
<Th>Total Hours</Th>
<Th>Role</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{filteredUsers.length === 0 ? (
<Tr>
<Td colSpan={5} textAlign="center">
Expand All @@ -316,17 +325,17 @@ const UserList = () => {
<Td>{`${user.firstName} ${user.lastName}`}</Td>
<Td>{user.email}</Td>
<Td>{user.totalHoursFormatted}</Td>

<Td>{capitalizeFirstLetter(user.role)}</Td>
<Td>
<SingleVisitorComponent visitorData={user} />
<SingleVisitorComponent visitorData={user} removeFunction={removeUser} />
</Td>
</Tr>
))
)}
</Tbody>
</Table>
</Box>
</Tbody>
</Table>
</Box>
</div>
</div>
);
Expand Down
60 changes: 60 additions & 0 deletions src/app/api/user/[userId]/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import connectDB from "@database/db";
import User, { IUser } from "@database/userSchema";
import Group from "@database/groupSchema";
import Event from "@database/eventSchema";
import { NextResponse, NextRequest } from "next/server";
import { revalidateTag } from "next/cache";
import { clerkClient } from '@clerk/nextjs/server'


type IParams = {
params: {
Expand Down Expand Up @@ -98,3 +102,59 @@ export async function PATCH(req: NextRequest, { params }: IParams) {
);
}
}

export async function DELETE(req: NextRequest, {params}: IParams) {

await connectDB(); // Connect to the database

const { userId } = params; // Destructure the userId from params

const bodyText = await new Response(req.body).text();
const email = JSON.parse(bodyText);


try {


const clerkUser = await clerkClient.users.getUserList({emailAddress: [email]});
await clerkClient.users.deleteUser(clerkUser.data[0].id);

const user = await User.findByIdAndDelete(userId).orFail();


if (!user) {
return NextResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}


// Remove user from groups
await Group.updateMany({groupees: userId},
{$pull: {groupees: userId}});


// Update events - set attendee ID to be null
await Event.updateMany({attendeeIds: userId},
{$pull: {attendeeIds: userId}, $push: {attendeeIds: null}}
)

await Event.updateMany({registeredIds: userId},
{$pull: {registeredIds: userId}, $push: {registeredIds: null}}
)

return NextResponse.json("User deleted: " + userId, { status: 200 });


} catch (err) {
console.error("Error deleting user (UserId = " + userId + "):", err);
return NextResponse.json(
"User not deleted (UserId = " + userId + ") " + err,
{ status: 400 }
);
}



}
155 changes: 155 additions & 0 deletions src/app/components/DeleteConfirmation.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
"use client";
import React, { useState, useEffect } from "react";
import {
Box,
Text,
Flex,
Button,
Modal,
ModalOverlay,
ModalContent,
ModalHeader,
ModalBody,
ModalCloseButton,
Table,
Thead,
Tr,
Th,
Tbody,
Td,
useDisclosure,
Icon,
Center,
} from "@chakra-ui/react";
import styles from "../styles/admin/editEvent.module.css";
import { IUser } from "@database/userSchema";
import { eventIndividualHours } from ".././lib/hours";
import { Schema } from "mongoose";
import { FaRegTrashAlt } from "react-icons/fa";
import {useUser, useClerk} from "@clerk/nextjs";
import { removeUserCookie } from "app/actions/cookieactions";
import { mutate } from "swr";


interface DeleteProps {
closeFromChild: React.MouseEventHandler<HTMLButtonElement>;
userData: IUser | null;
children?: React.ReactNode;
isSelf: boolean;
removeFunction?: (userId: string) => void;

}


function DeleteConfirmation({closeFromChild, userData, isSelf, removeFunction}: DeleteProps) {

const { isOpen, onOpen, onClose } = useDisclosure();


const {signOut} = useClerk();
// const {user} = useUser();


async function handleDelete(){


// Remove cookies when the user signs out
if (isSelf) {
await removeUserCookie();
signOut({ redirectUrl: '/' });
}


if (userData!=null) {
//const clerk_id = user.id;
const email = userData.email;

const res = await fetch(`/api/user/${userData._id}`, {
method: 'DELETE',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(email),
});

if (removeFunction) {
removeFunction(userData._id);
}
}

onClose();


}


return (<>
<Button
mt={2}
color="#d93636"
bg="white"
border="2px"
_hover={{ bg: "#d93636", color: "white" }}
onClick={onOpen}
variant="ghost"
>
<Icon color="36d936" fontSize="1.4rem" p={0.5}>
<FaRegTrashAlt />
</Icon>
Delete User
</Button>

<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay/>
<ModalContent
style={{ width: "40vw", height: "30vh", overflow: "auto" }}
maxW="100rem"
>

<ModalHeader
style={{
padding: "1% 5%",
textAlign: "left",
fontSize: "35px",
fontWeight: "bold",
fontFamily: "Lato",
width: "100%",
}}
>
<Flex direction="column" align="left" p={4}>
<Text>
Delete Account
</Text>

</Flex>
</ModalHeader>
<ModalCloseButton />
<hr />
<ModalBody
style={{ display: "flex", flexDirection: "column", padding: "0%" }}
className={styles.parentContainer}
>
<Flex direction="column" align="center">
<Text className={styles.boldText} p={2}>
Are you sure you want to delete this account?
</Text>

</Flex>
<Flex direction="column" align="center" p={4}>
<Button
mt={2}
bg="#d93636"
color="white"
_hover={{ bg: "#d93636", color: "white" }}
onClick={async() => {await handleDelete(); closeFromChild}}
>
Yes
</Button>
</Flex>
</ModalBody>
</ModalContent>
</Modal>
</>
);
}

export default DeleteConfirmation;

Loading