Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 23 additions & 23 deletions app/(protected)/admin/page.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
import getCurrentUserRole from "@/lib/data/current-user-role";
import { redirect } from "next/navigation";
import React from "react";
import { DataTable } from "./data-table";
import { columns } from "./columns";
// app/(protected)/admin/page.tsx
import { Metadata } from "next";
import { auth } from "@/auth";
import prismaDB from "@/lib/prisma";
import UserTable from "@/components/UserTable";

const AdminPage = async () => {
const currentUserRole = await getCurrentUserRole();
export const metadata: Metadata = {
title: "Admin • Dashboard",
description: "Promote users to admins or block/unblock them",
};

if (currentUserRole === "USER") {
redirect("/");
export default async function AdminPage() {
const session = await auth();
if (session?.user.role !== "ADMIN") {
return <p className="p-8 text-red-600">Not authorized.</p>;
}

const data = await prismaDB.user.findMany();
// fetch all users
const users = await prismaDB.user.findMany({
select: { id: true, name: true, email: true, role: true, isBlocked: true },
orderBy: { email: "asc" },
});

return (
<>
<section className="block-space big-container">
<div>
<h2>Admin Dashboard</h2>
</div>
<div className="container mx-auto py-10">
<DataTable columns={columns} data={data} />
</div>
</section>
</>
<main className="container py-8">
<h1 className="mb-6 text-2xl font-bold">Admin Dashboard</h1>
{/* Render the client-side table */}
<UserTable initialUsers={users} />
</main>
);
};

export default AdminPage;
}
29 changes: 14 additions & 15 deletions app/(protected)/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// app/layout.tsx
import type { Metadata } from "next";
import localFont from "next/font/local";
import "../globals.css";
Expand All @@ -18,31 +19,29 @@ export const metadata: Metadata = {

export default async function RootLayout({
children,
}: Readonly<{
}: {
children: React.ReactNode;
}>) {
const userSession = await auth();
}) {
// 1️⃣ Fetch the session on the server
const session = await auth();

return (
<html lang="en" className={cn(GeistSans.variable)} suppressHydrationWarning>
<body className={`antialiased`}>
<ThemeProvider
attribute="class"
defaultTheme="light"
enableSystem
disableTransitionOnChange
>
<SessionProvider>
<body className="antialiased">
{/* 2️⃣ Wrap in SessionProvider and pass the session */}
<SessionProvider session={session}>
{/* 3️⃣ ThemeProvider is now a client component that handles its own props */}
<ThemeProvider>
<main>
<MenuDialog />
<Header session={userSession} />

{/* 4️⃣ Header can also consume useSession() client-side */}
<Header session={session} />
{children}
<Footer />
</main>
</SessionProvider>
</ThemeProvider>
<Toaster />
</ThemeProvider>
</SessionProvider>
</body>
</html>
);
Expand Down
46 changes: 25 additions & 21 deletions app/(protected)/raw-deals/page.tsx
Original file line number Diff line number Diff line change
@@ -1,44 +1,43 @@
// app/(protected)/raw-deals/page.tsx
import React, { Suspense } from "react";
import DealCardSkeleton from "@/components/skeletons/DealCardSkeleton";
import { Metadata } from "next";
import prismaDB from "@/lib/prisma";
import DealCard from "@/components/DealCard";
import getCurrentUserRole from "@/lib/data/current-user-role";
import { DealType } from "@prisma/client";

import GetDeals, { GetAllDeals } from "@/app/actions/get-deal";
import getCurrentUserRole from "@/lib/data/current-user-role";
import SearchDeals from "@/components/SearchDeal";
import SearchEbitdaDeals from "@/components/SearchEbitdaDeals";
import Pagination from "@/components/pagination";
import { setTimeout } from "timers/promises";
import DealTypeFilter from "@/components/DealTypeFilter";
import { DealType } from "@prisma/client";
import SearchDealsSkeleton from "@/components/skeletons/SearchDealsSkeleton";
import SearchEbitdaDeals from "@/components/SearchEbitdaDeals";
import DealTypeFilterSkeleton from "@/components/skeletons/DealTypeFilterSkeleton";
import UserDealFilter from "@/components/UserDealFilter";
import DealContainer from "@/components/DealContainer";

import DealCardSkeleton from "@/components/skeletons/DealCardSkeleton";
import SearchDealsSkeleton from "@/components/skeletons/SearchDealsSkeleton";
import DealTypeFilterSkeleton from "@/components/skeletons/DealTypeFilterSkeleton";

export const metadata: Metadata = {
title: "Raw Deals",
description: "View the raw deals",
};

// After
type SearchParams = Promise<{ [key: string]: string | undefined }>;

const RawDealsPage = async (props: { searchParams: SearchParams }) => {
export default async function RawDealsPage(props: { searchParams: SearchParams }) {
// parse filters & pagination from URL
const searchParams = await props.searchParams;
const search = searchParams?.query || "";
const currentPage = Number(searchParams?.page) || 1;
const limit = Number(searchParams?.limit) || 20;
const offset = (currentPage - 1) * limit;

const ebitda = searchParams?.ebitda || "";
const userId = searchParams?.userId || "";
// Ensure dealTypes is always an array
const dealTypes =
typeof searchParams?.dealType === "string"
? [searchParams.dealType]
: searchParams?.dealType || [];

// fetch deals + count
const { data, totalPages, totalCount } = await GetAllDeals({
search,
offset,
Expand All @@ -48,20 +47,22 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => {
userId,
});

// server‐side role check
const currentUserRole = await getCurrentUserRole();
const isAdmin = currentUserRole === "ADMIN";

return (
<section className="block-space group container">
{/* — Header & filters */}
<div className="mb-8 text-center">
<h1 className="mb-4 text-4xl font-bold md:mb-6 lg:mb-8">Raw Deals</h1>
<h1 className="mb-4 text-4xl font-bold">Raw Deals</h1>
<p className="mx-auto max-w-2xl text-lg text-muted-foreground">
Browse through our collection of unprocessed deals gathered from
various sources including manual entries, bulk uploads, external
website scraping, and AI-inferred opportunities.
Browse our unprocessed deals from manual entries, bulk uploads,
website scraping, and AI inference.
</p>
</div>

<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div className="mb-6 flex flex-wrap items-center gap-4 md:justify-between">
<div className="flex items-center gap-2">
<h4 className="text-lg font-medium">
Total Deals: <span className="font-bold">{totalCount}</span>
Expand All @@ -79,6 +80,7 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => {
<SearchEbitdaDeals />
</Suspense>
</div>

<Suspense fallback={<DealTypeFilterSkeleton />}>
<DealTypeFilter />
</Suspense>
Expand All @@ -87,6 +89,7 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => {
</Suspense>
</div>

{/* — Deal list (grid or list) */}
<div className="group-has-[[data-pending]]:animate-pulse">
{data.length === 0 ? (
<div className="mt-12 text-center">
Expand All @@ -98,15 +101,16 @@ const RawDealsPage = async (props: { searchParams: SearchParams }) => {
<DealContainer
data={data}
userRole={currentUserRole!}
isAdmin={isAdmin}
currentPage={currentPage}
totalPages={totalPages}
totalCount={totalCount}
/>
)}
</div>

{/* — Pagination */}
<Pagination totalPages={totalPages} />
</section>
);
};

export default RawDealsPage;
}
29 changes: 29 additions & 0 deletions app/api/admin/users/[id]/promote/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// app/api/admin/users/[id]/promote/route.ts
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import prismaDB from "@/lib/prisma";

export async function POST(
request: Request,
context: {
// `params` is now a Promise that you must await
params: Promise<{ id: string }>;
}
) {
// 1️⃣ Auth check
const session = await auth();
if (session?.user.role !== "ADMIN") {
return new NextResponse("Forbidden", { status: 403 });
}

// 2️⃣ Await the params before grabbing `id`
const { id } = await context.params;

// 3️⃣ Promote in the database
await prismaDB.user.update({
where: { id },
data: { role: "ADMIN" },
});

return NextResponse.json({ message: "User promoted" });
}
24 changes: 24 additions & 0 deletions app/api/deals/[id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// app/api/deals/[id]/route.ts
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import prismaDB from "@/lib/prisma";

export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
// 1️⃣ get the current user session (server-side)
const session = await auth();

// 2️⃣ block non-admins
if (session?.user.role !== "ADMIN") {
return new NextResponse("Unauthorized", { status: 403 });
}

// 3️⃣ proceed with delete
await prismaDB.deal.delete({
where: { id: params.id },
});

return NextResponse.json({ success: true });
}
Loading