Skip to content

Commit e175f87

Browse files
committed
feat(refactor): add user roles & admin panel with major code changes
1 parent 15c719c commit e175f87

37 files changed

+2090
-836
lines changed

actions/generate-user-stripe.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ export async function generateUserStripe(priceId: string): Promise<responseActio
1919

2020
try {
2121
const session = await auth()
22+
const user = session?.user;
2223

23-
if (!session?.user || !session?.user.email) {
24+
if (!user || !user.email || !user.id) {
2425
throw new Error("Unauthorized");
2526
}
2627

27-
const subscriptionPlan = await getUserSubscriptionPlan(session.user.id)
28+
const subscriptionPlan = await getUserSubscriptionPlan(user.id)
2829

2930
if (subscriptionPlan.isPaid && subscriptionPlan.stripeCustomerId) {
3031
// User on Paid Plan - Create a portal session to manage subscription.
@@ -42,15 +43,15 @@ export async function generateUserStripe(priceId: string): Promise<responseActio
4243
payment_method_types: ["card"],
4344
mode: "subscription",
4445
billing_address_collection: "auto",
45-
customer_email: session.user.email,
46+
customer_email: user.email,
4647
line_items: [
4748
{
4849
price: priceId,
4950
quantity: 1,
5051
},
5152
],
5253
metadata: {
53-
userId: session.user.id,
54+
userId: user.id,
5455
},
5556
})
5657

actions/update-user-role.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
"use server";
2+
3+
import { revalidatePath } from "next/cache";
4+
import { auth } from "@/auth";
5+
import { UserRole } from "@prisma/client";
6+
7+
import { prisma } from "@/lib/db";
8+
import { userRoleSchema } from "@/lib/validations/user";
9+
10+
export type FormData = {
11+
role: UserRole;
12+
};
13+
14+
export async function updateUserRole(userId: string, data: FormData) {
15+
try {
16+
const session = await auth();
17+
18+
if (!session?.user || session?.user.id !== userId) {
19+
throw new Error("Unauthorized");
20+
}
21+
22+
const { role } = userRoleSchema.parse(data);
23+
24+
// Update the user role.
25+
await prisma.user.update({
26+
where: {
27+
id: userId,
28+
},
29+
data: {
30+
role: role,
31+
},
32+
});
33+
34+
revalidatePath("/dashboard/settings");
35+
return { status: "success" };
36+
} catch (error) {
37+
// console.log(error)
38+
return { status: "error" };
39+
}
40+
}

app/(auth)/layout.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,18 @@
1+
import { redirect } from "next/navigation";
2+
3+
import { getCurrentUser } from "@/lib/session";
4+
15
interface AuthLayoutProps {
2-
children: React.ReactNode
6+
children: React.ReactNode;
37
}
48

5-
export default function AuthLayout({ children }: AuthLayoutProps) {
6-
return <div className="min-h-screen">{children}</div>
9+
export default async function AuthLayout({ children }: AuthLayoutProps) {
10+
const user = await getCurrentUser();
11+
12+
if (user) {
13+
if (user.role === "ADMIN") redirect("/admin");
14+
redirect("/dashboard");
15+
}
16+
17+
return <div className="min-h-screen">{children}</div>;
718
}

app/(auth)/login/page.tsx

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import { Metadata } from "next"
2-
import Link from "next/link"
1+
import { Suspense } from "react";
2+
import { Metadata } from "next";
3+
import Link from "next/link";
34

4-
import { cn } from "@/lib/utils"
5-
import { buttonVariants } from "@/components/ui/button"
6-
import { Icons } from "@/components/shared/icons"
7-
import { UserAuthForm } from "@/components/forms/user-auth-form"
8-
import { Suspense } from "react"
5+
import { cn } from "@/lib/utils";
6+
import { buttonVariants } from "@/components/ui/button";
7+
import { UserAuthForm } from "@/components/forms/user-auth-form";
8+
import { Icons } from "@/components/shared/icons";
99

1010
export const metadata: Metadata = {
1111
title: "Login",
1212
description: "Login to your account",
13-
}
13+
};
1414

1515
export default function LoginPage() {
1616
return (
@@ -19,7 +19,7 @@ export default function LoginPage() {
1919
href="/"
2020
className={cn(
2121
buttonVariants({ variant: "outline", size: "sm" }),
22-
"absolute left-4 top-4 md:left-8 md:top-8"
22+
"absolute left-4 top-4 md:left-8 md:top-8",
2323
)}
2424
>
2525
<>
@@ -50,5 +50,5 @@ export default function LoginPage() {
5050
</p>
5151
</div>
5252
</div>
53-
)
53+
);
5454
}

app/(marketing)/pricing/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export default async function PricingPage() {
1414
const user = await getCurrentUser();
1515
let subscriptionPlan;
1616

17-
if (user) {
17+
if (user && user.id) {
1818
subscriptionPlan = await getUserSubscriptionPlan(user.id);
1919
}
2020

app/(protected)/admin/page.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { redirect } from "next/navigation";
2+
3+
import { getCurrentUser } from "@/lib/session";
4+
import { constructMetadata } from "@/lib/utils";
5+
import { DashboardHeader } from "@/components/dashboard/header";
6+
import InfoCard from "@/components/dashboard/info-card";
7+
import { DashboardShell } from "@/components/dashboard/shell";
8+
import TransactionsList from "@/components/dashboard/transactions-list";
9+
10+
export const metadata = constructMetadata({
11+
title: "Admin – SaaS Starter",
12+
description: "Admin page for only admin management.",
13+
});
14+
15+
export default async function AdminPage() {
16+
const user = await getCurrentUser();
17+
if (!user || user.role !== "ADMIN") redirect("/login");
18+
19+
return (
20+
<DashboardShell>
21+
<DashboardHeader heading="Admin" text="Access only for admin." />
22+
<div className="flex flex-col gap-5">
23+
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
24+
<InfoCard />
25+
<InfoCard />
26+
<InfoCard />
27+
</div>
28+
<TransactionsList />
29+
</div>
30+
</DashboardShell>
31+
);
32+
}

app/(dashboard)/dashboard/billing/page.tsx renamed to app/(protected)/dashboard/billing/page.tsx

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { redirect } from "next/navigation";
22

3-
import { BillingInfo } from "@/components/pricing/billing-info";
4-
import { DashboardHeader } from "@/components/dashboard/header";
5-
import { DashboardShell } from "@/components/dashboard/shell";
6-
import { Icons } from "@/components/shared/icons";
7-
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
83
import { getCurrentUser } from "@/lib/session";
94
import { getUserSubscriptionPlan } from "@/lib/subscription";
105
import { constructMetadata } from "@/lib/utils";
6+
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
7+
import { DashboardHeader } from "@/components/dashboard/header";
8+
import { DashboardShell } from "@/components/dashboard/shell";
9+
import { BillingInfo } from "@/components/pricing/billing-info";
10+
import { Icons } from "@/components/shared/icons";
1111

1212
export const metadata = constructMetadata({
1313
title: "Billing – SaaS Starter",
@@ -17,12 +17,13 @@ export const metadata = constructMetadata({
1717
export default async function BillingPage() {
1818
const user = await getCurrentUser();
1919

20-
if (!user) {
20+
let userSubscriptionPlan;
21+
if (user && user.id && user.role === "USER") {
22+
userSubscriptionPlan = await getUserSubscriptionPlan(user.id);
23+
} else {
2124
redirect("/login");
2225
}
2326

24-
const userSubscriptionPlan = await getUserSubscriptionPlan(user.id);
25-
2627
return (
2728
<DashboardShell>
2829
<DashboardHeader
@@ -33,7 +34,7 @@ export default async function BillingPage() {
3334
<Alert className="!pl-14">
3435
<Icons.warning />
3536
<AlertTitle>This is a demo app.</AlertTitle>
36-
<AlertDescription>
37+
<AlertDescription className="text-balance">
3738
SaaS Starter app is a demo app using a Stripe test environment. You
3839
can find a list of test card numbers on the{" "}
3940
<a

app/(dashboard)/dashboard/loading.tsx renamed to app/(protected)/dashboard/loading.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ import { DashboardShell } from "@/components/dashboard/shell";
55
export default function DashboardLoading() {
66
return (
77
<DashboardShell>
8-
<DashboardHeader heading="Posts" text="Create and manage posts." />
8+
<DashboardHeader
9+
heading="Panel"
10+
text="Current Role :"
11+
/>
912
<div className="divide-border-200 divide-y rounded-md border">
1013
<Skeleton className="h-[400px] w-full rounded-lg" />
1114
</div>

app/(dashboard)/dashboard/page.tsx renamed to app/(protected)/dashboard/page.tsx

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,24 @@
1-
import { redirect } from "next/navigation";
2-
1+
import { getCurrentUser } from "@/lib/session";
2+
import { constructMetadata } from "@/lib/utils";
3+
import { Button } from "@/components/ui/button";
34
import { DashboardHeader } from "@/components/dashboard/header";
45
import { DashboardShell } from "@/components/dashboard/shell";
56
import { EmptyPlaceholder } from "@/components/shared/empty-placeholder";
6-
import { Button } from "@/components/ui/button";
7-
import { getCurrentUser } from "@/lib/session";
8-
import { constructMetadata } from "@/lib/utils";
97

108
export const metadata = constructMetadata({
11-
title: "Settings – SaaS Starter",
12-
description: "Overview of your account and activities.",
9+
title: "Panel – SaaS Starter",
10+
description: "Create and manage content.",
1311
});
1412

1513
export default async function DashboardPage() {
1614
const user = await getCurrentUser();
1715

18-
if (!user) {
19-
redirect("/login");
20-
}
21-
2216
return (
2317
<DashboardShell>
24-
<DashboardHeader heading="Panel" text="Create and manage content." />
18+
<DashboardHeader
19+
heading="Panel"
20+
text={`Current Role : ${user?.role} — Change your role in settings.`}
21+
/>
2522
<div>
2623
<EmptyPlaceholder>
2724
<EmptyPlaceholder.Icon name="post" />

0 commit comments

Comments
 (0)