DEV Community

Cover image for How I built a sales management app with Next.js 13, Typescript, and Firebase
David Asaolu
David Asaolu

Posted on • Edited on

How I built a sales management app with Next.js 13, Typescript, and Firebase

In this tutorial, I'll walk you through how I built a sales management application that enables store owners or cashiers to record and track sales using Next.js v13, Typescript, and Firebase.

Upon completion, you'll be able to create advanced real-world web applications using Firebase's amazing features, like real-time database and authentication.

πŸ’‘ PS: This tutorial assumes you have a basic knowledge of React or Next.js.

Fully ready

Application Workflow

Before we start coding, let me summarise how the application works. The application does the following:

  • authenticates users via Email and Password with Firebase,
  • allows a user (cashier or store owner) to create various categories for the products,
  • add and delete products from the application, and
  • record and track sales made daily.

Here is a brief demo of the application:

The UI Design Process

Here, I'll walk you through creating the required pages for the web application.
First of all, you need to create a Login page. You don't need to create a sign-up page since it's exclusive to the user (cashier/store owner).

Sign Up page

Next, a Dashboard page that shows the metrics for the application and also includes navigation to other pages.

Dashboard Page

You also need a Products page where the user can add new products when available and delete products at any point in time.

Products Page

Next, create a Categories page to enable users to add or delete categories.

Categories Page

Finally, you need to create the main part of the application - the Sales page, where the user can view previous sales and add new ones.

You can add a date input field on this page to enable the user to fetch the sales made for each day, as shown below.

Sales Page

The Add New Sales component can be a modal, shown when a user clicks the Add New Sales button.

Add New Sales

In the upcoming sections, you'll learn how to create and connect the backend database to the UI and interact with Firebase in a Next.js app.

What is Firebase?

Firebase is a Backend-as-a-Service (Baas) owned by Google that enables developers to build full-stack web applications in a few minutes. Services like Firebase make it very easy for front-end developers to build full-stack web applications with little or no backend programming skills.

Firebase provides various authentication methods, a NoSQL database, a real-time database, file storage, cloud functions, hosting services, and many more.

How to add Firebase to a Next.js application

To add Firebase to a Next.js app, follow the steps below:

Create a Next.js 13 application that uses Typescript and the app router.

npx create-next-app sales-app
Enter fullscreen mode Exit fullscreen mode

Visit the Firebase console and sign in with a Gmail account.

Create a Firebase project once you are signed in.

Select the </> icon to create a new Firebase web app.

Create Firebase Web App

Provide the name of your app and register the app.

Register your Firebase app

Install the Firebase SDK by running the code snippet below.

npm install firebase
Enter fullscreen mode Exit fullscreen mode

Create a firebase.ts file at the root of your Next.js project and copy the Firebase configuration code for your app into the file.

Firebase Config

import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";

const firebaseConfig = {
  apiKey: "*****",
  authDomain: "*****",
  projectId: "*****",
  storageBucket: "*****",
  messagingSenderId: "*******",
  appId: "********",
  measurementId: "********"
};

const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
Enter fullscreen mode Exit fullscreen mode

Finally, update the firebase.ts to contain some required modules for Firebase Authentication and Firestore Database.

import { initializeApp, getApps } from "firebase/app";
import { getFirestore } from "firebase/firestore";
import { EmailAuthProvider } from "firebase/auth";
import { getAuth } from "firebase/auth";

const firebaseConfig = {
  apiKey: "AIzaSyCtmI3jLzqDSr3UIwuUdBa5ocsN5vjzpW8",
  authDomain: "stock-taking-19198.firebaseapp.com",
  projectId: "stock-taking-19198",
  storageBucket: "stock-taking-19198.appspot.com",
  messagingSenderId: "228033001185",
  appId: "1:228033001185:web:b2020053fb824a87d9a9a0",
  measurementId: "G-79BQVKMPSR"
};

// Initialize Firebase
let app = getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
const provider = new EmailAuthProvider();
const db = getFirestore(app);
const auth = getAuth(app);

export { provider, auth };
export default db;
Enter fullscreen mode Exit fullscreen mode

Congratulations!πŸŽ‰ You've successfully added Firebase to your Next.js app. Next, let's set up the needed Firebase features.

Setting up Firebase Authentication

Before you can add Firebase Authentication to your application, you need to set it up on your console.

Select Build on the left-hand panel, and click Authentication.
Add Authentication

Click the Get Started button, enable the Email/Password method, and click Save.

Firebase Email/Password Auth

If successful, your screen should display this:

Firebase Auth successful

Setting up Firebase Firestore

Select Firestore Database from the left-hand side menu and create a database.

Create the database in test mode, and use the default Cloud Firestore location settings.

Firebase location settings

After creating your database, select Usage from the top menu bar, edit the rules, and publish the changes. This enables you to make requests to the database for a longer period of time.

Firebase Database rules

πŸ’‘ PS: If you're not building this as a side project, ensure you switch the database to production mode to prevent your app from cyber attacks.

Finally, create three database collections for the categories, products, and sales data.

Database collections

Congratulations!πŸŽ‰ Your database is ready. You'll learn how to interact with it shortly.

User authentication with Firebase Auth

In this section, I'll walk you through the authentication aspect of the sales management system. You'll learn how to log users in and out of the application and how to protect pages containing confidential data from unauthenticated users.

First, create a utils.ts file that will contain the functions and import them into the required components.

Signing into the application

The function below allows the user to access the application. It accepts the user's email and password and returns a user object containing all the user's information.

You can create a new user on your Firebase console and execute the function below when the sign-in form is submitted.

import { AppRouterInstance } from "next/dist/shared/lib/app-router-context";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "./firebase";

export const LoginUser = (email: string, password: string, router: AppRouterInstance) => {
    signInWithEmailAndPassword(auth, email, password)
        .then((userCredential) => {
            const user = userCredential.user;
            //πŸ‘‡πŸ» logs user's details
            console.log("User >>", user);
            successMessage("Authentication successful πŸŽ‰");
            router.push("/dashboard");
        })
        .catch((error) => {
            console.error(error);
            errorMessage("Incorrect Email/Password ❌");
        });
};
Enter fullscreen mode Exit fullscreen mode

The code snippet above validates the user's credentials and returns an object containing all the information related to the user. If the process is successful, it redirects the user to the dashboard page; otherwise returns an error.

Logging users out of the application

Firebase also provides a signOut function that enables users to log out of the application.

Here is how it works:

import { AppRouterInstance } from "next/dist/shared/lib/app-router-context";
import { signOut } from "firebase/auth";
import { auth } from "./firebase";

export const LogOut = (router: AppRouterInstance) => {
    signOut(auth)
        .then(() => {
            successMessage("Logout successful! πŸŽ‰");
            router.push("/");
        })
        .catch((error) => {
            errorMessage("Couldn't sign out ❌");
        });
};
Enter fullscreen mode Exit fullscreen mode

The code snippet above logs users out of the application by getting the active user's details and logging them out with the help of the signOut function.

Protecting pages from unauthenticated users

To do this, you can store the user's details object to a state after logging in or use the Firebase onAuthStateChanged hook.

Using the onAuthStateChanged hook:

"use client"
import { auth } from '@/firebase'
import { onAuthStateChanged } from "firebase/auth";
import { useState, useCallback, useEffect } from "react"

const isUserLoggedIn = useCallback(() => {
        onAuthStateChanged(auth, (user) => {
            if (user) {
                setUser({ email: user.email, uid: user.uid });
                //πŸ‘‰πŸ» Perform an authenticated request
            } else {
                return router.push("/");
            }
        });
    }, [router]);

useEffect(() => {
    isUserLoggedIn();
}, [isUserLoggedIn]);
Enter fullscreen mode Exit fullscreen mode

The onAuthStateChanged hook checks if the user is active and returns the object containing all the user's details. You can execute the function on page load for all the routes, except the Login page.

Real-time communication with Firebase: data structure and CRUD operation

In this section, I'll walk you through setting up the data structure for the application and interacting with Firebase.

The Categories collection

The first page to set up in this application is the Categories page because when the user adds a product, it has to be under a category.
Therefore, create your Categories page similar to the image below.

Categories page

Execute the code snippet below when the user adds a new category.

import { collection, addDoc } from "firebase/firestore";
import db from "./firebase";

export const addCategory = async (name: string) => {
    try {
        await addDoc(collection(db, "categories"), {
            name
        })
        successMessage(`${name} category added! πŸŽ‰`)
    } catch (err) {
        errorMessage("Error! ❌")
        console.error(err)
}
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above accepts the category name from the input field and creates a new document (category) on Firebase.

To delete a category, run the code snippet below when a user clicks the delete button.

export const deleteCategory =  async (id: string, name:string) => {
    try {
        //πŸ‘‡πŸ» deletes the category
        await deleteDoc(doc(db, "categories", id));
        //πŸ‘‡πŸ» delets the products within the category
        const q = query(collection(db, "products"), where("category", "==", name));
        const unsubscribe = onSnapshot(q, (querySnapshot) => {
            querySnapshot.forEach((document) => {
                deleteDoc(doc(db, "products", document.id));
            });
        });
        successMessage(`${name} category deleted πŸŽ‰`)
    } catch (err) {
        errorMessage("Encountered an error ❌")
        console.log(err)
    }

}
Enter fullscreen mode Exit fullscreen mode

The deleteCategory function above accepts the ID and name of the selected category, deletes the category from the list, and deletes the products under the selected category.

Finally, you need to get the categories list from Firebase when the page loads. Execute the function below to do this:

export const getCategories = async (setCategories: any) => {
    try {
        const unsub = onSnapshot(collection(db, "categories"), doc => {
            const docs: any = []
            doc.forEach((d: any) => {
              docs.push( { ...d.data(), id: d.id })
            });
            setCategories(docs)
        }) 
    } catch (err) {
        console.error(err)
        setCategories([])
    }
}
Enter fullscreen mode Exit fullscreen mode

The code snippet above accepts a parameter called setCategories - the React state containing all the categories. Then sends a request to Firebase to retrieve the categories list and updates the setCategories state with the data.

The Products collection

After setting up the Categories page, you need to allow users to add and delete products under any available categories.

Add Product

Create a Products page similar to the image above and execute the code snippet below when the user adds a new product.

export const addProduct = async (name: string, price: number, category: string) => {
    try {
        await addDoc(collection(db, "products"), {
            name, price, category
        })
        successMessage(`${name} product added! πŸŽ‰`)
    }   
         catch (err) {
        errorMessage("Error! ❌")
        console.error(err)
    }

}
Enter fullscreen mode Exit fullscreen mode

The code snippet above accepts the product's name, price, and category from the form field and adds it to the products collection.

Run this function to display the products on page load.

export const getProducts = async (setProducts: any) => {
    try {
        const unsub = onSnapshot(collection(db, "products"), doc => {
            const docs: any = []
            doc.forEach((d: any) => {
              docs.unshift( { ...d.data(), id: d.id })
            });
            setProducts(docs)
        }) 
    } catch (err) {
        console.error(err)
        setProducts([])
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, allow the user to delete products from the products collection. Therefore, execute the function below when a user clicks the delete button.

export const deleteProduct =  async (id: string, name:string) => {
    try {
        await deleteDoc(doc(db, "products", id));
        successMessage(`${name} deleted πŸŽ‰`)
    } catch (err) {
        errorMessage("Encountered an error ❌")
        console.log(err)
    }

}
Enter fullscreen mode Exit fullscreen mode

The function accepts the product's name and ID, then deletes the product from the collection via its ID.

The Sales collection

Here, you need to create functions that enable the user to add and get sales for a particular day and the total sales made on the platform.

export const addSales = async (customerName: string, customerEmail: string, products: Items[], totalAmount: number, setAddNew: any) => {
    try {
        await addDoc(collection(db, "sales"), {
            customerName, customerEmail, products, totalAmount, timestamp: serverTimestamp()
        })
        successMessage("Sales recorded! πŸŽ‰")
        setAddNew(false)

    } catch (err) {
        console.error(err)
        errorMessage("Error! Try again ❌")
    }
}
Enter fullscreen mode Exit fullscreen mode

The function below accepts the customer's and products' details, then adds them to the sales collection.

To get all the sales, execute the code snippet below on the page load. It queries the sales collection and returns all the data.

export const getSales = async (setSales: any) => {
try {
    const docRef = collection(db, "sales")
    const q = query(docRef, orderBy("timestamp"))
    onSnapshot(q, (snapshot) => {
        const docs: any = []
            snapshot.forEach((d: any) => {
              docs.unshift( { ...d.data(), id: d.id })
            });
        setSales(docs)
    })  
    } catch (err) {
        console.error(err)
        setSales([])
    }
}
Enter fullscreen mode Exit fullscreen mode

On the dashboard page, a box shows the total amount made from the sales. The code snippet below returns the total amount.

export const getTotalSales = async (setTotalSales: any) => {
try {
        const unsub = onSnapshot(collection(db, "sales"), doc => {
            let totalSales:number = 0
            doc.forEach((d: any) => {
              totalSales += d.data().totalAmount
            });
            setTotalSales(totalSales)
        }) 
    } catch (err) {
    console.error(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, on the Products page, you may want to display sales only made on a particular day. The code snippet below queries the sales collection and returns sales made on a specific day.
You can edit the function to return sales made weekly, monthly, quarterly, or yearly. You just need to update the Date format with the right duration.

export const getSalesForDay = async (date: Date | null, setSales: any) => {
    try {
        const day = date?.getDate()
        const month = date?.getMonth()
        const year: number | undefined = date?.getFullYear()

        if (day !== undefined && month !== undefined && year !== undefined) {
            const startDate = new Date(year, month, day, 0, 0, 0);
            const endDate = new Date(year, month, day, 23, 59, 59);

            const docRef = collection(db, "sales")
            const q = query(docRef, orderBy("timestamp"), where("timestamp", ">=", Timestamp.fromDate(startDate)), where("timestamp", "<=", Timestamp.fromDate(endDate)))

            onSnapshot(q, (snapshot) => {
                const docs: any = []
                snapshot.forEach((d: any) => {
                docs.unshift( { ...d.data(), id: d.id })
                });
                setSales(docs)
        })
        }

     }
    catch (err) {
        console.error(err)
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Congratulations on making it thus far! You've learnt

  • what Firebase is,
  • how to add Firebase to a Next.js app,
  • how to work with Firebase Auth and Firebase Firestore, and
  • how to build a sales management application.

The source code is also available here.

Firebase is a great tool that provides almost everything you need to build a full-stack web application. If you want to create a full-stack web application without any backend programming experience, consider using Firebase.

Thank you for reading! πŸŽ‰

Open to workπŸ™‚

Did you enjoy this article or need an experienced Technical Writer / React Developer for a remote, full-time, or contract-based role? Feel free to contact me.
GitHub || LinkedIn || Twitter

Buy David a coffee
Thank You

Top comments (4)

Collapse
 
kellyblaire profile image
Kelly Okere

Nice work!

Collapse
 
arshadayvid profile image
David Asaolu

Thank you.πŸ™‚

Collapse
 
lexycon002 profile image
Awowole Hammad Olamilekan

A very sublime project; I will try to interact with your code and come up with something nice.
Thank you for sharing this

Collapse
 
arshadayvid profile image
David Asaolu • Edited

You're welcomeπŸ™‚