Saas Dashboard

After setting up the user authentication system, it is time for us to add a dashboard to out 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:

Navbar for unauthenticated users

For authenticated users:

Navbar 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

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.

Dashboard layout

To implement this layout, we'll need to add layout.jsx and page.jsx files under app/dashboard/.

text
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

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}

app/dashboard/page.jsx

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

Creating the user settings page

app/dashboard/settings/page.jsx

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("name"),
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="name"
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            <label
44              htmlFor="email"
45              className="block text-sm font-medium text-gray-700">
46              Email
47            </label>
48            <input
49              type="email"
50              id="email"
51              name="email"
52              defaultValue={session?.user?.email || ""}
53              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"
54              required
55            />
56          </div>
57
58          <div>
59            <button
60              type="submit"
61              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">
62              Update Profile
63            </button>
64          </div>
65        </form>
66      </div>
67    </div>
68  );
69}