generated from jotyy/Mantine-Admin
-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Improve ordering for user join chart
- User management dashboard
- Loading branch information
Showing
8 changed files
with
305 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import React from "react"; | ||
import { PageContainer } from "@/components/PageContainer/PageContainer"; | ||
import UsersManagementTable from "@/components/profile/UsersManagementTable"; | ||
|
||
const Page = () => { | ||
return ( | ||
<PageContainer title={"Users"}> | ||
<UsersManagementTable /> | ||
</PageContainer> | ||
); | ||
}; | ||
|
||
export default Page; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
"use client"; | ||
|
||
import React, { useState } from "react"; | ||
import { useUserProfiles } from "@/components/profile/hooks/useUserProfiles"; | ||
import { MantineReactTable, MRT_ColumnDef } from "mantine-react-table"; | ||
import { | ||
CreateReportRequestDto, | ||
FindAllProfileResponseItemDto, | ||
} from "@/wrapper/server"; | ||
import CenteredLoading from "@/components/general/CenteredLoading"; | ||
import CenteredErrorMessage from "@/components/general/CenteredErrorMessage"; | ||
import { Badge, MantineColor, Menu, Modal, Paper } from "@mantine/core"; | ||
import { useCustomTable } from "@/components/table/hooks/use-custom-table"; | ||
import { UserAvatarGroup } from "@/components/general/avatar/UserAvatarGroup"; | ||
import { useDisclosure } from "@mantine/hooks"; | ||
import ReportCreateForm from "@/components/report/form/ReportCreateForm"; | ||
import sourceType = CreateReportRequestDto.sourceType; | ||
|
||
const columns: MRT_ColumnDef<FindAllProfileResponseItemDto>[] = [ | ||
{ | ||
accessorKey: "profile.username", | ||
header: "Username", | ||
Cell: ({ row }) => { | ||
return <UserAvatarGroup userId={row.original.profile.userId} />; | ||
}, | ||
}, | ||
{ | ||
accessorFn: (row) => { | ||
if (row.isSuspended) { | ||
return "SUSPENDED"; | ||
} else if (row.isBanned) { | ||
return "BANNED"; | ||
} | ||
return "NORMAL"; | ||
}, | ||
header: "Status", | ||
filterVariant: "select", | ||
mantineFilterSelectProps: { | ||
data: [ | ||
{ label: "Normal", value: "NORMAL" }, | ||
{ label: "Suspended", value: "SUSPENDED" }, | ||
{ label: "Banned", value: "BANNED" }, | ||
], | ||
}, | ||
Cell: ({ row, renderedCellValue }) => { | ||
const item = row.original; | ||
const color: MantineColor = | ||
item.isSuspended || item.isBanned ? "red" : "green"; | ||
return <Badge color={color}>{renderedCellValue}</Badge>; | ||
}, | ||
}, | ||
{ | ||
header: "Joined at", | ||
accessorFn: (row) => | ||
new Date(row.profile.createdAt).toLocaleString("en-US"), | ||
sortingFn: (rowA, rowB, columnId) => { | ||
const createDateA = new Date(rowA.original.profile.createdAt); | ||
const createDateB = new Date(rowB.original.profile.createdAt); | ||
|
||
return createDateA.getTime() - createDateB.getTime(); | ||
}, | ||
id: "createdAt", | ||
}, | ||
]; | ||
|
||
const UsersManagementTable = () => { | ||
const { data, isLoading, isError, isFetching } = useUserProfiles(); | ||
|
||
const [reportModalOpened, reportModalUtils] = useDisclosure(); | ||
|
||
const [reportedUserId, setReportedUserId] = useState<string | undefined>( | ||
undefined, | ||
); | ||
|
||
const table = useCustomTable<FindAllProfileResponseItemDto>({ | ||
columns, | ||
data: data ?? [], | ||
rowCount: data?.length ?? 0, | ||
state: { | ||
isLoading: isLoading, | ||
showAlertBanner: isError, | ||
showProgressBars: isFetching, | ||
}, | ||
enableRowActions: true, | ||
renderRowActionMenuItems: (item) => { | ||
const profile = item.row.original.profile; | ||
return ( | ||
<> | ||
<Menu.Item | ||
onClick={() => { | ||
setReportedUserId(profile.userId); | ||
reportModalUtils.open(); | ||
}} | ||
> | ||
Generate report | ||
</Menu.Item> | ||
</> | ||
); | ||
}, | ||
}); | ||
|
||
if (isLoading) { | ||
return <CenteredLoading message="Loading..." />; | ||
} else if (isError) { | ||
return ( | ||
<CenteredErrorMessage | ||
message={"Failed to load users. Please try again."} | ||
/> | ||
); | ||
} else if (data == undefined) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<Paper withBorder radius="md" p="md" mt="lg"> | ||
<Modal | ||
title={"Generate report"} | ||
opened={reportModalOpened} | ||
onClose={reportModalUtils.close} | ||
> | ||
{reportedUserId && ( | ||
<ReportCreateForm | ||
sourceId={reportedUserId} | ||
sourceType={sourceType.PROFILE} | ||
onSuccess={reportModalUtils.close} | ||
/> | ||
)} | ||
</Modal> | ||
<MantineReactTable table={table} /> | ||
</Paper> | ||
); | ||
}; | ||
|
||
export default UsersManagementTable; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import React, { useMemo } from "react"; | ||
import { CreateReportRequestDto, ReportService } from "@/wrapper/server"; | ||
import { useForm } from "react-hook-form"; | ||
import { | ||
Button, | ||
ComboboxItem, | ||
Select, | ||
Stack, | ||
Text, | ||
Textarea, | ||
} from "@mantine/core"; | ||
import { | ||
reportCategoryToDescription, | ||
reportCategoryToString, | ||
} from "@/components/report/util/reportCategoryToString"; | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { notifications } from "@mantine/notifications"; | ||
import { z } from "zod"; | ||
|
||
const ReportCreateFormSchema = z.object({ | ||
category: z | ||
.nativeEnum(CreateReportRequestDto.category) | ||
.default(CreateReportRequestDto.category.SPAM), | ||
reason: z.string().optional(), | ||
}); | ||
|
||
type ReportCreateFormValues = z.infer<typeof ReportCreateFormSchema>; | ||
|
||
export interface ReportCreateFormProps { | ||
sourceId: string; | ||
sourceType: CreateReportRequestDto.sourceType; | ||
onSuccess?: () => void; | ||
} | ||
|
||
const ReportCreateForm = ({ | ||
sourceId, | ||
sourceType, | ||
onSuccess, | ||
}: ReportCreateFormProps) => { | ||
const { register, watch, handleSubmit, setValue } = | ||
useForm<ReportCreateFormValues>({ | ||
mode: "onSubmit", | ||
defaultValues: { | ||
reason: undefined, | ||
category: CreateReportRequestDto.category.SPAM, | ||
}, | ||
}); | ||
|
||
const categorySelectOptions = useMemo<ComboboxItem[]>(() => { | ||
return Object.values(CreateReportRequestDto.category).map((v) => { | ||
return { | ||
label: reportCategoryToString(v), | ||
value: v, | ||
}; | ||
}); | ||
}, []); | ||
|
||
const selectedCategory = watch("category"); | ||
|
||
const selectedCategoryDescription = useMemo(() => { | ||
return reportCategoryToDescription(selectedCategory); | ||
}, [selectedCategory]); | ||
|
||
const reportCreateMutation = useMutation({ | ||
mutationFn: async (data: ReportCreateFormValues) => { | ||
await ReportService.reportControllerCreate({ | ||
sourceId, | ||
sourceType, | ||
category: data.category, | ||
reason: data.reason, | ||
}); | ||
}, | ||
onError: () => { | ||
notifications.show({ | ||
color: "red", | ||
message: | ||
"Error while sending your report. Please try again. If this persists, contact support.", | ||
}); | ||
}, | ||
onSuccess: () => { | ||
notifications.show({ | ||
color: "green", | ||
message: | ||
"Thank you for submitting your report! It will be reviewed by our moderators as soon as possible.", | ||
}); | ||
|
||
if (onSuccess) onSuccess(); | ||
}, | ||
}); | ||
|
||
return ( | ||
<form | ||
className={"w-full h-full"} | ||
onSubmit={handleSubmit((data) => reportCreateMutation.mutate(data))} | ||
> | ||
<Stack className={"w-full h-full"}> | ||
<Text className={"text-sm text-dimmed"}> | ||
For auditing purposes, you need to generate a report before | ||
issuing a possible suspension/ban on a user. This helps us | ||
streamline a possible review process. | ||
</Text> | ||
<Select | ||
withAsterisk | ||
value={selectedCategory} | ||
onChange={(v) => { | ||
if (v) { | ||
setValue( | ||
"category", | ||
v as CreateReportRequestDto.category, | ||
); | ||
} | ||
}} | ||
name={"category"} | ||
allowDeselect={false} | ||
label={"Report category"} | ||
data={categorySelectOptions} | ||
description={selectedCategoryDescription} | ||
/> | ||
<Textarea | ||
{...register("reason")} | ||
label={"Reason"} | ||
description={ | ||
"Optional. A detailed reason may help us decide in a possible review process." | ||
} | ||
/> | ||
<Text className={"text-dimmed text-sm"}> | ||
The generated report may be handled by any GameNode | ||
moderator or admin. | ||
</Text> | ||
<Button className={"mt-2"} type={"submit"}> | ||
Submit report | ||
</Button> | ||
</Stack> | ||
</form> | ||
); | ||
}; | ||
|
||
export default ReportCreateForm; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters