Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/dev0302/nextjs-project-1/llms.txt

Use this file to discover all available pages before exploring further.

The dashboard is the control centre for every AnonMessage user. It shows all received anonymous messages in reverse-chronological order, lets users pause and resume incoming messages, provides a one-click link-copy widget, and allows individual message deletion — all behind a protected route that requires a valid NextAuth JWT session.

Access and Route Protection

The dashboard lives at /dashboard. The Next.js middleware checks for a valid JWT on every request to this path:
// src/middleware.ts
if (!token && url.pathname.startsWith("/dashboard")) {
  return NextResponse.redirect(new URL("/sign-in", request.url));
}
Any visitor without an active session is immediately redirected to /sign-in. Once authenticated, the Dashboard Server Component calls getServerSession to read the current user’s identity:
// src/app/(app)/dashboard/page.tsx
const session = await getServerSession(NEXT_AUTH_CONFIG);
The component uses session.user._id to fetch the live isAcceptingMessages status from MongoDB, and session.user.username is used by the CopyLink client component to build the shareable URL.

Typical User Workflow

1

Land on the dashboard

After signing in, the user is redirected to /dashboard. The Server Component fetches the current isAcceptingMessages flag from the database and passes it as a prop to MessageToggle.
2

Check the message inbox

The MessageCard Server Component is wrapped in <Suspense> with an animated skeleton fallback. While it fetches messages, three pulsing placeholder cards are shown. Once ready, all received messages are displayed in a responsive grid sorted newest-first.
3

Toggle message acceptance

The MessageToggle switch lets the user pause or resume incoming messages instantly. Flipping it calls POST /api/accept-messages and shows a toast confirmation. If the API call fails, the switch reverts to its previous state.
4

Share the profile link

The CopyLink widget displays the user’s full public URL (/u/[username]) and provides a Copy button. After copying, the button briefly shows a green ✓ checkmark for two seconds.
5

Delete unwanted messages

Each message card has a delete button. Clicking it calls PATCH /api/delete-message?messageId=.... The card fades and scales out immediately (optimistic UI), and the message is removed from the rendered list without a page reload.

Message List — Aggregation Pipeline

Messages are fetched by GET /api/get-messages. Because messages are embedded subdocuments inside the User document, a plain findById cannot sort the internal array. The route uses a MongoDB aggregation pipeline to unwind, sort, and regroup the messages:
// src/app/api/get-messages/route.ts — aggregation pipeline
const userId = new mongoose.Types.ObjectId(user._id);

const userMessages = await UserModel.aggregate([
  { $match: { _id: userId } },
  { $unwind: "$messages" },
  { $sort: { "messages.createdAt": -1 } },
  { $group: { _id: "$_id", messages: { $push: "$messages" } } },
]).exec();
Pipeline stages explained:
StagePurpose
$matchSelects the single user document by _id
$unwindFlattens the messages array so each message becomes its own document
$sortSorts individual messages by createdAt descending (newest first)
$groupReassembles the sorted messages back into a single array
If the user has no messages, the pipeline returns an empty array and the route responds with { success: true, messages: [] } rather than a 404.
The dashboard uses React <Suspense> with a MessagesSkeleton fallback while MessageCard (a Server Component) fetches data. The skeleton renders three animated placeholder cards (animate-pulse) so the layout does not shift when messages load. Because MessageCard fetches with cache: "no-store", it always reflects the latest data on each page visit.

Message Acceptance Toggle

MessageToggle is a "use client" component that receives the current isAcceptingMessages value as the initialIsAccepting prop (read directly from MongoDB in the Server Component):
// src/app/(app)/dashboard/page.tsx
const user = await User.findById(session?.user._id)
  .select("isAcceptingMessages")
  .lean();

<MessageToggle initialIsAccepting={user?.isAcceptingMessages ?? false} />
When the toggle is flipped it calls the acceptance API:
// src/components/MessageToogle.tsx — toggle call
const res = await axios.post("/api/accept-messages", {
  acceptMessage: newValue,
});
POST /api/accept-messages validates the boolean payload against a Zod schema, then runs:
const updatedUser = await User.findByIdAndUpdate(
  userId,
  { isAcceptingMessages: acceptMessage },
  { new: true }
);
CopyLink is a "use client" component at src/components/CopyLink.tsx that reads session.user.username from the NextAuth client session to build the profile URL:
// src/components/CopyLink.tsx
const { data: session } = useSession();
const username = session?.user?.username;
const profileUrl = `${process.env.NEXT_PUBLIC_BASE_URL}/u/${username}`;

const handleCopy = async () => {
  if (!username || copied) return;
  await navigator.clipboard.writeText(profileUrl);
  setCopied(true);
  setTimeout(() => setCopied(false), 2000);
};
NEXT_PUBLIC_BASE_URL must be set in the environment (e.g. https://yourdomain.com) for the copied URL to be correct in production.

Deleting Messages

Each message card triggers a PATCH request with the message ID as a query parameter:
PATCH /api/delete-message?messageId=664f1c3b...
The route authenticates the request, then uses MongoDB’s $pull operator to remove the matching subdocument from the user’s messages array in a single atomic update:
// src/app/api/delete-message/route.tsx — $pull update
const messageId = req.nextUrl.searchParams.get("messageId");

const result = await UserModel.findByIdAndUpdate(
  userId,
  {
    $pull: {
      messages: { _id: messageId },
    },
  },
  { new: true }
);
The response includes the updated user document. On the client, MessageGrid performs an optimistic removal — the card disappears immediately with a CSS transition while the API call completes in the background.

Session Data Used

The dashboard relies on two fields from session.user:
session.user._id       // MongoDB ObjectId string — used for DB queries
session.user.username  // Unique handle — used to build the shareable URL
Both fields are injected into the JWT during sign-in by the jwt callback in lib/auth.ts and are always available in any Server Component via getServerSession(NEXT_AUTH_CONFIG).

Build docs developers (and LLMs) love