DEV Community

Cover image for DEVELOPING A FINANCIAL DASHBOARD APPLICATION USING NEXT.JS
BARNABU for Swahilipot Developers

Posted on • Updated on

DEVELOPING A FINANCIAL DASHBOARD APPLICATION USING NEXT.JS

INTRODUCTION

Over the past two weeks, I worked on developing a financial dashboard application using Next.js.

TABLE OF CONTENTS

  1. What to be Developed
  2. Overview of Application
  3. System Requirements
  4. Getting Started

WHAT TO BE DEVELOPED

  • A financial dashboard home page.

  • A login page.

  • Financial dashboard layout.

OVERVIEW OF APPLICATION

In the development of the financial dashboard application, there are some features that are going to be implemented like:

  • Styling: The different ways to style your application in Next.js.

  • Optimizations: How to optimize images, links, and fonts.
    Routing: How to create nested layouts and pages using file-system routing.

  • Data Fetching: How to set up a database on Vercel, and best practices for fetching and streaming.

SYSTEM REQUIREMENTS

This involves the system requirements:

GETTING STARTED

Creating The Financial Dashboard Application:

You need to install the pnpm as your package manager, and if you don't have pnpm install in your operating system, install by running this command on terminal:

npm install -g pnpm
Enter fullscreen mode Exit fullscreen mode

Then to create a Next.js financial dashboard app, open your terminal, navigate to directory you want your app folder to be and then run the following command in the terminal:

npx create-next-app@latest nextjs-dashboard --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" --use-pnpm
Enter fullscreen mode Exit fullscreen mode

Folder Structure:

You'll notice that the project has the following folder structure:

  • /app: Contains all the routes, components, and logic for your application, this is where you'll be mostly working from.
  • /app/lib: Contains functions used in your application, such as reusable utility functions and data fetching functions.
  • /app/ui: Contains all the UI components for your application, such as cards, tables, and forms.
  • /public: Contains all the static assets for your application, such as images.
  • Config Files: You'll also notice config files such as next.config.js at the root of your application. Most of these files are created and pre-configured when you start a new project using create-next-app.

Running The Development Server:

Run pnpm i on your terminal to install the project's packages.

pnpm i
Enter fullscreen mode Exit fullscreen mode

Followed by pnpm dev to start the development server.

pnpm dev
Enter fullscreen mode Exit fullscreen mode

pnpm dev starts your Next.js development server. Check to see if it's working.
Open http://localhost:3000 on your browser.

CREATING A FINANCIAL DASHBOARD HOME PAGE

The home page will created by adding the images and fonts. To add the images, use the next/image component to automatically optimize your images.

Adding The Desktop Hero Image;

In your /app/page.tsx file, import the component from next/image. Then, add the image by using the following statements below:

import AcmeLogo from '@/app/ui/acme-logo';
import { ArrowRightIcon } from '@heroicons/react/24/outline';
import Link from 'next/link';
import { lusitana } from '@/app/ui/fonts';
import Image from 'next/image';

export default function Page() {
  return (
    // ...
    <div className="flex items-center justify-center p-6 md:w-3/5 md:px-28 md:py-12">
      {/* Add Hero Images Here */}
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop version"
      />
    </div>
    //...
  );
}
Enter fullscreen mode Exit fullscreen mode

Adding The Mobile Hero Image;

To add the Mobile Hero Image, in the same /app/page.tsx add the following statements below the Desktop Hero Image statements.

{/* Add Hero Images Here */}
      <Image
        src="/hero-desktop.png"
        width={1000}
        height={760}
        className="hidden md:block"
        alt="Screenshots of the dashboard project showing desktop version"
      />
      <Image
        src="/hero-mobile.png"
        width={560}
        height={620}
        className="block md:hidden"
        alt="Screenshot of the dashboard project showing mobile version"
      />
Enter fullscreen mode Exit fullscreen mode

Now let's add some fonts to that home page. In your /app/ui folder, create a new file called fonts.ts. You'll use this file to keep the fonts that will be used throughout your application.

import { Inter } from 'next/font/google';

export const inter = Inter({ subsets: ['latin'] });
Enter fullscreen mode Exit fullscreen mode

Finally, add the font to the

element in /app/layout.tsx:
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body className={`${inter.className} antialiased`}>{children}</body>
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

So now the financial dashboard application home page will look like this below:

Home page

CREATING A LOGIN PAGE

Start by creating a new route called /login and paste the following code in your /app/login/page.tsx:

import AcmeLogo from '@/app/ui/acme-logo';
import LoginForm from '@/app/ui/login-form';

export default function LoginPage() {
  return (
    <main className="flex items-center justify-center md:h-screen">
      <div className="relative mx-auto flex w-full max-w-[400px] flex-col space-y-2.5 p-4 md:-mt-32">
        <div className="flex h-20 w-full items-end rounded-lg bg-blue-500 p-3 md:h-36">
          <div className="w-32 text-white md:w-36">
            <AcmeLogo />
          </div>
        </div>
        <LoginForm />
      </div>
    </main>
  );
}
Enter fullscreen mode Exit fullscreen mode

You'll notice the page imports . We will be using the NextAuth.js to add authentication to our application. NextAuth.js abstracts away much of the complexity involved in managing sessions, sign-in and sign-out, and other aspects of authentication.

Setting Up NextAuth.js;

Install NextAuth.js by running the following command in your terminal:

pnpm i next-auth@beta
Enter fullscreen mode Exit fullscreen mode

Here, you're installing the beta version of NextAuth.js, which is compatible with Next.js 14.
Next, generate a secret key for your application. This key is used to encrypt cookies, ensuring the security of user sessions. You can do this by running the following command in your terminal:

openssl rand -base64 32
Enter fullscreen mode Exit fullscreen mode

Then, in your .env file, add your generated key to the AUTH_SECRET variable:

AUTH_SECRET=your-secret-key
Enter fullscreen mode Exit fullscreen mode

Adding The Page Options;

Create an auth.config.ts file at the root of our project that exports an authConfig object. This object will contain the configuration options for NextAuth.js. For now, it will only contain the pages option:

import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
  pages: {
    signIn: '/login',
  },
} satisfies NextAuthConfig;
Enter fullscreen mode Exit fullscreen mode

You can use the pages option to specify the route for custom sign-in, sign-out, and error pages. This is not required, but by adding signIn: '/login' into our pages option, the user will be redirected to our custom login page, rather than the NextAuth.js default page.

Protecting Your Routes With Next.js Middleware;

Next, add the logic to protect your routes. This will prevent users from accessing the dashboard pages unless they are logged in.

import type { NextAuthConfig } from 'next-auth';

export const authConfig = {
  pages: {
    signIn: '/login',
  },
  callbacks: {
    authorized({ auth, request: { nextUrl } }) {
      const isLoggedIn = !!auth?.user;
      const isOnDashboard = nextUrl.pathname.startsWith('/dashboard');
      if (isOnDashboard) {
        if (isLoggedIn) return true;
        return false; // Redirect unauthenticated users to login page
      } else if (isLoggedIn) {
        return Response.redirect(new URL('/dashboard', nextUrl));
      }
      return true;
    },
  },
  providers: [], // Add providers with an empty array for now
} satisfies NextAuthConfig;
Enter fullscreen mode Exit fullscreen mode

The authorized callback is used to verify if the request is authorized to access a page via Next.js Middleware. It is called before a request is completed, and it receives an object with the auth and request properties. The auth property contains the user's session, and the request property contains the incoming request.
Next, you will need to import the authConfig object into a Middleware file. In the root of your project, create a file called middleware.ts and paste the following code:

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export default NextAuth(authConfig).auth;

export const config = {
  // https://nextjs.org/docs/app/building-your-application/routing/middleware#matcher
  matcher: ['/((?!api|_next/static|_next/image|.*\\.png$).*)'],
};
Enter fullscreen mode Exit fullscreen mode

Here you're initializing NextAuth.js with the authConfig object and exporting the auth property. You're also using the matcher option from Middleware to specify that it should run on specific paths. Then create a new file called auth.ts that spreads your authConfig object:

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
});
Enter fullscreen mode Exit fullscreen mode

Adding The Credentials Providers;

Next, you will need to add the providers option for NextAuth.js. providers is an array where you list different login options such as Google or GitHub. The Credentials provider allows users to log in with a username and a password. In your /auth.ts add the following code:

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [Credentials({})],
});
Enter fullscreen mode Exit fullscreen mode

Adding The Sign In Functionality;

You can use the authorize function to handle the authentication logic. Similarly to Server Actions, you can use zod to validate the email and password before checking if the user exists in the database:

import NextAuth from 'next-auth';
import { authConfig } from './auth.config';
import Credentials from 'next-auth/providers/credentials';
import { z } from 'zod';

export const { auth, signIn, signOut } = NextAuth({
  ...authConfig,
  providers: [
    Credentials({
      async authorize(credentials) {
        const parsedCredentials = z
          .object({ email: z.string().email(), password: z.string().min(6) })
          .safeParse(credentials);
      },
    }),
  ],
});
Enter fullscreen mode Exit fullscreen mode

After validating the credentials, create a new getUser function that queries the user from the database.

import { sql } from '@vercel/postgres';
import type { User } from '@/app/lib/definitions';
import bcrypt from 'bcrypt';

async function getUser(email: string): Promise<User | undefined> {
  try {
    const user = await sql<User>`SELECT * FROM users WHERE email=${email}`;
    return user.rows[0];
  } catch (error) {
    console.error('Failed to fetch user:', error);
    throw new Error('Failed to fetch user.');
  }
}

if (parsedCredentials.success) {
          const { email, password } = parsedCredentials.data;
          const user = await getUser(email);
          if (!user) return null;
Enter fullscreen mode Exit fullscreen mode

Updating The Login Form;

Now you need to connect the auth logic with your login form. In your /actions.ts file, create a new action called authenticate. This action should import the signIn function from /auth.ts:

'use server';

import { signIn } from '@/auth';
import { AuthError } from 'next-auth';

// ...

export async function authenticate(
  prevState: string | undefined,
  formData: FormData,
) {
  try {
    await signIn('credentials', formData);
  } catch (error) {
    if (error instanceof AuthError) {
      switch (error.type) {
        case 'CredentialsSignin':
          return 'Invalid credentials.';
        default:
          return 'Something went wrong.';
      }
    }
    throw error;
  }
}
Enter fullscreen mode Exit fullscreen mode

Finally, in your login-form.tsx component, you can use React's useActionState to call the server action, handle form errors, and display the form's pending state:

import { useActionState } from 'react';
import { authenticate } from '@/app/lib/actions';

const [errorMessage, formAction, isPending] = useActionState(
    authenticate,
    undefined,
  );

<form action={formAction} className="space-y-3">

<Button className="mt-4 w-full" aria-disabled={isPending}>
          Log in <ArrowRightIcon className="ml-auto h-5 w-5 text-gray-50" />
        </Button>

{errorMessage && (
            <>
              <ExclamationCircleIcon className="h-5 w-5 text-red-500" />
              <p className="text-sm text-red-500">{errorMessage}</p>
            </>
          )}
Enter fullscreen mode Exit fullscreen mode

Adding Logout Functionality;

To add the logout functionality to , call the signOut function from auth.ts in your

element:
import { signOut } from '@/auth';

action={async () => {
            'use server';
            await signOut();
          }}

Now, try it out. You should be able to log in and out of your application. The login page should look like this:

Login Page

CREATING DASHBOARD LAYOUT

Dashboards have some sort of navigation that is shared across multiple pages. In Next.js, you can use a special /layout.tsx file to create UI that is shared between multiple pages. inside the /dashboard folder, add a new file called /layout.tsx and paste the following code:

import SideNav from '@/app/ui/dashboard/sidenav';

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
      <div className="w-full flex-none md:w-64">
        <SideNav />
      </div>
      <div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
    </div>
  );
}

You'll see the dashboard layout like this below:

Dashboard Layout

Also set up pages for the /customers and /invoices.
Customers Page: /app/dashboard/customers/page.tsx

export default function Page() {
  return <p>Customers Page</p>;
}

Invoices Page: /app/dashboard/invoices/page.tsx

export default function Page() {
  return <p>Invoices Page</p>;
}

Navigation Between Pages:

In Next.js, you can use the Component to link between pages in your application. allows you to do client-side navigation with JavaScript. To use the component, open /app/ui/dashboard/nav-links.tsx, and import the Link component from next/link. Then, replace the tag with :

import {
  UserGroupIcon,
  HomeIcon,
  DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
**import Link from 'next/link';**

// ...

export default function NavLinks() {
  return (
    <>
      {links.map((link) => {
        const LinkIcon = link.icon;
        return (
          <Link
            key={link.name}
            href={link.href}
            className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
          >
            <LinkIcon className="w-6" />
            <p className="hidden md:block">{link.name}</p>
          </Link>
        );
      })}
    </>
  );
}

CREATING THE DATABASE

  1. Create A Github Repository;

To start, let's push your repository to Github if you haven't done so already. This will make it easier to set up your database and deploy. If you need help setting up your repository, take a look at this guide on Github.

  1. Create A Vercel Account;

Visit Vercel.com to create an account. Choose the free "hobby" plan. Select Continue with GitHub to connect your GitHub and Vercel accounts.

  1. Connect And Deploy Your Project;

You'll be taken to this screen where you can select and import the GitHub repository you've just created:

Vercel Dashboard

Name your project and click Deploy.

  1. Create A Postgres Database;

To set up a database, click Continue to Dashboard and select the Storage tab from your project dashboard. Select Connect Store → Create New → Postgres → Continue.

Postgress Database

Top comments (0)