Merge remote-tracking branch 'origin' into token-auth

This commit is contained in:
Tuan Dang
2024-07-09 11:52:14 +07:00
22 changed files with 390 additions and 28 deletions
@@ -2,17 +2,17 @@ import { useRouter } from "next/router";
import { createNotification } from "@app/components/notifications";
import { Button, DeleteActionModal } from "@app/components/v2";
import { useDeleteUser } from "@app/hooks/api";
import { useDeleteMe } from "@app/hooks/api";
import { usePopUp } from "@app/hooks/usePopUp";
export const DeleteAccountSection = () => {
const router = useRouter();
const { popUp, handlePopUpOpen, handlePopUpClose, handlePopUpToggle } = usePopUp([
"deleteAccount"
] as const);
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteUser();
const { mutateAsync: deleteUserMutateAsync, isLoading } = useDeleteMe();
const handleDeleteAccountSubmit = async () => {
try {
@@ -26,11 +26,13 @@ import { useGetOrganizations, useUpdateServerConfig } from "@app/hooks/api";
import { AuthPanel } from "./AuthPanel";
import { RateLimitPanel } from "./RateLimitPanel";
import { UserPanel } from "./UserPanel";
enum TabSections {
Settings = "settings",
Auth = "auth",
RateLimit = "rate-limit"
RateLimit = "rate-limit",
Users = "users"
}
enum SignUpModes {
@@ -135,6 +137,7 @@ export const AdminDashboardPage = () => {
<Tab value={TabSections.Settings}>General</Tab>
<Tab value={TabSections.Auth}>Authentication</Tab>
<Tab value={TabSections.RateLimit}>Rate Limit</Tab>
<Tab value={TabSections.Users}>Users</Tab>
</div>
</TabList>
<TabPanel value={TabSections.Settings}>
@@ -320,6 +323,9 @@ export const AdminDashboardPage = () => {
<TabPanel value={TabSections.RateLimit}>
<RateLimitPanel />
</TabPanel>
<TabPanel value={TabSections.Users}>
<UserPanel />
</TabPanel>
</Tabs>
</div>
)}
@@ -0,0 +1,161 @@
import { useState } from "react";
import { faMagnifyingGlass, faUsers, faXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { createNotification } from "@app/components/notifications";
import {
Button,
DeleteActionModal,
EmptyState,
IconButton,
Input,
Table,
TableContainer,
TableSkeleton,
TBody,
Td,
Th,
THead,
Tr
} from "@app/components/v2";
import { useUser } from "@app/context";
import { useDebounce, usePopUp } from "@app/hooks";
import { useAdminDeleteUser, useAdminGetUsers } from "@app/hooks/api";
import { UsePopUpState } from "@app/hooks/usePopUp";
const UserPanelTable = ({
handlePopUpOpen
}: {
handlePopUpOpen: (
popUpName: keyof UsePopUpState<["removeUser"]>,
data: {
username: string;
id: string;
}
) => void;
}) => {
const [searchUserFilter, setSearchUserFilter] = useState("");
const { user } = useUser();
const userId = user?.id || "";
const debounedSearchTerm = useDebounce(searchUserFilter, 500);
const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage } = useAdminGetUsers({
limit: 20,
searchTerm: debounedSearchTerm
});
const isEmpty = !isLoading && !data?.pages?.[0].length;
return (
<>
<Input
value={searchUserFilter}
onChange={(e) => setSearchUserFilter(e.target.value)}
leftIcon={<FontAwesomeIcon icon={faMagnifyingGlass} />}
placeholder="Search users..."
/>
<div className="mt-4">
<TableContainer>
<Table>
<THead>
<Tr>
<Th className="w-5/12">Name</Th>
<Th className="w-5/12">Username</Th>
<Th className="w-5" />
</Tr>
</THead>
<TBody>
{isLoading && <TableSkeleton columns={4} innerKey="users" />}
{!isLoading &&
data?.pages?.map((users) =>
users.map(({ username, email, firstName, lastName, id }) => {
const name = firstName || lastName ? `${firstName} ${lastName}` : "-";
return (
<Tr key={`user-${id}`} className="w-full">
<Td className="w-5/12">{name}</Td>
<Td className="w-5/12">{email}</Td>
<Td>
{userId !== id && (
<div className="flex justify-end">
<IconButton
size="lg"
colorSchema="danger"
variant="plain"
ariaLabel="update"
isDisabled={userId === id}
onClick={() => handlePopUpOpen("removeUser", { username, id })}
>
<FontAwesomeIcon icon={faXmark} />
</IconButton>
</div>
)}
</Td>
</Tr>
);
})
)}
</TBody>
</Table>
{!isLoading && isEmpty && <EmptyState title="No users found" icon={faUsers} />}
</TableContainer>
{!isEmpty && (
<Button
className="mt-4 py-3 text-sm"
isFullWidth
variant="star"
isLoading={isFetchingNextPage}
isDisabled={isFetchingNextPage || !hasNextPage}
onClick={() => fetchNextPage()}
>
{hasNextPage ? "Load More" : "End of list"}
</Button>
)}
</div>
</>
);
};
export const UserPanel = () => {
const { handlePopUpToggle, popUp, handlePopUpOpen, handlePopUpClose } = usePopUp([
"removeUser"
] as const);
const { mutateAsync: deleteUser } = useAdminDeleteUser();
const handleRemoveUser = async () => {
const { id } = popUp?.removeUser?.data as { id: string; username: string };
try {
await deleteUser(id);
createNotification({
type: "success",
text: "Successfully deleted user"
});
} catch (err) {
createNotification({
type: "error",
text: "Error deleting user"
});
}
handlePopUpClose("removeUser");
};
return (
<div className="mb-6 rounded-lg border border-mineshaft-600 bg-mineshaft-900 p-4">
<div className="mb-4">
<p className="text-xl font-semibold text-mineshaft-100">Users</p>
</div>
<UserPanelTable handlePopUpOpen={handlePopUpOpen} />
<DeleteActionModal
isOpen={popUp.removeUser.isOpen}
deleteKey="remove"
title={`Are you sure you want to delete User with username ${
(popUp?.removeUser?.data as { id: string; username: string })?.username || ""
}?`}
onChange={(isOpen) => handlePopUpToggle("removeUser", isOpen)}
onDeleteApproved={handleRemoveUser}
/>
</div>
);
};