Skip to content

Commit 0893589

Browse files
authored
Merge pull request #17 from hack4impact-calpoly/K1-30-Set-up-routing-for-Clerk-Admin/Player-Dashboard
K1 30 Set up routing for Clerk Admin/Player Dashboard
2 parents aeb7cf2 + a271b73 commit 0893589

File tree

7 files changed

+102
-6
lines changed

7 files changed

+102
-6
lines changed

src/app/adminDashboard/layout.tsx

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { auth } from "@clerk/nextjs/server";
2+
import { redirect } from "next/navigation";
3+
import connectDB from "@/database/db";
4+
import User from "@/database/userSchema";
5+
6+
export default async function AdminLayout({ children }: { children: React.ReactNode }) {
7+
const { userId } = await auth();
8+
if (!userId) {
9+
return redirect("/login");
10+
}
11+
12+
await connectDB();
13+
// our userSchema is loosely typed, so we need to clarify what the role is when using lean()
14+
const user = await User.findOne({ clerkId: userId }).lean<{ role: string } | null>();
15+
16+
if (!user || user.role !== "admin") {
17+
return redirect("/playerDashboard");
18+
}
19+
20+
return <>{children}</>;
21+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { NextResponse } from "next/server";
2+
import { auth, clerkClient } from "@clerk/nextjs/server";
3+
import connectDB from "@/database/db";
4+
import User from "@/database/userSchema";
5+
6+
export async function PATCH(req: Request, { params }: { params: { id: string } }) {
7+
const { userId, sessionClaims } = await auth();
8+
if (!userId) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
9+
10+
// IMPORTANT perhaps uncomment this in production
11+
// if (sessionClaims?.role !== "admin") {
12+
// return NextResponse.json({ error: "Forbidden" }, { status: 403 });
13+
// }
14+
15+
const { role } = await req.json();
16+
17+
await connectDB();
18+
19+
// Update Mongo ( new: true will pass the updated document to mongo)
20+
const updated = await User.findByIdAndUpdate(params.id, { role }, { new: true }).lean<{ clerkId: string } | null>();
21+
if (!updated) {
22+
return NextResponse.json({ error: "User not found" }, { status: 404 });
23+
}
24+
25+
// Sync Clerk metadata
26+
const client = await clerkClient();
27+
28+
await client.users.updateUserMetadata(updated.clerkId, {
29+
publicMetadata: { role },
30+
});
31+
32+
return NextResponse.json({ ok: true, user: updated }, { status: 200 });
33+
}

src/app/dashboard/page.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,20 @@
1-
export default function Dashboard() {}
1+
import { auth } from "@clerk/nextjs/server";
2+
import { redirect } from "next/navigation";
3+
4+
//Redirect the user to the proper dashboard
5+
6+
//ADD THESE TO ENV
7+
//NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
8+
//NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard
9+
10+
export default async function Dashboard() {
11+
const { sessionClaims } = await auth();
12+
13+
const role = sessionClaims?.role;
14+
15+
if (role === "admin") {
16+
redirect("/adminDashboard");
17+
}
18+
19+
redirect("/playerDashboard");
20+
}

src/components/ChakraButton.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export default function ChakraButton({ href, color, label, width, minW, iconSrc
2222
minW={minW}
2323
py={8}
2424
position="relative"
25-
overflow="hidden" // keeps the blue bottom highlight within the button
25+
overflow="hidden" // keeps the bottom highlight stroke within the button
2626
borderRadius="18px"
2727
bg={`${color}.500`}
2828
color="white"

src/components/PlayerNavbar.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function PlayerNavbar({ role, name, coins = "0", avatarSrc }: NavbarProps
5252

5353
{/* Right side actions */}
5454
<HStack>
55-
<ChakraButton href="/" color="red" label="SHOP" width="140px" iconSrc="/Icons/FaShoppingBag.png" />
55+
<ChakraButton href="/shop" color="red" label="SHOP" width="140px" iconSrc="/Icons/FaShoppingBag.png" />
5656
<ChakraButton href="/" color="yellow" label={coins} width="140px" iconSrc="/Icons/FaStar.png" />
5757
</HStack>
5858
</HStack>

src/database/userSchema.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@ import mongoose, { Schema } from "mongoose";
22

33
// Updated UserSchema to match specifications
44
const UserSchema = new Schema({
5+
clerkId: { type: String, required: true, unique: true },
56
name: { type: String, required: true, trim: true },
67
username: { type: String, required: true, trim: true },
7-
role: { type: String, required: true, trim: true },
8+
role: { type: String, required: true, trim: true, default: "player" },
89
email: { type: String, required: true, trim: true },
910
});
1011

src/middleware.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,28 @@
1-
import { clerkMiddleware } from "@clerk/nextjs/server";
1+
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";
2+
import { NextResponse } from "next/server";
23

3-
export default clerkMiddleware();
4+
const isPublicRoute = createRouteMatcher(["/login(.*)"]);
5+
const isAdminRoute = createRouteMatcher(["/adminDashboard(.*)"]);
6+
7+
// !IMPORTANT, add this to your env:
8+
// NEXT_PUBLIC_CLERK_SIGN_IN_URL=/login
9+
//otherwise auth.protect() will default to clerks hosted login route.
10+
11+
//Keep in mind when you change roles, it wont appear until clerks session token refreshes.
12+
//https://clerk.com/docs/guides/sessions/customize-session-tokens
13+
14+
export default clerkMiddleware(async (auth, req) => {
15+
if (isPublicRoute(req)) return NextResponse.next();
16+
const { sessionClaims } = await auth.protect();
17+
const role = sessionClaims?.role;
18+
19+
// Protect admin routes (can pass a error instead)
20+
if (isAdminRoute(req) && role !== "admin") {
21+
return NextResponse.redirect(new URL("/playerDashboard", req.url));
22+
}
23+
24+
return NextResponse.next();
25+
});
426

527
export const config = {
628
matcher: [

0 commit comments

Comments
 (0)