Skip to content

Commit

Permalink
[admin] add reports section to admin panel
Browse files Browse the repository at this point in the history
  • Loading branch information
PhantomKnight287 committed Feb 3, 2024
1 parent b5914b2 commit 69ae291
Show file tree
Hide file tree
Showing 18 changed files with 392 additions and 12 deletions.
4 changes: 4 additions & 0 deletions apps/admin/app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ const URLS = [
label: "Tracks",
href: "tracks",
},
{
label: "Reports",
href: "reports",
},
];

function DashboardLayout({ children }: { children: ReactNode }) {
Expand Down
47 changes: 47 additions & 0 deletions apps/admin/app/dashboard/reports/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"use server";

import { auth } from "@/auth";
import { assertAdmin } from "@/utils/auth";
import { prisma } from "@repo/db";
import { revalidatePath } from "next/cache";

export async function DeleteReportedComment(reportId: string) {
const session = await auth();
if (!assertAdmin(session)) return { error: "Unauthorized" };
const report = await prisma.report.findUnique({
where: { id: reportId },
});
if (!report) return { error: "Report not found" };
const tx = await prisma.$transaction([
prisma.comment.delete({
where: {
id: report.commentId!,
},
}),
prisma.report.update({
where: { id: reportId },
data: {
status: "accepted",
},
}),
]);

revalidatePath("/dashboard/reports");
}

export async function RejectReportedComment(reportId: string) {
const session = await auth();
if (!assertAdmin(session)) return { error: "Unauthorized" };
const report = await prisma.report.findUnique({
where: { id: reportId },
});
if (!report) return { error: "Report not found" };
const tx = await prisma.report.update({
where: { id: reportId },
data: {
status: "rejected",
},
});

revalidatePath("/dashboard/reports");
}
1 change: 1 addition & 0 deletions apps/admin/app/dashboard/reports/loading.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from "@/components/shared/loading";
117 changes: 117 additions & 0 deletions apps/admin/app/dashboard/reports/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import { auth } from "@/auth";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { assertAdmin } from "@/utils/auth";
import { fromNow } from "@/utils/time";
import { prisma } from "@repo/db";
import { cn, parseToNumber, upperFirst } from "@repo/utils";
import { redirect } from "next/navigation";
import ViewReportContent from "./view.client";
async function Reports({
searchParams,
}: {
searchParams: Record<string, string>;
}) {
const session = await auth();
if (assertAdmin(session) === false) redirect(`/no-access`);
const reports = await prisma.report.paginate({
limit: parseToNumber(searchParams.limit as string, 100),
page: parseToNumber(searchParams.page as string, 1),
where: {
commentId: {
not: null,
},
},
orderBy: [
{
status: "desc",
},
{
createdAt: "asc",
},
],
include: {
author: {
select: {
id: true,
username: true,
name: true,
},
},
comment: {
select: {
id: true,
content: true,
createdAt: true,
author: {
select: {
username: true,
name: true,
id: true,
},
},
},
},
},
});
return (
<div className="container">
<div className="flex flex-col items-center justify-center">
<div className="flex w-full">
<div className="ml-auto mb-2">{/* <CreateNewRecord /> */}</div>
</div>
<Table>
<TableHeader>
<TableRow>
<TableHead>Reporter</TableHead>
<TableHead>Report</TableHead>
<TableHead>Status</TableHead>
<TableHead>Reported At</TableHead>
<TableHead>Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{reports.result.map((report) => (
<TableRow key={report.id}>
<TableCell>{report.author.name}</TableCell>
<TableCell>{report.content?.substring(0, 30)}...</TableCell>
<TableCell>
<span
className={cn(
"px-2 py-1 rounded-full text-xs font-medium",
{
"bg-red-500 text-white": report.status === "pending",
"bg-green-500 text-white": report.status === "accepted",
"bg-gray-500 text-white": report.status === "rejected",
}
)}
>
{upperFirst(report.status)}
</span>
</TableCell>
<TableCell>{fromNow(report.createdAt)}</TableCell>
<TableCell className="flex items-center justify-center flex-row max-w-[100px]">
<ViewReportContent
//@ts-expect-error
comment={report.comment!}
reportContent={report.content}
reportId={report.id}
reporter={report.author as any}
/>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</div>
</div>
);
}

export default Reports;
130 changes: 130 additions & 0 deletions apps/admin/app/dashboard/reports/view.client.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"use client";
import { AvatarFallback, AvatarImage, Avatar } from "@/components/ui/avatar";
import { Button } from "@/components/ui/button";
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import { fromNow } from "@/utils/time";
import { useState } from "react";
import { toast } from "sonner";
import { DeleteReportedComment, RejectReportedComment } from "./actions";

function ViewReportContent({
comment,
reportContent,
reportId,
reporter,
}: {
reportId: string;
reportContent: string;
reporter: { id: string; username: string; name: string };
comment: {
id: string;
content: string;
createdAt: Date;
author: { id: string; username: string; name: string };
};
}) {
const [loading, setLoading] = useState(false);
return (
<Dialog>
<DialogTrigger asChild>
<Button>View</Button>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Report</DialogTitle>
</DialogHeader>
<div className="flex flex-row items-center justify-start gap-1">
<a
href={`${
process.env.NODE_ENV === "development"
? "http://localhost:3000/@"
: "https://frameground.tech/@"
}${reporter.username}`}
target="_blank"
rel="noreferrer"
className="text-blue-500 text-sm"
>
{reporter.name} (@{reporter.username})
</a>
reported {fromNow(comment.createdAt)}
</div>
<div>Reason: {reportContent}</div>
<div className="flex flex-col gap-1">
<div className="flex flex-row items-center justify-between">
<div className="flex flex-row items-start justify-start gap-2">
<Avatar>
<AvatarImage
src={`/github-avatar/${comment.author.username}`}
/>
<AvatarFallback>
{comment.author.username!.toUpperCase()}
</AvatarFallback>
</Avatar>
<div className="flex flex-col items-start justify-start">
<div className="flex flex-row gap-1 items-center">
<a
href={`${
process.env.NODE_ENV === "development"
? "http://localhost:3000/@"
: "https://frameground.tech/@"
}${comment.author.username}`}
target="_blank"
rel="noopener noreferrer"
className="hover:underline font-medium text-sm"
>
{comment.author.username}
</a>
<span className="text-sm text-muted-foreground"></span>
<span className="text-sm text-muted-foreground">
{fromNow(comment.createdAt)}
</span>
</div>
<div className="text-sm text-white">{comment.content}</div>
</div>
</div>
</div>
</div>
<DialogFooter className="flex flex-row w-full">
<Button
variant={"destructive"}
className="flex-grow"
onClick={async () => {
setLoading(true);
const d = await DeleteReportedComment(reportId);
if (d?.error) {
toast.error(d.error);
}
setLoading(false);
}}
disabled={loading}
>
Delete Comment
</Button>
<Button
className="flex-grow"
disabled={loading}
onClick={async () => {
setLoading(true);
const d = await RejectReportedComment(reportId);
if (d?.error) {
toast.error(d.error);
}
setLoading(false);
}}
>
Dismiss Report
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

export default ViewReportContent;
2 changes: 2 additions & 0 deletions apps/admin/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { siteConfig } from "@repo/config";
import { ThemeProvider } from "@/components/theme-provider";
import Header from "@/components/header";
import VerifyUser from "@/components/auth";
import { Toaster } from "sonner";

export const fontSans = FontSans({
subsets: ["latin"],
Expand Down Expand Up @@ -36,6 +37,7 @@ export default function RootLayout({ children }: { children: ReactNode }) {
enableSystem
disableTransitionOnChange
>
<Toaster />
<NextAuthProvider>
<VerifyUser />
<Header />
Expand Down
2 changes: 1 addition & 1 deletion apps/admin/components/auth/index.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { assertAdmin } from "@/utils/auth";
import { useSession } from "next-auth/react";
import { redirect, useRouter } from "next/navigation";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

function VerifyUser() {
Expand Down
8 changes: 8 additions & 0 deletions apps/admin/components/header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ function Header() {
</div>
</div>
</Link>
<nav className="ml-6 hidden md:flex items-center justify-center flex-row">
<Link
href="/dashboard"
className="font-semibold hover:text-gray-400 transition-all duration-100"
>
Dashboard
</Link>
</nav>
<div className="flex flex-row ml-auto">
<InteractiveHeaderComponents />
</div>
Expand Down
31 changes: 31 additions & 0 deletions apps/admin/components/ui/sonner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client"

import { useTheme } from "next-themes"
import { Toaster as Sonner } from "sonner"

type ToasterProps = React.ComponentProps<typeof Sonner>

const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme()

return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
toastOptions={{
classNames: {
toast:
"group toast group-[.toaster]:bg-background group-[.toaster]:text-foreground group-[.toaster]:border-border group-[.toaster]:shadow-lg",
description: "group-[.toast]:text-muted-foreground",
actionButton:
"group-[.toast]:bg-primary group-[.toast]:text-primary-foreground",
cancelButton:
"group-[.toast]:bg-muted group-[.toast]:text-muted-foreground",
},
}}
{...props}
/>
)
}

export { Toaster }
8 changes: 8 additions & 0 deletions apps/admin/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,14 @@ const nextConfig = {
}
return config;
},
async rewrites() {
return [
{
source: "/github-avatar/:username",
destination: "https://github.com/:username.png",
},
];
},
};

module.exports = nextConfig;
Loading

0 comments on commit 69ae291

Please sign in to comment.