If you need to authenticate your users, you probably need to give them access to a private section of the website.
Shipped provides patterns to handle private pages.
Authenticated pages
These are pages accessible only if a user is logged in.
Use the NextAuth hook useSession
you retrieve the current user session, and if they are logged in or not.
src/app/dashboard/page.tsx
"use client";
import { Button, Center, Spinner, Stack, Text } from "@chakra-ui/react";
import { useSession } from "next-auth/react";
const Dashboard = () => {
const { data: session, status } = useSession();
return (
<Center minH="100vh">
{status === "loading" && <Spinner color="brand.500" />}
{status === "authenticated" && (
<p>You are logged in as {session?.user?.email}</p>
)}
{status === "unauthenticated" && (
<Stack>
<Text>Sign in to access</Text>
<Button as="a" href="/login" colorScheme="brand">
Sign in
</Button>
</Stack>
)}
</Center>
);
};
export default Dashboard;
Authenticated API endpoints
These are API endpoints that are protected and only logged in users can call them and get a correct response.
src/app/api/user/route.ts
import { NextRequest, NextResponse } from "next/server";
import { getServerSession } from "next-auth/next";
import { authOptions } from "../auth/[...nextauth]/route";
import { HttpStatusCode } from "axios";
import { prismaClient } from "@/prisma/db";
export async function POST(req: NextRequest) {
const session = await getServerSession(authOptions);
if (!session || !session?.user?.email) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: HttpStatusCode.Unauthorized } // 401
);
}
if (session && session?.user?.email) {
const user = await prismaClient.user.findFirst({
where: {
email: session?.user?.email,
},
});
if (!user) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: HttpStatusCode.Unauthorized } // 401
);
}
if (user) {
return NextResponse.json({ user }, { status: HttpStatusCode.Ok }); // 200
}
}
}
How to add a new private page
As an example, let's say we want to add a new section to our SaaS, at /todo
to show a simple todo app.
Add the new route to the file src/data/routes.ts
export enum Routes {
/* ... */
todo = "/todo",
}
Create a new folder in src/app
and call it todo
.
Create a new file, called page.tsx
inside the todo
folder.
In the page.tsx
, add a code similar to this
import { WebAppPage } from "@/components/templates/WebAppPage/WebAppPage";
import { Routes } from "@/data/routes";
const TodoPage = () => {
return <WebAppPage currentPage={Routes.todo} />;
};
export default TodoPage;
Open the component <WebAppPage />
at src/components/templates/WebAppPage/WebAppPage.tsx
and scroll down.
Render your new page, when the current page is Routes.todo
<Flex>
{currentPage === Routes.dashboard && (
<Center w="100%" flexDir="column">
<Dashboard />
</Center>
)}
{currentPage === Routes.todo && (
<Center w="100%" flexDir="column">
<Todo />
</Center>
)}
{/* Add the route components here */}
</Flex>
To add the new menu item to the sidebar, open src/components/organisms/Sidebar/SidebarMenuItems.tsx
scroll to the MenuItem, and add a new element:
src/components/organisms/Sidebar/SidebarMenuItems.tsx
<MenuItem
route={Routes.todo}
currentPage={currentPage}
onClick={onMenuItemClick}
loadingRoute={loadingRoute}
>
<TbChecklist size="16px" /> <MenuLabel>Todo</MenuLabel>
</MenuItem>
Your menu item will now appear in the sidebar: