DEV Community

Cover image for How To Build Your Own Newsletter App? p.1
Emil Pearce for novu

Posted on • Originally published at novu.co

How To Build Your Own Newsletter App? p.1

TL;DR

In this 2-part tutorial, I'll guide you through building a newsletter application that allows users to subscribe to a mailing list using a Google or GitHub account.

The application stores the user's details (email, first and last names) to Firebase, enabling the admin user to send notifications of beautifully designed email templates to every subscriber within the application using Novu.

You'll learn how to:

  • implement Firebase Google, GitHub, and Email/Password authentication to your applications
  • Save and fetch data from Firebase Firestore
  • Create and modify email templates in the Novu Dev Studio
  • Send email templates using the Novu

Any Gif

Building the application interface with Next.js

In this section, I'll walk you through building the user interface for the newsletter application.

First, let's set up a Next.js project. Open your terminal and run the following command:

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

Follow the prompts to select your preferred configuration settings. For this tutorial, we'll use TypeScript and the Next.js App Router for navigation.

CLI installation

Install the React Icons package, which allows us to use various icons within the application.

npm install react-icons
Enter fullscreen mode Exit fullscreen mode

The application pages are divided into two parts - the subscriber routes and admin routes.

  • Subscriber Routes:
    • / - The application home page that displays GitHub and Google sign-in buttons for user authentication.
    • /subscribe - This page notifies users that they have successfully subscribed to the newsletter.
  • Admin Routes:
    • /admin - This route displays the admin sign-up and login buttons.
    • /admin/register - Here, admins can access the sign-up form to create a new admin account.
    • /admin/login - Existing admin users can log into the application using their email and password.
    • /admin/dashboard - Admin users can access this route to view all subscribers and manage the creation and sending of newsletters.

The Subscriber Routes

Copy the code snippet below into app/page.tsx file. It displays the GiHub and Google sign-in buttons.

"use client";
import { useState } from "react";
import { FaGoogle, FaGithub } from "react-icons/fa";

export default function Home() {
    const [loading, setLoading] = useState<boolean>(false);

    const handleGoogleSignIn = () => {
        setLoading(true);
        //👉🏻 Firebase Google Sign in function
    };

    const handleGithubSignIn = () => {
        setLoading(true);
        //👉🏻 Firebase GitHub Sign in function
    };

    return (
        <main className='flex h-screen flex-col p-8 items-center justify-center'>
            <h2 className='text-3xl font-bold mb-6'>Sign up to the Newsletter</h2>

            <button
                className='py-3 px-6 flex items-center justify-center rounded-md border-gray-600 border-2 w-3/5 mb-4 text-lg hover:bg-gray-800 hover:text-gray-50'
                disabled={loading}
            >
                <FaGithub className='inline-block mr-2' />
                {loading ? "Signing in..." : "Sign Up with Github"}
            </button>

            <button
                className='py-3 px-6 flex items-center justify-center rounded-md border-gray-600 border-2 w-3/5 mb-4 text-lg hover:bg-gray-800 hover:text-gray-50'
                disabled={loading}
            >
                <FaGoogle className='inline-block mr-2' />
                {loading ? "Signing in..." : "Sign Up with Google"}
            </button>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Signup page

Create a subscribe folder within the app directory. Inside the subscribe folder, add a page.tsx file and a layout.tsx file.

cd app
mkdir subscribe && cd subscribe
touch page.tsx layout.tsx
Enter fullscreen mode Exit fullscreen mode

To indicate that the user has successfully subscribed to the newsletter, copy and paste the following code snippet into the subscribe/page.tsx file:

"use client";

export default function Subscribe() {
    const handleSignOut = () => {
        //👉🏻 signout the subscriber
    };

    return (
        <main className='p-8 min-h-screen flex flex-col items-center justify-center'>
            <h3 className='text-3xl font-bold mb-4'>You&apos;ve Subscribed!</h3>
            <button
                className='bg-black text-gray-50 px-8 py-4 rounded-md w-[200px]'
                onClick={handleSignOut}
            >
                Back
            </button>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Signed up page

Finally, update the subscribe/layout.tsx file with the following changes to set the page title to Subscribe | Newsletter Subscription:

import type { Metadata } from "next";
import { Sora } from "next/font/google";

const sora = Sora({ subsets: ["latin"] });

//👇🏻 changes the page title
export const metadata: Metadata = {
    title: "Subscribe | Newsletter Subscription",
    description: "Generated by create next app",
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang='en'>
            <body className={sora.className}>{children}</body>
        </html>
    );
}
Enter fullscreen mode Exit fullscreen mode

The Admin Routes

Create an admin folder within the app directory. Inside the admin folder, add a page.tsx file to represent the Admin home page and a layout.tsx file to display the page title.

cd app
mkdir admin && cd admin
touch page.tsx layout.tsx
Enter fullscreen mode Exit fullscreen mode

Copy the code snippet below into the admin/page.tsx file. It displays the links to the Admin Sign-up and Login pages.

import Link from "next/link";

export default function Admin() {
    return (
        <main className='flex min-h-screen flex-col items-center justify-center p-8'>
            <h2 className='text-3xl font-bold mb-6'>Admin Panel</h2>

            <Link
                href='/admin/register'
                className='py-3 px-6 flex items-center justify-center rounded-md border-gray-600 border-2 w-3/5 mb-4 text-lg hover:bg-gray-800 hover:text-gray-50'
            >
                Sign Up as an Admin
            </Link>

            <Link
                href='/admin/login'
                className='py-3 px-6 flex items-center justify-center rounded-md border-gray-600 border-2 w-3/5 mb-4 text-lg hover:bg-gray-800 hover:text-gray-50'
            >
                Log in as an Admin
            </Link>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Admin Panel

Update the page title by copying this code snippet into the admin/layout.tsx file.

import type { Metadata } from "next";
import { Sora } from "next/font/google";

const sora = Sora({ subsets: ["latin"] });

export const metadata: Metadata = {
    title: "Admin | Newsletter Subscription",
    description: "Generated by create next app",
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang='en'>
            <body className={sora.className}>{children}</body>
        </html>
    );
}
Enter fullscreen mode Exit fullscreen mode

Next, you need to create the Admin Register, Login, and Dashboard pages. Therefore, create the folders containing a page.tsx file.

cd admin
mkdir login register dashboard
cd login && page.tsx
cd ..
cd register && page.tsx
cd ..
cd dashboard && page.tsx
Enter fullscreen mode Exit fullscreen mode

Within the register/page.tsx file, copy and pasted the code snippet provided below. The code block displays the sign-up form where users can enter their email and password to create an admin user account.

"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";

export default function Register() {
    const [email, setEmail] = useState<string>("");
    const [disabled, setDisabled] = useState<boolean>(false);
    const [password, setPassword] = useState<string>("");

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        setDisabled(true);
        //👉🏻 admin sign up function
    };
    return (
        <main className='flex min-h-screen flex-col items-center justify-center p-8'>
            <h2 className='font-bold text-3xl text-gray-700 mb-3'>Admin Sign Up</h2>
            <form
                className='md:w-2/3 w-full flex flex-col justify-center'
                onSubmit={handleSubmit}
            >
                <label htmlFor='email' className='text-lg'>
                    Email
                </label>
                <input
                    type='email'
                    id='email'
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    required
                    className='w-full py-3 px-6 border-gray-600 border-[1px] rounded-md mb-4'
                />

                <label htmlFor='password' className='text-lg'>
                    Password
                </label>
                <input
                    type='password'
                    id='password'
                    required
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                    className='w-full py-3 px-6 border-gray-600 border-[1px] rounded-md mb-4'
                />

                <button
                    className='py-3 px-6 rounded-md bg-black text-gray-50'
                    disabled={disabled}
                >
                    Register
                </button>
                <p className='text-md mt-4 text-center'>
                    Already have an account?{" "}
                    <Link href='/admin/login' className='text-blue-500'>
                        Log in
                    </Link>
                </p>
            </form>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Admin sign up page

Update the login/page.tsx file similarly to the admin register page. It accepts the user's email and password and logs the user into the application.

"use client";
import React, { useState } from "react";
import Link from "next/link";

export default function Login() {
    const [email, setEmail] = useState<string>("");
    const [password, setPassword] = useState<string>("");
    const [disabled, setDisabled] = useState<boolean>(false);

    const handleSubmit = (e: React.FormEvent) => {
        e.preventDefault();
        setDisabled(true);
        //👉🏻 log user into the application
    };

    return (
        <main className='flex min-h-screen flex-col items-center justify-center p-8'>
            <h2 className='font-bold text-3xl text-gray-700 mb-3'>Admin Login</h2>
            <form
                className='md:w-2/3 w-full flex flex-col justify-center'
                onSubmit={handleSubmit}
            >
                <label htmlFor='email' className='text-lg'>
                    Email
                </label>
                <input
                    type='email'
                    id='email'
                    value={email}
                    onChange={(e) => setEmail(e.target.value)}
                    placeholder='test@gmail.com'
                    required
                    className='w-full py-3 px-6 border-gray-600 border-[1px] rounded-md mb-4'
                />

                <label htmlFor='password' className='text-lg'>
                    Password
                </label>
                <input
                    type='password'
                    id='password'
                    required
                    placeholder='test123'
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                    className='w-full py-3 px-6 border-gray-600 border-[1px] rounded-md mb-4'
                />

                <button
                    className='py-3 px-6 rounded-md bg-black text-gray-50'
                    disabled={disabled}
                >
                    Log in
                </button>
                <p className='text-md mt-4 text-center'>
                    Don&apos;t have an account?{" "}
                    <Link href='/admin/register' className='text-blue-500'>
                        Create one
                    </Link>
                </p>
            </form>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Admin Login

Finally, let's create the admin dashboard page. It displays all the available subscribers and a form enabling admin users to create and view the existing newsletters.

Create campaign page

Copy the code snippet below into the dashboard/page.tsx file.

"use client";
import { useState } from "react";
import Link from "next/link";
import Newsletters from "@/components/Newsletters";
import SubscribersList from "@/components/SubscribersList";

type Subscriber = {
    id: string;
    data: {
        email: string;
        firstName: string;
        lastName: string;
        topics: string[];
    };
};

export default function Dashboard() {
    const [subscribers, setSubscribers] = useState<Subscriber[]>([]);
    const [toggleView, setToggleView] = useState<boolean>(false);
    const handleToggleView = () => setToggleView(!toggleView);

    const fetchAllSubscribers = async () => {
        //👉🏻 fetch all subscribers
    };

    const handleLogout = () => {
        //👉🏻 sign user out
    };

    return (
        <div className='flex w-full min3-h-screen relative'>
            <div className='lg:w-[15%] border-r-2 lg:flex hidden flex-col justify-between min-h-[100vh]'>
                <nav className='fixed p-4 pb-8 flex flex-col justify-between h-screen'>
                    <div className='flex flex-col space-y-4'>
                        <h3 className='text-2xl font-bold text-blue-500 mb-6'>Dashboard</h3>
                        <Link
                            href='#subscribers'
                            onClick={handleToggleView}
                            className={`${
                                toggleView ? "" : "bg-blue-400 text-blue-50"
                            }  p-3 mb-2 rounded-md`}
                        >
                            Subscribers
                        </Link>
                        <Link
                            href='#newsletters'
                            onClick={handleToggleView}
                            className={`${
                                !toggleView ? "" : "bg-blue-400 text-blue-50"
                            }  p-3 mb-2 rounded-md`}
                        >
                            Newsletters
                        </Link>
                    </div>

                    <button className='text-red-500 block mt-10' onClick={handleLogout}>
                        Log out
                    </button>
                </nav>
            </div>

            <main className='lg:w-[85%] w-full bg-white h-full p-4'>
                <div className='flex items-center lg:justify-end justify-between mb-3'>
                    <Link
                        href='#'
                        onClick={handleToggleView}
                        className='lg:hidden block text-blue-700'
                    >
                        {!toggleView ? "View Newsletters" : "View Subscribers"}
                    </Link>
                    <button
                        className='bg-red-500 text-white px-5 py-3 rounded-md lg:hidden block'
                        onClick={handleLogout}
                    >
                        Log Out
                    </button>
                </div>
                <div>
                    {toggleView ? (
                        <Newsletters />
                    ) : (
                        <SubscribersList subscribers={subscribers} />
                    )}
                </div>
            </main>
        </div>
    );
}
Enter fullscreen mode Exit fullscreen mode

From the code snippet above, we have two components: the Newsletters and the SubscribersList.

The Newsletters component renders the existing newsletters and a form that allows the admin to create new ones. The SubscribersList component displays all the existing subscribers within the application.

Therefore, create a components folder that contains the Newsletters and SubscribersList components within the app folder.

cd app
mkdir components && cd components
touch SubscribersList.tsx Newsletters.tsx
Enter fullscreen mode Exit fullscreen mode

Copy the code snippet below into the SubscribersList.tsx. It accepts the existing subscribers' data as a prop and renders it within a table.

"use client";

type Subscriber = {
    id: string;
    data: {
        email: string;
        firstName: string;
        lastName: string;
        topics: string[];
    };
};

export default function SubscribersList({
    subscribers,
}: {
    subscribers: Subscriber[];
}) {
    return (
        <main className='w-full'>
            <section className='flex items-center justify-between mb-4'>
                <h2 className='font-bold text-2xl mb-3'>All Subscribers</h2>
            </section>
            <div className='w-full'>
                <table className='w-full'>
                    <thead>
                        <tr>
                            <th className='py-3'>First Name</th>
                            <th className='py-3'>Last Name</th>
                            <th className='py-3'>Email Address</th>
                        </tr>
                    </thead>
                    <tbody>
                        {subscribers.map((subscriber) => (
                            <tr key={subscriber.id}>
                                <td className='py-3'>{subscriber.data.firstName}</td>
                                <td className='py-3'>{subscriber.data.lastName}</td>
                                <td className='py-3'>{subscriber.data.email}</td>
                            </tr>
                        ))}
                    </tbody>
                </table>
            </div>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Lastly, update the Newsletters.tsx component to display the newsletter creation form and display the existing newsletters.

"use client";
import React, { useEffect, useState } from "react";

export default function Newsletters() {
    const [recipients, setRecipients] = useState<string[]>([]);
    const [subject, setSubject] = useState<string>("");
    const [message, setMessage] = useState<string>("");
    const [disabled, setDisable] = useState<boolean>(false);
    const [subscribers, setSubscribers] = useState<SubscribersData[]>([]);
    const [newsLetters, setNewsletters] = useState<string[]>([]);

    const handleSendNewsletter = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        emailNewsletter();
    };

    const emailNewsletter = async () => {
        //👉🏻 send newsletter
    };

    const fetchRecipients = async () => {
        //👉🏻 fetch all subscribers
    };

    const fetchNewsLetters = async () => {
        //👉🏻 fetch all newsletters
    };

    useEffect(() => {
        fetchRecipients();
        fetchNewsLetters();
    }, []);

    return (
        <main className='w-full'>
            <h2 className='font-bold text-2xl mb-4'>Create Campaign</h2>
            <form onSubmit={handleSendNewsletter} className='mb-8'>
                <label htmlFor='subject'>Subject</label>
                <input
                    type='text'
                    id='subject'
                    value={subject}
                    required
                    onChange={(e) => setSubject(e.target.value)}
                    className='w-full px-4 py-3 border-[1px] border-gray-600 rounded-sm mb-3'
                />

                <label htmlFor='recipients'>Recipients</label>
                <input
                    type='text'
                    id='recipients'
                    className='w-full px-4 py-3 border-[1px] border-gray-600 rounded-sm mb-3'
                    disabled
                    readOnly
                    value={recipients.join(", ")}
                />
                <label htmlFor='message'>Message</label>
                <textarea
                    id='message'
                    rows={5}
                    value={message}
                    required
                    onChange={(e) => setMessage(e.target.value)}
                    className='w-full px-4 py-3 border-[1px] border-gray-600 rounded-sm'
                ></textarea>
                <button
                    className='bg-blue-500 text-white py-3 px-6 rounded my-3'
                    disabled={disabled}
                >
                    {disabled ? "Sending..." : "Send Newsletter"}
                </button>
            </form>

            <h2 className='font-bold text-2xl '>Recent Newsletters</h2>
            <div className='flex flex-col gap-4'>
                {newsLetters.map((item, index) => (
                    <div
                        className='flex justify-between items-center bg-gray-100 p-4'
                        key={index}
                    >
                        <h3 className='font-bold text-md'>{item}</h3>
                        <button className='bg-green-500 text-gray-50 px-4 py-2 rounded-md'>
                            Sent
                        </button>
                    </div>
                ))}
            </div>
        </main>
    );
}
Enter fullscreen mode Exit fullscreen mode

Create campaign 2 gif

Congratulations!🥳 You've successfully completed the user interface for the application.

In the upcoming section, you'll learn how to connect the application to a Firebase backend and effectively manipulate data within the application.


How to add Firebase authentication to a Next.js application

In this section, you'll learn how to implement multiple authentication methods using Firebase within your application.

Subscribers will have the option to subscribe or sign in using either a Google or GitHub account, while Admin users will be able to sign in using the Email and Password authentication method.

Setting up a Firebase project

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

Create a Firebase project and select the </> icon to create a new Firebase web app.

Firebase setup 1

Provide your app name and register it.

Firebase setup 2

Install the Firebase SDK in your Next.js project 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 setup 3

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

const firebaseConfig = {
    apiKey: "******",
    authDomain: "**********",
    projectId: "********",
    storageBucket: "******",
    messagingSenderId: "**********",
    appId: "********",
    measurementId: "********",
};
//👇🏻 gets the app config
const app =
    getApps().length === 0 ? initializeApp(firebaseConfig) : getApps()[0];
//👇🏻 creates a Firebase Firestore instance
const db = getFirestore(app);
//👇🏻 creates a Firebase Auth instance
const auth = getAuth(app);
//👇🏻 creates a Google & GitHub Auth instance
const googleProvider = new GoogleAuthProvider();
const githubProvider = new GithubAuthProvider();
//👇🏻 export them for use within the application
export { db, auth, googleProvider, githubProvider };
Enter fullscreen mode Exit fullscreen mode

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

To do this, navigate to the left-hand side panel, select Build, and then click on Authentication to add Firebase Authentication to your project.

Firebase setup 4

Lastly, enable the Google and Email/Password authentication methods.

Firebase setup 5

Add Google authentication to Next.js

First, create a util.ts file within the app folder and copy the provided code snippet into the file.

import { signInWithPopup } from "firebase/auth";
import { auth } from "../../firebase";

//👇🏻 Split full name into first name and last name
export const splitFullName = (fullName: string): [string, string] => {
    const [firstName, ...lastNamePart] = fullName.split(" ");
    return [firstName, lastNamePart.join(" ")];
};

//👇🏻 Handle Sign in with Google and Github
export const handleSignIn = (provider: any, authProvider: any) => {
    signInWithPopup(auth, provider)
        .then((result) => {
            const credential = authProvider.credentialFromResult(result);
            const token = credential?.accessToken;
            if (token) {
                const user = result.user;
                const [first_name, last_name] = splitFullName(user.displayName!);
                console.log([first_name, last_name, user.email]);
            }
        })
        .catch((error) => {
            const errorCode = error.code;
            const errorMessage = error.message;
            console.error({ errorCode, errorMessage });
            alert(`An error occurred, ${errorMessage}`);
        });
};
Enter fullscreen mode Exit fullscreen mode

The code snippet enables users to sign into the application via Google or GitHub authentication. It accepts authentication providers as parameters and logs the user's email, first name, and last name to the console after a successful authentication process.

Next, you can execute the handleSignIn function within the app/page.tsx file when a user clicks the Sign Up with Google or GitHub buttons.

import { GoogleAuthProvider, GithubAuthProvider } from "firebase/auth";
import { googleProvider, githubProvider } from "../../firebase";
import { handleSignIn } from "./util";

const handleGoogleSignIn = () => {
    setLoading(true);
    handleSignIn(googleProvider, GoogleAuthProvider);
};

const handleGithubSignIn = () => {
    setLoading(true);
    handleSignIn(githubProvider, GithubAuthProvider);
};
Enter fullscreen mode Exit fullscreen mode

Add GitHub authentication to Next.js

Before the GitHub authentication method can work as expected, you need to create a GitHub OAuth App.

Github 1

Once you've created the GitHub OAuth App, copy the client ID and client secret from the app settings. You'll need these credentials to activate the GitHub authentication method within the application.

Github 2

Go back to the Firebase Console. Enable the GitHub sign-in method and paste the client ID and secret into the input fields provided. Additionally, add the authorisation callback URL to your GitHub app.

Github 3

Congratulations! You've successfully added GitHub and Google authentication methods to the application. Next, let's add the Email/Password authentication for Admin users.

Add email and password authentication to Next.js

Before we proceed, ensure you have enabled the email/password sign-in method within the Firebase project. Then, execute the functions below when a user signs up and logs in as an admin user.

import {
    createUserWithEmailAndPassword,
    signInWithEmailAndPassword,
} from "firebase/auth";
import { auth } from "../../firebase";

//👇🏻 Admin Firebase Sign Up Function
export const adminSignUp = async (email: string, password: string) => {
    try {
        const userCredential = await createUserWithEmailAndPassword(
            auth,
            email,
            password
        );
        const user = userCredential.user;
        if (user) {
            //👉🏻 sign up successful
        }
    } catch (e) {
        console.error(e);
        alert("Encountered an error, please try again");
    }
};

//👇🏻 Admin Firebase Login Function
export const adminLogin = async (email: string, password: string) => {
    try {
        const userCredential = await signInWithEmailAndPassword(
            auth,
            email,
            password
        );
        const user = userCredential.user;
        if (user) {
            //👉🏻 log in successful
        }
    } catch (e) {
        console.error(e);
        alert("Encountered an error, please try again");
    }
};
Enter fullscreen mode Exit fullscreen mode

The provided code snippets include functions for admin user sign-up (adminSignUp) and login (adminLogin) using Firebase authentication. You can trigger these functions when a user signs up or logs in as an admin user.

You can sign users (admin and subscribers) out of the application using the code snippet below.

import { signOut } from "firebase/auth";
import { auth } from "../../firebase";

//👇🏻 Firebase Logout Function
export const adminLogOut = async () => {
    signOut(auth)
        .then(() => {
            //👉🏻 sign out successful
        })
        .catch((error) => {
            console.error({ error });
            alert("An error occurred, please try again");
        });
};
Enter fullscreen mode Exit fullscreen mode

Congratulations! You've completed the authentication process for the application. You can read through the concise documentation if you encounter any issues.


How to interact with Firebase Firestore

In this section, you'll learn how to save and retrieve data from Firebase Firestore by saving newsletters, subscribers, and admin users to the database.

Before we proceed, you need to add the Firebase Firestore to your Firebase project.

Firestore 1

Create the database in test mode, and pick your closest region.

Firestore 2

After creating your database, select Rules 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.

Firestore 3

Congratulations!🎉 Your Firestore database is ready.

Saving subscribers’ data to the database

After a user subscribes to the newsletter, you need to save their first name, last name, email, and user ID to Firebase. To do this, execute the saveToFirebase function after a subscriber successfully signs in via the GitHub or Google sign-in method.

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

export type SubscribersData = {
    firstName: string;
    lastName: string;
    email: string;
    id: string;
};

//👇🏻 Save the subscriber data to Firebase
const saveToFirebase = async (subscriberData: SubscribersData) => {
    try {
        //👇🏻 checks if the subscriber already exists
        const querySnapshot = await getDocs(collection(db, "subscribers"));
        querySnapshot.forEach((doc) => {
            const data = doc.data();
            if (data.email === subscriberData.email) {
                window.location.href = "/subscribe";
                return;
            }
        });
        //👇🏻 saves the subscriber details
        const docRef = await addDoc(collection(db, "subscribers"), subscriberData);
        if (docRef.id) {
            window.location.href = "/subscribe";
        }
    } catch (e) {
        console.error("Error adding document: ", e);
    }
};
Enter fullscreen mode Exit fullscreen mode

The saveToFirebase function validates if the user is not an existing subscriber to prevent duplicate entries before saving the subscriber's data to the database.

To differentiate between existing subscribers and admin users within the application, you can save admin users to the database.

Execute the provided function below when a user signs up as an admin.

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

//👇🏻 Add Admin to Firebase Database
const saveAdmin = async (email: string, uid: string) => {
    try {
        const docRef = await addDoc(collection(db, "admins"), { email, uid });
        if (docRef.id) {
            alert("Sign up successful!");
            window.location.href = "/admin/dashboard";
        }
    } catch (e) {
        console.error("Error adding document: ", e);
    }
};
Enter fullscreen mode Exit fullscreen mode

Next, update the admin log-in function to ensure that the user's data exists within the admin collection before granting access to the application.

import { auth, db } from "../../firebase";
import { addDoc, collection, getDocs, where, query } from "firebase/firestore";

//👇🏻 Admin Firebase Login Function
export const adminLogin = async (email: string, password: string) => {
    try {
        const userCredential = await signInWithEmailAndPassword(
            auth,
            email,
            password
        );
        const user = userCredential.user;
        //👇🏻 Email/Password sign in successful
        if (user) {
            //👇🏻 check if the user exists within the database
            const q = query(collection(db, "admins"), where("uid", "==", user.uid));
            const querySnapshot = await getDocs(q);
            const data = [];
            querySnapshot.forEach((doc) => {
                data.push(doc.data());
            });
            if (data.length) {
                window.location.href = "/admin/dashboard";
                alert("Log in successful!");
            }
        }
    } catch (e) {
        console.error(e);
        alert("Encountered an error, please try again");
    }
};
Enter fullscreen mode Exit fullscreen mode

To ensure that only authenticated users can access the Admin dashboard, Firebase allows you to retrieve the current user's data at any point within the application. This enables us to protect the Dashboard page from unauthorised access and listen to changes in the user's authentication state.

import { onAuthStateChanged } from "firebase/auth";
import { auth } from "../firebase";

export default function Dashboard() {
    const router = useRouter();

    useEffect(() => {
        onAuthStateChanged(auth, (user) => {
            if (!user) {
                //👉🏻 redirect user to log in form
                router.push("/");
            }
        });
    }, [router]);
}
Enter fullscreen mode Exit fullscreen mode

Congratulations on making it thus far! 🎉 In the upcoming sections, you'll learn how to create and send beautifully designed newsletters to subscribers using Novu Echo and React Email.

Top comments (4)

Collapse
 
yowise profile image
a.infosecflavour

Great tutorial!

Collapse
 
combarnea profile image
Tomer Barnea

@empe this looks amazing! Thanks!

Collapse
 
gal_tidhar_e93f7847d6ce6a profile image
Gal Tidhar

That's how devrel is done @empe awesome work :) super knowledgeable

Collapse
 
denis_kralj_68b59d1119484 profile image
Denis Kralj

Awesome guide with great level of detail!