After setting up the user authentication system, it is time for us to add a dashboard to our SaaS app. This dashboard should only be accessible to authenticated users, and it should provide them with options to update user information, such as the name and email address.
Updating the navbar
Let's start with the navbar, our navigation menu needs to be updated to show a Get Started button for unauthenticated users and a Dashboard button for authenticated users.
For unauthenticated users:
For authenticated users:
In order to achieve this, we need to retrieve the user session in our navbar component.
If the session
exists, user is authenticated, and the Dashboard button will be displayed. If the session
does not exist, user is not authenticated, and the Get Started button will be displayed instead.
components/navbar.jsx
1import { auth } from "@/libs/auth";
2
3export default async function Navbar() {
4 const session = await auth();
5
6 return (
7 <nav className="flex items-center justify-between px-6 py-4 bg-white shadow-md">
8 <a href="/" className="text-xl font-bold">
9 MySaaS
10 </a>
11
12 <div className="hidden md:flex gap-6">
13 <a href="/features" className="hover:text-blue-600">
14 Features
15 </a>
16 <a href="/pricing" className="hover:text-blue-600">
17 Pricing
18 </a>
19 <a href="/about" className="hover:text-blue-600">
20 About
21 </a>
22 </div>
23
24 {session ? (
25 <a
26 href="/dashboard"
27 className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
28 Dashboard
29 </a>
30 ) : (
31 <a
32 href="/signin"
33 className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700">
34 Get Started
35 </a>
36 )}
37 </nav>
38 );
39}
Creating the dashboard layout
As for the dashboard itself, it can be designed in many different ways. As an example, we are going to have a side menu on the left side, allowing the user to navigate between different dashboard pages, which will be shown on the right side.
To implement this layout, we'll need to add layout.jsx
and page.jsx
files under app/dashboard/
.
1src/app
2├── api
3├── check-your-email
4├── dashboard
5│ ├── layout.jsx <===== Layout for the dashboard
6│ └── page.jsx <===== Dashboard homepage
7├── favicon.ico
8├── globals.css
9├── layout.jsx
10├── page.jsx
11├── pricing
12└── signin
Create a navigation menu inside the dashboard layout.jsx
. This ensures that the menu will be shared across all future dashboard pages we are going to create.
app/dashboard/layout.jsx
1import Link from "next/link";
2
3export default function DashboardLayout({ children }) {
4 return (
5 <div className="min-h-screen flex">
6 <div className="w-52 bg-gray-900 text-white">
7 <nav className="mt-6">
8 <ul className="space-y-2">
9 <li>
10 <Link
11 href="/dashboard"
12 className="block px-6 py-2 hover:bg-gray-700">
13 Home
14 </Link>
15 </li>
16 <li>
17 <Link
18 href="/dashboard/settings"
19 className="block px-6 py-2 hover:bg-gray-700">
20 Settings
21 </Link>
22 </li>
23 <li>
24 <Link
25 href="/dashboard/analytics"
26 className="block px-6 py-2 hover:bg-gray-700">
27 Analytics
28 </Link>
29 </li>
30 <li>
31 <Link
32 href="/dashboard/reports"
33 className="block px-6 py-2 hover:bg-gray-700">
34 Reports
35 </Link>
36 </li>
37 </ul>
38 </nav>
39 </div>
40
41 <div className="flex-1 bg-gray-100 p-6">{children}</div>
42 </div>
43 );
44}
The page.jsx
corresponds to the dashboard homepage. As our dashboard gets more complex, we'll gradually add more components to it, but for now, we are just going to display a personalized welcome message.
app/dashboard/page.jsx
1import { auth } from "@/libs/auth";
2
3export default async function () {
4 const session = await auth();
5 return <div>Welcome, {session?.user?.name || session?.user?.email}!</div>;
6}
Blocking unauthenticated access
The dashboard should only be accessible to authenticated users. When an unauthenticated user tries to access the dashboard, they should be redirected to the sign in page.
You can add the traffic blocking logic to all dashboard pages like this:
app/dashboard/page.jsx
1import { auth } from "@/libs/auth";
2import { redirect } from "next/navigation";
3
4export default async function () {
5 const session = await auth();
6
7 if (!session) {
8 return redirect("/signin");
9 }
10
11 return <div>Welcome, {session?.user?.name || session?.user?.email}!</div>;
12}
You need to check if the session exists, and if not, redirect the user to a different page.
In the above example, whenever an unauthenticated user tries to visit /dashboard
, they will be redirected to /signin
instead.
Make sure to add this logic to all dashboard pages that needs to be protected.
Creating the user settings page
Lastly, the dashboard should have a settings page, where the user can update their information.
1src/app
2├── api
3├── check-your-email
4├── dashboard
5│ ├── layout.jsx
6│ ├── page.jsx
7│ └── settings
8│ └── page.jsx <===== The settings page
9├── favicon.ico
10├── globals.css
11├── layout.jsx
12├── page.jsx
13├── pricing
14└── signin
As an example, we are creating a form that allows the user to update their name:
app/dashboard/settings/page.jsx
1import { auth } from "@/libs/auth";
2import prisma from "@/libs/db";
3import { redirect } from "next/navigation";
4
5export default async function SettingsPage() {
6 const session = await auth();
7 return (
8 <div className="flex min-h-[80vh] items-center justify-center bg-gray-100">
9 <div className="w-full max-w-md bg-white p-8 rounded-lg shadow-md">
10 <h1 className="text-2xl font-bold text-center mb-6">User Settings</h1>
11 <form
12 action={async (formData) => {
13 "use server";
14 await prisma.user.update({
15 where: {
16 email: session?.user?.email,
17 },
18 data: {
19 name: formData.get("username"),
20 },
21 });
22
23 redirect("/dashboard/settings");
24 }}
25 className="space-y-6">
26 <div>
27 <label
28 htmlFor="name"
29 className="block text-sm font-medium text-gray-700">
30 Name
31 </label>
32 <input
33 type="text"
34 id="name"
35 name="username"
36 defaultValue={session?.user?.name || ""}
37 className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
38 required
39 />
40 </div>
41 . . .
42 <div>
43 <button
44 type="submit"
45 className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
46 Update Profile
47 </button>
48 </div>
49 </form>
50 </div>
51 </div>
52 );
53}
There are a few things to pay attention to here:
First, the <input>
field should have a defaultValue
, and it should be either the user's name, if it exists, or an empty string.
1<input
2 type="text"
3 id="name"
4 name="username"
5 defaultValue={session?.user?.name || ""}
6 className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
7 required
8/>
Second, make sure the <input>
field has a name
attribute. When the form is submitted, the input value will be transferred as formData
, and can only be retrieved via the value of name
.
1<input
2 type="text"
3 id="name"
4 name="username"
5 defaultValue={session?.user?.name || ""}
6 className="mt-1 block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm focus:outline-none focus:ring-2 focus:ring-blue-500"
7 required
8/>
Third, there must be a button with type="submit"
.
1<button
2 type="submit"
3 className="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500">
4 Update Profile
5</button>
And finally add a form action, which will be executed when the form is submitted.
1<form
2 action={async (formData) => {
3 "use server";
4 await prisma.user.update({
5 where: {
6 email: session?.user?.email,
7 },
8 data: {
9 name: formData.get("username"),
10 },
11 });
12
13 redirect("/dashboard/settings");
14 }}
15 className="space-y-6">
16 . . .
17</form>
Notice that formData
will be passed to this function, and you must use formData.get("username")
to retrieve the input value for <input name="username" />
.