πŸ”₯ 2 TRICKS πŸ”₯ to build a Meetup.com clone with React in 30 minutes πŸͺ„βœ¨

-2-tricks-to-build-a-meetup.com-clone-with-react-in-30-minutes-πŸͺ„

In this tutorial, you’ll learn how to build a Meetup.com clone we will touch:

  • Create and join online events
  • Add comments under an upcoming event
  • [TRICK 1]: Building an authentication / authorization in 5 minutes with SuperTokens
  • [TRICK 2]: Notify when someone joins and comment on their event in 10 minutes with Novu.

So Let’s meet up!

Meetup

Novu: Open-source notification infrastructure πŸš€

Just a quick background about us. Novu is an open-source notification infrastructure. We basically help to manage all the product notifications. It can be In-App (the bell icon like you have in the Dev Community – Websockets), Emails, SMSs and so on.

Like

Let’s set it up! πŸ”₯

Here, I’ll walk you through creating the project setup for the application. We’ll use React.js for the front end and Node.js for the backend server.

Create a folder for the web application as done below.

mkdir meetup-clone
cd meetup-clone
mkdir client server

Adding a Node.js server πŸ”Œ

Navigate into the server folder and create a package.json file.

cd server & npm init -y

Install Express, Nodemon, and the CORS library.

npm install express cors nodemon

ExpressJSΒ is a fast, minimalist framework that provides several features for building web applications in Node.js,Β CORSΒ is a Node.js package that allows communication between different domains, andΒ NodemonΒ is a Node.js tool that automatically restarts the server after detecting file changes.

Create an index.js file – the entry point to the web server.

touch index.js

Set up a Node.js server using ExpressJS. The code snippet below returns a JSON object when you visit the http://localhost:4000/api in your browser.

//πŸ‘‡πŸ»index.js
const express = require("express");
const cors = require("cors");
const app = express();
const PORT = 4000;

app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.use(cors());

app.get("/api", (req, res) => {
    res.json({
        message: "Hello world",
    });
});

app.listen(PORT, () => {
    console.log(`Server listening on ${PORT}`);
});

Configure Nodemon by adding the start command to the list of scripts in the package.json file. The code snippet below starts the server using Nodemon.

//πŸ‘‡πŸ» In server/package.json

"scripts": {
    "test": "echo "Error: no test specified" && exit 1",
    "start": "nodemon index.js"
  },

Congratulations!πŸŽ‰ You can now start the server by using the command below.

npm start

Setting up the React application βš’οΈ

Navigate into the client folder via your terminal and create a new React.js project withΒ Vite.

npm create vite@latest

InstallΒ React IconsΒ andΒ React RouterΒ – a JavaScript library that enables us to navigate between pages in a React application.

npm install react-router-dom react-icons

Delete the redundant files, such as the logo and the test files from the React app, and update the App.jsx file to display β€œHello World” as done below.

function App() {
    return (
        <div>
            <p>Hello World!p>
        div>
    );
}
export default App;

Copy theΒ CSS file required for styling the projectΒ into the src/index.css file.

Crafting the user interface πŸ§ͺ

Here, we’ll create the user interface for the Meetup clone to enable users to create and join events and view event details.

Create a pages folder within the client/src folder containing the CreateEvent.jsx, Dashboard.jsx, EventDetails.jsx, Events.jsx, EventsCategories.jsx, and Home.jsx.

cd client/src
mkdir pages
cd pages
touch Home.jsx Dashboard.jsx CreateEvent.jsx EventDetails.jsx Events.jsx EventsCategories.jsx
  • From the code snippet above,
    • The Home.jsx component is the application’s homepage where users can view all upcoming events.
    • The Dashboard.jsx component displays all the user’s events.
    • The CreateEvent.jsx component enables users to provide details about an event and create a new event.
    • The EventDetails.jsx component provides all the details about an event.
    • The Events.jsx and EventsCategories.jsx components are similar. The Events.jsx component displays all the available events, and the EventsCategories.jsx displays all the events under a particular category.

Next, create a components folder within the client/src folder containing the CategoriesSection.jsx, EventsSection.jsx, Footer.jsx, Hero.jsx, and Nav.jsx components. These components are the different sections of the homepage.

cd client/src
mkdir components
cd components
touch CategoriesSection.jsx EventsSection.jsx Footer.jsx Hero.jsx Nav.jsx

Update the App.jsx file to render the pages using React Router.

import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Dashboard from "./pages/Dashboard";
import Events from "./pages/Events";
import EventsCategory from "./pages/EventsCategory";
import CreateEvent from "./pages/CreateEvent";
import EventDetails from "./pages/EventDetails";

function App() {
    return (
        <Router>
            <Routes>
                <Route path='/' element={<Home />} />
                <Route path='/dashboard' element={<Dashboard />} />
                <Route path='/events/all' element={<Events />} />
                <Route path='/events/:category' element={<EventsCategory />} />
                <Route path='/create/event' element={<CreateEvent />} />
                <Route path='/event/:slug' element={<EventDetails />} />
            Routes>
        Router>
    );
}

export default App;

Home Page 🏠

The Home page displays all the available events within the application and enables users to navigate to other pages on the website. Copy the code below into the Home.jsx file.

import CategoriesSection from "../components/CategoriesSection";
import EventsSection from "../components/EventsSection";
import Footer from "../components/Footer";
import Hero from "../components/Hero";
import Nav from "../components/Nav";

const Home = () => {
    return (
        <div>
            <Nav />
            <Hero />
            <EventsSection />
            <CategoriesSection />
            <Footer />
        div>
    );
};

export default Home;

Home Page

Adding Events πŸ“•

These pages display all the events within the application or within a particular category.

import event from "../assets/event.jpeg";
import Nav from "../components/Nav";
import { AiOutlineCalendar } from "react-icons/ai";
import { BsCheckCircle } from "react-icons/bs";
import { ImLocation2 } from "react-icons/im";
import { Link } from "react-router-dom";

const Events = () => {
    return (
        <>
            <Nav />
            <div className='home_events' style={{ paddingTop: "20px" }}>
                <h1 style={{ fontSize: "30px", marginBottom: "20px" }}>All Eventsh1>

                <div className='body_events'>
                    <Link to={`/event/slug`} className='i_event'>
                        <img src={event} alt='Event' className='i_image' />
                        <div className='i_content'>
                            <h2 style={{ marginBottom: "10px" }}>Novu Community Callh2>
                            <p style={{ marginBottom: "10px", opacity: 0.7 }}>
                                Hosted by: Novu Development Team
                            p>
                            <div
                                style={{
                                    display: "flex",
                                    alignItems: "center",
                                    opacity: 0.7,
                                    marginBottom: "10px",
                                }}
                            >
                                <AiOutlineCalendar style={{ marginRight: "5px" }} />
                                <p>Starting at 8:00pmp>
                            div>
                            <div
                                style={{
                                    display: "flex",
                                    alignItems: "center",
                                    opacity: 0.7,
                                    marginBottom: "10px",
                                }}
                            >
                                <ImLocation2 style={{ marginRight: "5px", color: "red" }} />
                                <p>Online (Discord Channel)p>
                            div>
                            <div
                                style={{
                                    display: "flex",
                                    alignItems: "center",
                                    opacity: 0.7,
                                    marginBottom: "10px",
                                }}
                            >
                                <BsCheckCircle style={{ marginRight: "5px", color: "green" }} />
                                <p>12 goingp>
                            div>
                        div>
                    Link>
                div>
            div>
        
    );
};

export default Events;

The Create Event page

The Create Event page πŸ“…

This page displays a form field that accepts the details of the new event. Copy the code snippet below into the CreateEvent.jsx file.

import Nav from "../components/Nav";
import { useState } from "react";
import { postNewEvent } from "../utils/util";
import { useNavigate } from "react-router-dom";

const CreateEvent = () => {
    const [title, setTitle] = useState("");
    const navigate = useNavigate();
    const [location, setLocation] = useState("");
    const [category, setCategory] = useState("");
    const [description, setDescription] = useState("");
    const [startTime, setStartTime] = useState("");

    const handleSubmit = (e) => {
        e.preventDefault();
        console.log({ title, location, category, startTime, description });
        setTitle("");
        setLocation("");
        setCategory("");
        setDescription("");
        setStartTime("");
    };
    return (
        <div className='create_event'>
            <Nav />
            <div style={{ padding: "30px" }}>
                <h2
                    style={{
                        textAlign: "center",
                        marginBottom: "30px",
                        color: "#1d5d9b",
                    }}
                >
                    Create new event
                h2>
                <form className='create_form' onSubmit={handleSubmit}>
                    <label htmlFor='title'>Titlelabel>
                    <input
                        type='text'
                        name='title'
                        id='title'
                        value={title}
                        onChange={(e) => setTitle(e.target.value)}
                        required
                        className='event_title'
                    />

                    <label htmlFor='location'>Locationlabel>
                    <input
                        type='text'
                        name='location'
                        id='location'
                        value={location}
                        onChange={(e) => setLocation(e.target.value)}
                        className='event_title'
                        required
                    />

                    <div
                        style={{
                            width: "100%",
                            display: "flex",
                            alignItems: "center",
                            justifyContent: "space-between",
                        }}
                    >
                        <div
                            style={{
                                display: "flex",
                                flexDirection: "column",
                                width: "50%",
                                marginRight: "7px",
                            }}
                        >
                            <label htmlFor='startTime'>Starting Timelabel>
                            <input
                                type='time'
                                name='startTime'
                                id='startTime'
                                value={startTime}
                                onChange={(e) => setStartTime(e.target.value)}
                                className='event_title'
                                required
                            />
                        div>
                        <div
                            style={{ display: "flex", flexDirection: "column", width: "50%" }}
                        >
                            <label htmlFor='category'>Categorylabel>
                            <select
                                value={category}
                                onChange={(e) => setCategory(e.target.value)}
                                className='event_title'
                                required
                            >
                                <option value='travel-and-outdoor'>Travel and Outdooroption>
                                <option value='religion'>Religionoption>
                                <option value='sports-and-fitness'>Sports and Fitnessoption>
                                <option value='social-activities'>Social Activitiesoption>
                            select>
                        div>
                    div>

                    <label htmlFor='description'>Descriptionlabel>
                    <textarea
                        rows={8}
                        value={description}
                        onChange={(e) => setDescription(e.target.value)}
                        required
                    />
                    <button className='createEventBtn' type='submit'>
                        Create Event
                    button>
                form>
            div>
        div>
    );
};

export default CreateEvent;

The Dashboard page

The Dashboard page

The Dashboard page shows the user’s events and allows the user to create events.

import Nav from "../components/Nav";
import { Link } from "react-router-dom";

const Dashboard = () => {
    return (
        <div className='dashboard_container'>
            <Nav />
            <div className='dashboard_main'>
                <section className='header_events'>
                    <h1 style={{ fontSize: "30px" }}>Your Eventsh1>
                    <Link to='/create/event' className='link'>
                        Create new event
                    Link>
                section>
                <div>{/*--user's events*/}div>
            div>
        div>
    );
};

export default Dashboard;

The Event Details page

The Event Details page πŸ—“οΈ

This page displays information about an event, enables users to register for an event with just a click, and leave a comment about an event.

import Nav from "../components/Nav";
import event from "../assets/event.jpeg";
import { useState } from "react";
import { useParams } from "react-router-dom";

const EventDetails = () => {
    const [comment, setComment] = useState("");
    const { slug } = useParams();

    const addComment = (e) => {
        e.preventDefault();
        console.log(comment, slug);
    };
    return (
        <div>
            <Nav />
            <header className='details_header'>
                <h2 style={{ marginBottom: "15px" }}>Titleh2>
                <p style={{ opacity: 0.6 }}>
                    Hosted by: <span style={{ fontWeight: "bold" }}>Hostspan>
                p>
            header>
            <main className='details_main'>
                <div className='details_content'>
                    <img src={event} alt='Event' className='details_image' />
                    <div style={{ marginBottom: "30px" }}>Descriptiondiv>
                    <div style={{ padding: "30px 0" }}>
                        <h2 style={{ color: "#1d5d9b", marginBottom: "15px" }}>
                            Attendees
                        h2>
                        <p style={{ opacity: 0.6 }}>Attendeesp>
                    div>

                    <div className='comments'>
                        <h2 style={{ color: "#1d5d9b" }}>Commentsh2>
                        <form className='comment_form' onSubmit={addComment}>
                            <textarea
                                rows={4}
                                className='commentInput'
                                value={comment}
                                onChange={(e) => setComment(e.target.value)}
                                required
                            />
                            <button className='buttons commentBtn'>Commentbutton>
                        form>

                        <div className='comment_section'>
                            <div
                                style={{
                                    padding: "15px",
                                    border: "1px solid #ddd",
                                    borderRadius: "3px",
                                    marginBottom: "10px",
                                }}
                                key={comment.id}
                            >
                                <p style={{ color: "#1d5d9b", marginBottom: "3px" }}>@Userp>
                                <p style={{ opacity: 0.5 }}>Commentp>
                            div>
                        div>
                    div>
                div>
                <div className='details_cta'>
                    <p style={{ marginBottom: "10px", opacity: "0.6" }}>
                        Click here to register
                    p>
                    <button className='buttons registerBtn'>Registerbutton>
                div>
            main>
        div>
    );
};

export default EventDetails;

EventDetails

Authentication and Authorization πŸ”‘

SuperTokens is an open-source authentication service provider that enables you to add secure and seamless user authentication and session management to your software applications.

It also provides a prebuilt UI for various forms of authentication, such as email and password login, social login, and passwordless login. WithΒ SuperTokens, you can add authentication to both web and mobile applications in a few minutes.

Adding it to our app πŸŽ‰

Here, you’ll learn how to add authentication to your React and Node.js applications with SuperTokens. You can add SuperTokens automatically to your application or manually to an existing project.

To install SuperTokens automatically, run the code snippet below to install a starter app.

npx create-supertokens-app@latest --recipe=emailpassword

πŸ’‘ PS: The recipe flag represents the authentication method you want to set up with SuperTokens. You check theΒ documentation for a complete guide.

Configuring SuperTokens

Go to theΒ homepageΒ and create an account.

Head to your dashboard and fill the Get Started form accordingly.

Configuring SuperTokens

Next, switch to the Core Configuration Details menu tab to generate your Core connectionURI and Core API key.

connectionURI

Connect SuperTokens with React app πŸ“³

Install SuperTokens to the React app by running the code snippet below.

npm i -s supertokens-auth-react

Add the code snippet below to the App.jsx file.

import SuperTokens, { SuperTokensWrapper } from "supertokens-auth-react";
import { getSuperTokensRoutesForReactRouterDom } from "supertokens-auth-react/ui";
import { EmailPasswordPreBuiltUI } from "supertokens-auth-react/recipe/emailpassword/prebuiltui";
import * as reactRouterDom from "react-router-dom";
import EmailPassword from "supertokens-auth-react/recipe/emailpassword";
import Session from "supertokens-auth-react/recipe/session";
import { SessionAuth } from "supertokens-auth-react/recipe/session";

SuperTokens.init({
    appInfo: {
        appName: "meetup-clone",
        apiDomain: "http://localhost:4000",
        websiteDomain: "http://localhost:5173",
        apiBasePath: "/auth",
        websiteBasePath: "/",
    },
    recipeList: [EmailPassword.init(), Session.init()],
});

The code snippet above initialises SuperTokens with Email & Password within the React app.

Finally, update the routes as done below.

return (
    <SuperTokensWrapper>
        <Router>
            <Routes>
                {getSuperTokensRoutesForReactRouterDom(reactRouterDom, [
                    EmailPasswordPreBuiltUI,
                ])}
                <Route
                    path='/'
                    element={
                        <SessionAuth>
                            <Home />
                        SessionAuth>
                    }
                />
                <Route path='/' element={<Home />} />
                <Route
                    path='/dashboard'
                    element={
                        <SessionAuth>
                            <Dashboard />
                        SessionAuth>
                    }
                />
                <Route
                    path='/events/all'
                    element={
                        <SessionAuth>
                            <Events />
                        SessionAuth>
                    }
                />
                <Route
                    path='/events/:category'
                    element={
                        <SessionAuth>
                            <EventsCategory />
                        SessionAuth>
                    }
                />
                <Route
                    path='/create/event'
                    element={
                        <SessionAuth>
                            <CreateEvent />
                        SessionAuth>
                    }
                />
                <Route
                    path='/event/:slug'
                    element={
                        <SessionAuth>
                            <EventDetails />
                        SessionAuth>
                    }
                />
            Routes>
        Router>
    SuperTokensWrapper>
);

From the code snippet above, I wrapped all the routes with the component provided by SuperTokens to protect them from unauthenticated users until they sign into the application.

SessionAuth

Adding SuperTokens to the Node.js server

Install SuperTokens to the Node.js app by running the code below.

npm i -s supertokens-node

Add the code snippet below to initialise SuperTokens.

const supertokens = require("supertokens-node");
const Session = require("supertokens-node/recipe/session");
const EmailPassword = require("supertokens-node/recipe/emailpassword");
const { middleware } = require("supertokens-node/framework/express");
const { errorHandler } = require("supertokens-node/framework/express");

supertokens.init({
    framework: "express",
    supertokens: {
        connectionURI: "",
        apiKey: "",
    },
    appInfo: {
        appName: "meetup-clone",
        apiDomain: "http://localhost:4000",
        websiteDomain: "http://localhost:5173",
        apiBasePath: "/auth",
        websiteBasePath: "/",
    },
    recipeList: [EmailPassword.init(), Session.init()],
});

Update the server CORS as shown below.

app.use(
    cors({
        origin: "http://localhost:5173",
        allowedHeaders: ["content-type", ...supertokens.getAllCORSHeaders()],
        credentials: true,
    })
);

// IMPORTANT: CORS should be before the below line.
app.use(middleware());

Finally, add the error handler provided by SuperTokens.

// ...your API routes

// Add this AFTER all your routes
app.use(errorHandler());

Congratulations!πŸŽ‰ You’ve successfully added SuperTokens to the React and Node.js application. If you encounter any issues, feel free to followΒ the SuperTokens installation guide.

Communicating with the Node.js server

In this section, you’ll learn how to communicate with the Node.js server by retrieving and creating events within the application.

Before we begin, create a utils folder containing a util.js file within the React app.

cd client
mkdir utils
cd utils
touch util.js

Getting existing events

Create an events array within the index.js file on the server containing the event’s attributes.

const events = [
    {
        id: generateID(),
        title: "Novu Community Call",
        slug: "novu-community-call",
        host: "Novu Development Team",
        category: "social-activities",
        start_time: "8:00pm",
        location: "Online (Discord Channel)",
        comments: [
            { user: "nevodavid", id: generateID(), comment: "Can't wait!😍" },
            { user: "emil_pearce", id: generateID(), comment: "Let's go!πŸš€" },
        ],
        attendees: [
            "nevodavid",
            "emil_pearce",
            "tomer_barnea",
            "unicodeveloper",
            "scopsy",
        ],
        description:
            "Dear attendee,n We hope this message finds you well! We're excited to invite you to our upcoming Novu Community Call, where we will come together to share insights, updates, and engage in meaningful discussions. Your presence and contributions are highly valued as we continue to grow and strengthen our vibrant Novu community.",
    },
    {
        id: generateID(),
        title: "Novu Team Hangout",
        slug: "novu-team-hangout",
        host: "Novu Team",
        category: "social-activities",
        start_time: "12:30pm",
        location: "Online (Google Meet)",
        comments: [
            { user: "nevodavid", id: generateID(), comment: "Can't wait!😍" },
            { user: "emil_pearce", id: generateID(), comment: "Let's go!πŸš€" },
        ],
        attendees: ["nevodavid", "tomer_barnea", "unicodeveloper", "scopsy"],
        description:
            "Dear attendee,n We hope this message finds you well! We're excited to invite you to our upcoming Novu Community Call, where we will come together to share insights, updates, and engage in meaningful discussions. Your presence and contributions are highly valued as we continue to grow and strengthen our vibrant Novu community.",
    },
];

Add another endpoint that returns the events in a JSON format.

app.get("/events", (req, res) => {
    res.json({
        message: "Success!",
        events,
    });
});

Next, create a function within the utils/util.js file that sends a request to the endpoint from the React app.

export const fetchEvents = (setEvents) => {
    fetch("http://localhost:4000/events")
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                setEvents(data.events);
            }
        })
        .catch((err) => console.error(err));
};

Finally, execute the function when the Home component mounts.

const Home = () => {
    const [events, setEvents] = useState([]);
    //generates a random string as ID
    const generateID = () => Math.random().toString(36).substring(2, 10);

    useEffect(() => {
        fetchEvents(setEvents);
        //save a user_id property to the database
        if (!localStorage.getItem("user_id")) {
            localStorage.setItem("user_id", generateID());
        }
    }, []);

    return <div>{/*--render events from the server--*/}div>;
};

The code snippet above retrieves all the events from the server and creates a user_id property on the web browser to enable us to identify each user.

πŸ’‘Β PS: I’m using local storage because this is a small application. If you are using SuperTokens in a production environment, kindly check theΒ SuperTokens backend guide.

Getting events by categories

To fetch the events under a particular category, the /events/:category client route accepts the category name as part of its path name and sends it to the server to return all the events under the category.

<Route
    path='/events/:category'
    element={
        <SessionAuth>
            <EventsCategory />
        SessionAuth>
    }
/>

Add an endpoint on the server that retrieves the events based on their category name.

app.post("/event/category", (req, res) => {
    const { category } = req.body;
    const result = events.filter((e) => e.category === category);
    res.json({ message: "Success!", events: result });
});

Send a request to the endpoint on the server and display the events under a particular category.

export const fetchEventByCategory = (category, setEvents) => {
    fetch("http://localhost:4000/event/category", {
        method: "POST",
        body: JSON.stringify({ category }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                console.log(data.events);
                setEvents(data.events);
            }
        })
        .catch((err) => console.error(err));
};

Category

Retrieving event details via slug

To do this, add a POST route to the server that filters the events via the slug received from the React app.

app.post("/event/slug", (req, res) => {
    const { slug } = req.body;
    const result = events.filter((e) => e.slug === slug);
    res.json({ message: "Success!", event: result[0] });
});

The code snippet above uses the event’s slug to retrieve its entire event object from the server.

Create a function within the utils/util.js that sends a request to the endpoint on the server.

//πŸ‘‡πŸ»Within the util.js file
export const fetchEventBySlug = (slug, setEvent) => {
    fetch("http://localhost:4000/event/slug", {
        method: "POST",
        body: JSON.stringify({ slug }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                setEvent(data.event);
            }
        })
        .catch((err) => console.error(err));
};

Execute the function on component mount within the EventDetails component and display the properties accordingly.

import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams } from "react-router-dom"
import { useState, useEffect } from "react"
import { fetchEventBySlug } from "../utils/util"

export const EventDetails = () => {
    const [eventDetails, setEventDetails] = useState({});
    const { slug } = useParams();

    useEffect(() => {
        fetchEventBySlug(slug, setEventDetails);
        setLoading(false);
    }, [slug]);

    return <div>{/*--displays event details --*/}div>;
};

Creating new events

Creating new events

Create an endpoint that adds a new event to the events array.

//πŸ‘‡πŸ» generates slug from a text
const createSlug = (text) => {
    let slug = text
        .trim()
        .toLowerCase()
        .replace(/[^ws-]/g, "");
    slug = slug.replace(/s+/g, "-");
    return slug;
};
//πŸ‘‡πŸ» generates a random string as ID
const generateID = () => Math.random().toString(36).substring(2, 10);

//πŸ‘‡πŸ» endpoint for creating new events
app.post("/create/event", async (req, res) => {
    const { title, location, startTime, category, description, host } = req.body;

    const eventObject = {
        id: generateID(),
        title,
        slug: createSlug(title),
        host,
        category,
        start_time: startTime,
        location,
        comments: [],
        attendees: [],
        description,
    };
    events.unshift(eventObject);
    res.json({ message: "Event added successfully!βœ…" });
});

The code snippet above accepts the event attributes from the form within the CreateEvent component and adds the event to the events array when the user submits the form.

CreateEvent

Execute the function below when the user submits the form.

//πŸ‘‡πŸ» runs when a user submits the form
const handleSubmit = (e) => {
    e.preventDefault();
    postNewEvent(
        title,
        location,
        category,
        startTime,
        description,
        localStorage.getItem("user_id")
    );
};
//πŸ‘‡πŸ» makes a request to the server
const postNewEvent = () => {
    fetch("http://localhost:4000/create/event", {
        method: "POST",
        body: JSON.stringify({
            title,
            location,
            category,
            startTime,
            description,
            host,
        }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                alert(data.message);
                navigate("/dashboard");
            }
        })
        .catch((err) => console.error(err));
};

Adding comments to events

To add comments to an event, create another endpoint that accepts the user’s id, the event’s slug, and the comment from the React app.

app.post("/event/comment", async (req, res) => {
    const { comment, user, slug } = req.body;
    for (let i = 0; i < events.length; i++) {
        if (events[i].slug === slug) {
            events[i].comments.unshift({
                user,
                id: generateID(),
                comment,
            });

            return res.json({ message: "Comment added successfully!βœ…" });
        }
    }

The code snippet above retrieves the event with the same slug from the request and updates the comment property with the latest comment.

Add comment

Execute the function when a user drops a new comment.

const addComment = (e) => {
    e.preventDefault();
    postNewComment(comment, localStorage.getItem("user_id"), slug);
};

const postNewComment = (comment, user, slug) => {
    fetch("http://localhost:4000/event/comment", {
        method: "POST",
        body: JSON.stringify({ comment, user, slug }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                alert(data.message);
            }
        })
        .catch((err) => console.error(err));
};

Registering for an event

When a user registers for an event, the user’s id is added to the attendees property (array) on the particular event. Although, you need to check if the user hasn’t registered before you update the attendees property.

Add a POST route to the server that carries out the function.

app.post("/register/event", async (req, res) => {
    const { userID, eventID } = req.body;

    for (let i = 0; i < events.length; i++) {
        if (events[i].id === eventID) {
            const validate = events[i].attendees.filter((user) => user === userID);
            if (validate.length === 0) {
                events[i].attendees.push(user);

                return res.json({ message: "Registered successfully!βœ…" });
                }
            } else {
                return res.json({ message: "You cannot register twice ❌" });
            }
        }
    }
});

The code snippet above accepts the event and user id from the React app, filters the events array via the id, and updates the attendee’s list on the event with the matching id.

Execute the function below when a user clicks the Register button.

const eventRegister = (user, id) => {
    fetch("http://localhost:4000/register/event", {
        method: "POST",
        body: JSON.stringify({ user, id }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                alert(data.message);
                navigate("/");
            }
        })
        .catch((err) => console.error(err));
};

Retrieving users’ events

Within the Dashboard component, you can display all the events the user registered for.

First, create an endpoint that accepts the user’s id, loops through the entire event, and returns the user’s events.

app.post("/user/events", (req, res) => {
    const { userID } = req.body;
    let userEvents = [];
    for (let i = 0; i < events.length; i++) {
        let result = events[i].attendees.filter((user) => user === userID);
        if (result.length > 0) {
            userEvents.push(events[i]);
        }
    }
    res.json({ message: "Successful", events: userEvents });
});

Display the events returned from the server when the Dashboard page loads.

const fetchMyEvents = (userID, setEvents) => {
    fetch("http://localhost:4000/user/events", {
        method: "POST",
        body: JSON.stringify({ userID }),
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json",
        },
    })
        .then((res) => res.json())
        .then((data) => {
            if (data.message) {
                console.log(data);
                setEvents(data.events);
            }
        })
        .catch((err) => console.error(err));
};

Congratulations on making it thus far!πŸŽ‰ In the upcoming section, you’ll learn how to alert users when someone comments and registers for their events with Novu.

Trick 2: Adding notifications to your app ℹ️

Here, we need to notify the users when someone comments and registers for their events. Also, you can alert everyone when there is a new event.

To do this, we’ll useΒ NovuΒ – an open-source notification infrastructure that enables you to send in-app, SMS, chat, push, and e-mail notifications from a single dashboard.

Initiate it ⚑️

Navigate into the client folder and create a Novu project by running the code below.

cd client
npx novu init

Enter your application name and sign in to your Novu dashboard. The code snippet below contains the steps you should follow after running npx novu init.

Now let's setup your account and send your first notification
? What is your application name? Meetup Clone
? Now lets setup your environment. How would you like to proceed? Create a free cloud account (Recommended)
? Create your account with: Sign-in with GitHub
? I accept the Terms and Conditions (https://novu.co/terms) and have read the Privacy Policy (https://novu.co/privacy) Yes
βœ” Created your account successfully.

Visit the demo page, copy your subscriber ID from the page, and click the Skip Tutorial button.

Notification template

Create a notification template with a workflow as shown below:

different

πŸ’‘PS: You’ll need to create three different notification templates to be triggered when someone comments on an event, registers for an event, and creates a new event.

Novu DigestΒ allows you to control how you send notifications in your app. It collects multiple trigger events, schedules them, or sends them as a single message.

Update the In-App notification step to send this message to the event host when someone comments on their event.

{{user}} commented on your event.

Novu notification bell

Adding Novu notification bell to a React app

Novu in-app notification uses a notification bell to send alerts to users. Here, you’ll learn how to add it to your React applications.

Install the Novu Notification package.

npm install @novu/notification-center

Create aΒ Novu.jsxΒ file within the components folder and copy the below into the file.

import React from "react";
import {
    NovuProvider,
    PopoverNotificationCenter,
    NotificationBell,
} from "@novu/notification-center";
import { useNavigate } from "react-router-dom";

function Novu() {
    const navigate = useNavigate();
    const onNotificationClick = (notification) =>
        navigate(notification.cta.data.url);

    return (
        <>
            <NovuProvider
                subscriberId=''
                applicationIdentifier=''
            >
                <PopoverNotificationCenter
                    onNotificationClick={onNotificationClick}
                    colorScheme='light'
                >
                    {({ unseenCount }) => <NotificationBell unseenCount={unseenCount} />}
                PopoverNotificationCenter>
            NovuProvider>
        
    );
}

The code snippet above enables us to add Novu’s notification bell icon to the application. With this, you can view all the notifications within the app.

Select Settings on your Novu Admin Panel to copy your App ID and replace the subscriber’s ID placeholder with yours.

Import theΒ Novu.jsxΒ component into theΒ Nav.jsxcomponent.

import meetup from "../assets/meetup.png"
import { Link } from "react-router-dom"
import Novu from "./Novu"

const Nav = () => {
  return (
      <nav className='navbar'>
          <Link to="https://dev.to/">
              <img src={meetup} alt="Meetup" className="logo"/>
          Link>
          <div className="navBtn">
              <Novu />
              <button className="buttons signUpBtn">Log outbutton>
          div>
      nav>
  )
}

export default Nav

Configuring Novu on a Node.js server

Configuring Novu on a Node.js server

Install the Novu SDK for Node.js into the server folder.

npm install @novu/node

Import Novu from the package and create an instance using your API Key.

const { Novu } = require("@novu/node");
const novu = new Novu("");

Create a function within theΒ index.jsΒ file that sends notification to the event host via Novu.

const addCommentNotification = async (userID) => {
    await novu.subscribers.identify(userID, {
        firstName: "inAppSubscriber",
    });
    const response = await novu.trigger("notify", {
        to: {
            subscriberId: "",
        },
        payload: {
            user: userID,
        },
    });
    return response.data.data;
};

Execute the function when a user comments on an event.

app.post("/event/comment", async (req, res) => {
    const { comment, user, slug } = req.body;
    for (let i = 0; i < events.length; i++) {
        if (events[i].slug === slug) {
            events[i].comments.unshift({
                user,
                id: generateID(),
                comment,
            });
            //πŸ‘‡πŸ» sends notification via Novu
            const sendNotification = await addCommentNotification(user);
            if (sendNotification.acknowledged) {
                return res.json({ message: "Comment added successfully!βœ…" });
            }
        }
    }
});

Congratulations! You’ve completed the application.πŸŽ‰

Conclusion

So far, you’ve learnt how to authenticate users with SuperTokens, communicate between a React and Node.js app, and send in-app notifications using the Novu Digest.

NovuΒ enables you to create a rich notification system in your applications, thereby providing a great user experience for your users. You should also try outΒ SuperTokensΒ – it is easy to integrate and offers a seamless authentication process to your users.

The source code for this tutorial is available here:

https://github.com/novuhq/blog/tree/main/meetup-clone-react-supertokens-nodejs.

Thank you for reading!

Like

Total
0
Shares
Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Post
what-is-generative-ai-and-how-can-marketers-use-it-now?

What Is Generative AI and How Can Marketers Use It Now?

Next Post
how-to-implement-user-registration-and-email-verification-in-react

How to Implement User Registration and Email Verification in React

Related Posts