Tutorials

How to Create a Drag-and-Drop Kanban Board Using React, Node.js and Socket.io (& SuprSend Notifications)?

Sanjeev Kumar
June 20, 2023
TABLE OF CONTENTS

In this article, we explore how to make an agile Kanban board in JIRA using the dynamic duo of React and Node.js, coupled with the flexible data storage capabilities of MongoDB. Leveraging the power of these technologies, we'll delve into the process of designing and building a highly interactive and responsive Kanban board, allowing teams to seamlessly manage and monitor their projects.

To augment the board's functionality, we'll integrate Suprsend, a centralised single notification API to power up its notification system. By incorporating Suprsend, developers can take advantage of real-time notifications, enabling teams to stay informed about critical updates and changes on the Kanban board. This added layer of communication enhances collaboration and ensures that everyone involved is promptly notified of relevant events, resulting in improved productivity and seamless project management.

Github Repo: GitHub - SuprSend/KanBan

Getting Started

Creating the folder structure

 Create the project folder containing two sub-folders named client and server

Coding Block Example
Copied ✔

mkdir kanban
cd kanban
mkdir client server

Setting up the Client part

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

Coding Block Example
Copied ✔

cd client
npx create-react-app ./

Installing dependencies

Install Socket.io client API and React Router. React Router is a popular routing library for React applications. It allows us to handle navigation and routing in a declarative manner within the React components. 

Coding Block Example
Copied ✔
npm install socket.io-client react-router-dom

Cleaning up unnecessary data

Delete the redundant files such as the logo and the test files from the React app, and update the App.js file to display Hello World as below. Run npm start to verify the app is working fine.

Coding Block Example
Copied ✔

function App() {
   return (

Hello World!

); } export default App;

Adding styles

Add the following code which contains css for the app into index.css file

Coding Block Example
Copied ✔

* {
  font-family: "Space Grotesk", sans-serif;

  box-sizing: border-box;
}

a {
  text-decoration: none;
}

body {
  margin: 0;

  padding: 0;
}

.navbar {
  width: 100%;

  background-color: black;

  color: white;

  height: 10vh;

  border-bottom: 1px solid #ddd;

  display: flex;

  align-items: center;

  justify-content: center;

  padding: 20px;
}

.form__input {
  min-height: 20vh;

  display: flex;

  align-items: center;

  justify-content: center;
}

.input {

  border: 2px solid #008CBA;

  border-radius: 4px;

  background-color: #f8f8f8;

  color: #000;

  transition: 0.3s;

  box-sizing: border-box;

  outline: none;

  margin: 0 5px;

  width: 50%;

  padding: 10px 15px;
}
.input:focus {
  border-color: #66afe9;

  outline: none;
}

.addCardBtn {
  font-weight: 900;

  font-family: cursive;

  width: 120px;

  padding: 10px;

  cursor: pointer;

  background-color: #5050e9;

  color: #fff;

  border: none;

  outline: none;

  height: 43px;
}

.container {
  width: 100%;

  min-height: 100%;

  display: flex;

  align-items: center;

  justify-content: space-between;

  padding: 10px;
}

.head {
  border-radius: 10px;

  text-align: center;

  text-transform: capitalize;

  color: white;

  font-weight: 900;
}
.pending__head {
  background-color: rgb(183, 6, 6);
}
.completed__head {
  background-color: rgb(0, 104, 0);
}
.ongoing__head {
  background-color: rgb(255, 115, 0);
}

.completed__wrapper,
.ongoing__wrapper,
.pending__wrapper {
  width: 32%;

  min-height: 60vh;

  display: flex;

  flex-direction: column;

  padding: 5px;
}

.pending__container {
  background-color: rgba(183, 6, 6, 0.154);
}

.ongoing__container {
  background-color: rgba(255, 115, 0, 0.154);
}

.completed__container {
  background-color: rgb(0, 104, 0, 0.154);
}

.cardcontainer {
  width: 100%;

  min-height: 10vh;

  max-height: 40vh;

  overflow-y: scroll;

  display: flex;

  flex-direction: column;

  padding: 5px;

  margin-top: 5px;

  border-radius: 5px;
}

.pending__items,
.ongoing__items,
.completed__items {
  width: 100%;

  border-radius: 5px;

  margin-bottom: 10px;

  background-color: white;

  padding: 15px;
}

.comment {
  text-align: right;

  font-size: 14px;

  cursor: pointer;

  color: rgb(85, 85, 199);
}

.comment:hover {
  text-decoration: underline;
}

.comments__container {
  padding: 20px;
}

.comment__form {
  width: 100%;

  display: flex;

  align-items: center;

  justify-content: center;

  flex-direction: column;

  margin-bottom: 30px;
}

.comment__form > label {
  margin-bottom: 15px;
}

.comment__form textarea {
  width: 80%;

  padding: 15px;

  margin-bottom: 15px;
}

.commentBtn {
  padding: 10px;

  width: 200px;

  background-color: #367e18;

  outline: none;

  border: none;

  color: #fff;

  height: 45px;

  cursor: pointer;
}

.comments__section {
  width: 100%;

  display: flex;

  align-items: center;

  justify-content: center;

  flex-direction: column;
}

.login__form {
  width: 100%;

  height: 100vh;

  display: flex;

  flex-direction: column;

  align-items: center;

  justify-content: center;
}

.login__form > label {
  margin-bottom: 15px;
}

.login__form > input {
  width: 70%;

  padding: 10px 15px;

  margin-bottom: 15px;
}

.btn {
  padding: 15px;

  cursor: pointer;

  margin: 10px;

  font-size: 16px;

  outline: none;

  width: 200px;

  background-color: #4CAF50;

  border: 2px solid white;

  color: white;

  text-align: center;

  text-decoration: none;

  transition-duration: 0.4s;

  border-radius: 4px;
}

.btn:hover {
  background-color: white;

  color: #4CAF50;

  border: 2px solid #4CAF50;
}

Setting up the server

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

Coding Block Example
Copied ✔

cd server
npm init -y
    

Installing dependencies 

Install Express.js,Mongoose, dotenv, Mongodb Driver, CORS and Socket.io Server API.

Express.js 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.

Coding Block Example
Copied ✔
 npm install express cors socket.io
    

Index.js 

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

Coding Block Example
Copied ✔
touch index.js
    

Setting up the server

Set up a simple Node.js server using Express.js.

Coding Block Example
Copied ✔

const express = require("express");
const app = express();
const PORT = process.env.PORT || 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());
app.get("/home", (req, res) => {
	 res.json({
 	 message: "Hello world",
 });
});
app.listen(PORT, () => {
 console.log(`Server listening on ${PORT}`);
});  
    


Import the HTTP and the CORS library to allow data transfer between the client and the server domains.

Coding Block Example
Copied ✔

const express = require("express");
const app = express();
const PORT = process.env.PORT || 4000;
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// Newly added imports
const http = require("http").Server(app);
const cors = require("cors");
app.use(cors());
app.get("/home", (req, res) => {
res.json({
message: "Hello world",
	});
});
http.listen(PORT, () => {
console.log(`Server listening on ${PORT}`);
});
    

Creating schemas and models: 

Create Model using Mongoose in a file named models.js:

Coding Block Example
Copied ✔

const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: { type: String, required: true },
password: { type: String, required: true },
});
const User = mongoose.model('User', UserSchema);
module.exports = { User};
    


We can similarly create models for tasks,boards comments.

Adding Imports

Add Mongodb and Mongoose ODM and import models. Connect to the MongoDb Database. You can create a database for free on Mongodb. Make sure to keep the MONGODB_URI in .env instead of hardcoding it.

Coding Block Example
Copied ✔

		const express = require("express");
		const app = express();
		const cors = require("cors");
		const http = require("http").Server(app);
		const PORT = 4000;
		const mongoose = require("mongoose");
		const dotenv = require("dotenv");
		const { User } = require("./models");
		dotenv.config();

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

		mongoose
		.connect(process.env.MONGODB_URI, {
			useNewUrlParser: true,
			useUnifiedTopology: true,
		})
		.then(() => console.log("Database connected!"))
		.catch((err) => console.error(err));
		// Rest of the code
    

Adding socket.io

Next, add Socket.io to the project to create a real-time connection. Before the app.get()block, copy the code below.

Coding Block Example
Copied ✔

//New imports
.....
const socketIO = require('socket.io')(http, {
cors: {
	origin: "http://localhost:3000"
 	}
});

// Add this before the app.get() block

socketIO.on('connection', (socket) => {
console.log(`${socket.id} User connected!`);
socket.on('disconnect', () => {
		socket.disconnect()
	console.log(' User disconnected');
	});
});
    


From the code snippet above, the socket.io("connection") function establishes a connection with the React app, then creates a unique ID for each socket and logs the ID to the console whenever a user visits the web page.

When you refresh or close the web page, the socket fires the disconnect event showing that a user has disconnected from the socket.

OPTIONAL  

Install Nodemon. Nodemon is a Node.js tool that automatically restarts the server after detecting file changes, and Socket.io allows us to configure a real-time connection on the server.

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.

Coding Block Example
Copied ✔

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

    


You can now run the server with Nodemon by using the command below.

Coding Block Example
Copied ✔
npm start
    


If you are not using nodemon, either run the server with the command "node index.js" or modify the script by adding the start command to the list of scripts in the package.json file as shown below:

Coding Block Example
Copied ✔

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

Developing UI/UX with Kanban Board Features

Let's create the UI for the application. It will have three pages: the Login page, Task page – the main part of the application, and The Comments page – where users can comment on each task.

Navigate into client/src and create a components folder containing the Login.js, Task.js, and Comments.js files.

Coding Block Example
Copied ✔

		 cd client/src
		 mkdir components
		 cd components
		 touch Login.js Task.js Comments.js

    


Modify the App.js file to setup the routes.

Coding Block Example
Copied ✔

import { BrowserRouter, Route, Routes } from "react-router-dom";
import Comments from "./components/Comments";
import Task from "./components/Task";
import Login from "./components/Login";

function App() {
  return (
    
      
        } />
        } />
        } />
      
    
  );
}

export default App;
    

The Register Page

In this, User has to enter an email and a password. The details are stored in the database. After successful registration,an alert is displayed and user is redirected to login page:

Coding Block Example
Copied ✔

		import React, { useState } from "react";
		import { useNavigate } from "react-router-dom";

		const Register = () => {
		const [username, setUsername] = useState("");
		const [password, setPassword] = useState("");
		const navigate = useNavigate();
		const handleRegister = async (e) => {
			e.preventDefault();

			try {
			const response = await fetch("http://localhost:4000/register", {
				method: "POST",
				headers: {
				"Content-Type": "application/json",
				},
				body: JSON.stringify({ username, password }),
			});

			if (!response.ok) {
				throw new Error(`Error registering user: ${response.statusText}`);
			}
			const data = await response.json();
			console.log(data);
			alert("Registered Successfully");

			navigate("/login");
			} catch (error) {
			console.error(error);
			}
		};

		return (
			
setUsername(e.target.value)} value={username} /> setPassword(e.target.value)} // value={password} />
); }; export default Register;

The Login page

Here, the application accepts the username and password, verifies the details from database and if credentials supplied are correct, alert is displayed.

Coding Block Example
Copied ✔

import React, { useState } from "react";
import { useNavigate } from "react-router-dom";

const Login = () => {
  const [username, setUsername] = useState("");
  const [password, setPassword] = useState("");
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    const response = await fetch("http://localhost:4000/login", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ username, password }),
    });
    const data = await response.json();
    // console.log(data);
    if (response.ok) {
      console.log("Login successful:", data);
      localStorage.setItem('userId', data.userId);
      alert(data.message);
      navigate("/task");
    } else {
      console.log("Login failed:", data);
      alert(data.message);
    }
  };

  return (
    
setUsername(e.target.value)} value={username} /> setPassword(e.target.value)} // value={password} />
); }; export default Login;

Task Page

We'll divide the layout into 2 components namely: AddTask.js – the form input section, and TaskWindow.js – the section containing the tasks.

Coding Block Example
Copied ✔

cd src/components
touch Navig.js AddTask.js TaskWindow.js

    


Code for Task.js will look something like this

Coding Block Example
Copied ✔

		import React from "react";
		import AddTask from "./AddTask";
		import TaskWindow from "./TaskWindow";
		import socketIO from "socket.io-client";

		const socket = socketIO.connect("http://localhost:4000");
		const Task = () => {
		
		return (
			
); }; export default Task;


AddTask.js looks something like this:

Coding Block Example
Copied ✔

		import React, { useState } from "react";

		const AddTask = ({ socket }) => {
		const [task, setTask] = useState("");
		const handleAddTask = (e) => {
			e.preventDefault();
			
			setTask("");
		};

		return (
			
setTask(e.target.value)} />
); }; export default AddTask;


Code for TaskWindow is as follows

Coding Block Example
Copied ✔

		import React from "react";
		import { Link } from "react-router-dom";

		const TaskWindow = ({ socket }) => {
		return (
			

Pending Tasks

Debug the Notification center

2 Comments

Ongoing Tasks

Add Comment

Completed Tasks

Debug the Notification center

2 Comments

); }; export default TaskWindow;

Comments Page

Comments page will have some code like this:

Coding Block Example
Copied ✔

		import React, { useEffect, useState } from "react";
		import socketIO from "socket.io-client";
		import { useParams } from "react-router-dom";
		const socket = socketIO.connect("http://localhost:4000");

		const Comments = () => {
		const [comment, setComment] = useState("");
		const addComment = (e) => {
			e.preventDefault();
			console.log({
			comment,
			userId: localStorage.getItem("userId"),
			});
			setComment("");
		};
		return (
			

Existing Comments

); }; export default Comments;

Adding Drag and Drop feature:

React-beautiful-dnd (Drag and Drop) is a library developed by Atlassian that aims to provide a beautiful, accessible drag and drop experience for your React.js applications.

Drag and Drop functionality can greatly enhance the user experience by allowing users to physically move elements within your application, often for reordering lists or transferring items between lists.

Install React Beautiful DND and ensure you are not using React in strict mode. (Check src/index.js).

npm install react-beautiful-dnd


In server/index.js file,create an object containing all the dummy data for each task category.

Coding Block Example
Copied ✔

const fetchID = () => Math.random().toString(36).substring(1, 9);

		let tasks = {
		pending: {
			title: "pending",

			items: [
			{
				id: fetchID(),

				title: "Provide the proposed designs",

				comments: [],
			},
			],
		},

		ongoing: {
			title: "ongoing",

			items: [
			{
				id: fetchID(),

				title: "Refine and finalise the designs",

				comments: [
				{
					name: "John",

					text: "Verify designs for copyright issues",

					id: fetchID(),
				},
				],
			},
			],
		},

		completed: {
			title: "completed",

			items: [
			{
				id: fetchID(),

				title: "Create posters",

				comments: [
				{
					name: "Doe",

					text: "Check the dimensions",

					id: fetchID(),
				},
				],
			},
			],
		},
		};



		app.get("/home", (req, res) => {
		res.json(tasks);
		});

    


Now, let's fetch the tasks in TaskWindow.js file.

Coding Block Example
Copied ✔

		import React, { useState, useEffect } from "react";
		import { Link } from "react-router-dom";

		const TaskWindow = ({ socket }) => {
		const [tasks, setTasks] = useState({});
		useEffect(() => {
			function fetchTasks() {
			fetch("http://localhost:4000/home")
				.then((res) => res.json())

				.then((data) => {
				console.log(data);

				setTasks(data);
				});
			}

			fetchTasks();
		}, []);
		return (
			
{Object.entries(tasks).map((task) => (

{task[1].title} Tasks

{task[1].items.map((item, index) => (

{item.title}

{item.comments.length > 0 ? `View Comments` : "Add Comment"}

))}
))}
); }; export default TaskWindow;


The DragDropContext accepts a prop on DragEnd, which fires immediately after dragging an element.

Coding Block Example
Copied ✔

			const handleDragEnd = ({ destination, source }) => {
			if (!destination) return;

			if (
			destination.index === source.index &&
			destination.droppableId === source.droppableId
			)
			return;

			socket.emit("taskDragged", {
			source,

			destination,
			});
		};


	Now, create the taskDragged event's listener in server
		socketIO.on("connection", (socket) => {
		console.log(`${socket.id} user connected!`);
		socket.on("taskDragged", (data) => {
			console.log(data);
		});

		socket.on("disconnect", () => {
			socket.disconnect();
			console.log("User disconnected");
		});
		});
    


Now, create the taskDragged event's listener in server

Coding Block Example
Copied ✔

			const handleDragEnd = ({ destination, source }) => {
			if (!destination) return;

			if (
			destination.index === source.index &&
			destination.droppableId === source.droppableId
			)
			return;

			socket.emit("taskDragged", {
			source,

			destination,
			});
		};


	Now, create the taskDragged event's listener in server
		socketIO.on("connection", (socket) => {
		console.log(`${socket.id} user connected!`);
		socket.on("taskDragged", (data) => {
			console.log(data);
		});

		socket.on("disconnect", () => {
			socket.disconnect();
			console.log("User disconnected");
		});
		});

    


But there's a problem. The dragged item doesn't remain at its destination. So let's modify the code. The final code looks something like this:

Coding Block Example
Copied ✔

		import React, { useState, useEffect } from "react";
		import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
		import { Link } from "react-router-dom";

		const TaskWindow = ({ socket }) => {
		const [tasks, setTasks] = useState({});

		useEffect(() => {
			function fetchTasks() {
			// const userId = localStorage.getItem("userId");
			fetch(`http://localhost:4000/home`)
				.then((res) => res.json())
				.then((data) => {
				console.log("Fetched tasks:", data);
				setTasks(data);
				});
			}

			fetchTasks();
		}, []);

		useEffect(() => {
			socket.on("tasks", (data) => {
			console.log("Received updated tasks:", data);
			setTasks(data);
			});
		}, [socket]);


		const handleDragEnd = ({ destination, source }) => {
			if (!destination) return;
			if (
			destination.index === source.index &&
			destination.droppableId === source.droppableId
			) {
			return;
			}

			socket.emit("taskDragged", {
			source,
			destination,
			});
		};

		if (!tasks) {
			return 
Loading tasks...
; } return (
{Object.entries(tasks).map((task) => (

{task[1].title} tasks

{(provided) => (
{task[1].items.map((item, index) => ( {(provided) => (

{item.title}

{item.comments.length > 0 ? `View Comments` : "Add Comment"}

)}
))} {provided.placeholder}
)}
))}
); }; export default TaskWindow;

Adding a task

For adding a task, we will use the given code

Coding Block Example
Copied ✔

import React, { useState } from "react";

const AddTask = ({ socket }) => {
  const [task, setTask] = useState("");

  const handleAddTodo = (e) => {
    e.preventDefault();
    socket.emit("createTask", { task });
    setTask("");
  };

  return (
    
setTask(e.target.value)} />
); }; export default AddTask;


We'll have to include the listener for the same in server

Coding Block Example
Copied ✔

socketIO.on("connection", (socket) => {
  console.log(`${socket.id} user connected.`);

  socket.on("createTask", (data) => {
    const newTask = { id: fetchID(), title: data.task, comments: [] };
    tasks["pending"].items.push(newTask);
    socket.emit("tasks", tasks);
  });

  // ... Other listeners

});
    

Finalizing the Comments Page

Now we'll finish the comments page.

Coding Block Example
Copied ✔

import React, { useEffect, useState } from "react";
import socketIO from "socket.io-client";
import { useParams } from "react-router-dom";

const socket = socketIO.connect("http://localhost:4000");

const Comments = () => {
  const { category, id } = useParams();

  const [comment, setComment] = useState("");

  const addComment = (e) => {
    e.preventDefault();

    socket.emit("addComment", {
      comment,
      category,
      id,
      userId: localStorage.getItem("userId"),
    });

    setComment("");
  };

  return (
    

Existing Comments

); }; export default Comments;


Again, we'll add the listener in server.js

Coding Block Example
Copied ✔

socket.on("addComment", (data) => {
    const { category, userId, comment, id } = data;

    const taskItems = tasks[category].items;

    for (let i = 0; i < taskItems.length; i++) {
        if (taskItems[i].id === id) {
            taskItems[i].comments.push({
                name: userId,
                text: comment,
                id: fetchID(),
            });

            socket.emit("comments", taskItems[i].comments);
        }
    }
});
    


Also, we need to retreive all the comments which are already present.

Coding Block Example
Copied ✔

const Comments = () => {
  const { category, id } = useParams();
  const [comment, setComment] = useState("");
  // Add this:
  const [commentList, setCommentList] = useState([]);

  useEffect(() => {
    socket.on("comments", (data) => setCommentList(data));
  }, []);

  // ... after this

  return (
    
{/* ... rest of the code */}

Existing Comments

{/* Add the following code */} {commentList.map((comment) => (

{comment.text} by {comment.name}

))}
{/* ... rest of the code */}
); };


Finally add this code to load the comments when the page is loaded

Coding Block Example
Copied ✔

useEffect(() => {
socket.emit("fetchComments", { category, id });
}, [category, id]);
    


So finally Comments.js looks like this:

Coding Block Example
Copied ✔

import React, { useEffect, useState } from "react";
import socketIO from "socket.io-client";
import { useParams } from "react-router-dom";

const socket = socketIO.connect("http://localhost:4000");

const Comments = () => {
  const { category, id } = useParams();
  const [comment, setComment] = useState("");
  const [commentList, setCommentList] = useState([]);

  useEffect(() => {
    socket.emit("fetchComments", { category, id });
  }, [category, id]);

  useEffect(() => {
    socket.on("comments", (data) => setCommentList(data));
  }, []);

  const addComment = (e) => {
    e.preventDefault();

    socket.emit("addComment", {
      comment,
      category,
      id,
      userId: localStorage.getItem("userId"),
    });

    setComment("");
  };

  return (
    

Existing Comments

{commentList.map((comment) => (

{comment.text} by{" "} {comment.name}

))}
); }; export default Comments;


Finally, add the listener for the same in server.js:

Coding Block Example
Copied ✔

socket.on("fetchComments", (data) => {
  const { category, id } = data;
  const taskItems = tasks[category].items;
  for (let i = 0; i < taskItems.length; i++) {
    if (taskItems[i].id === id) {
      socket.emit("comments", taskItems[i].comments);
    }
  }
});
    

Implementing Suprsend Notifications:

SuprSend is a notification infrastructure as a service platform for easily creating, managing and delivering notifications to your end users. SuprSend has all the features set which enable you to send notifications in a reliable and scalable manner, as well as take care of end-user experience, thereby eliminating the need to build any notification service in-house.

Firstly create an account on Suprsend.

Install SuprSend Node.js SDK in the backend

Coding Block Example
Copied ✔

npm install @suprsend/node-sdk

# to upgrade to latest SDK version
npm install @suprsend/node-sdk@latest

    


Initialize the SDK in server.js

Coding Block Example
Copied ✔

const {Suprsend} = require("@suprsend/node-sdk");

		// Initialize SDK
		const supr_client = new Suprsend("WORKSPACE KEY", "WORKSPACE SECRET");
    


Replace WORKSPACE KEY and WORKSPACE SECRET with your workspace values. You will get both the tokens from Suprsend dashboard (Settings page -> "API keys" section)

Create a New User.  To create a new user or to update the profile of an existing user, you'll have to first instantiate the user object. Call supr_client.user.get_instance to instantiate user object.

Coding Block Example
Copied ✔

const distinct_id = "__uniq_user_id__"  // Unique id of user in your application.
const user = supr_client.user.get_instance(distinct_id) // Instantiate User profile
    


Add the User Channels. Use user.add_* method(s) to add user channels in a profile

Coding Block Example
Copied ✔

user.add_email(`${distinct_id}`);


/* For adding slack using email of user and access token of slack app,do this: */
    user.add_slack(
{

  "email": `${distinct_id}`,
    "Access_token": process.env.ACCESS_TOKEN
}
      )

/* OR to add the incoming webhook for sending notification to slack channel, do this: */

      user.add_slack(
        {
          "incoming_webhook": {
            "url": process.env.INCOMING_WEBHOOK
            
          }
        })
  // After setting the channel details on user-instance, call save()
		const response1 = user.save() //save() returns promise
    


When you call user.save(), the SDK internally makes an HTTP call to SuprSend Platform to register this request, and you'll immediately receive a response indicating the acceptance / failure status.

Coding Block Example
Copied ✔

// Response structure
{
	"success": true, // if true, request was accepted.
	"status": "success",
	"status_code": 202, // http status code
	"message": "OK"
}

{
	"success": false, // error will be present in message
	"status": "fail",
	"status_code": 500, // http status code
	"message": "error message"
}
    


NOTE
: For including slack notifications, you should use OAuth method to grant access to your slack workspace. A good way would be to use Slack Button(refer to docs : https://api.slack.com/docs/slack-button ) which gives incoming webhooks, slash commands and bot users wrapped in OAuth.

Create a workflow on SuprSend platform. For Event based workflow trigger, you'll have to create the workflow on SuprSend Platform.

Once the workflow is created, Add an import and then you can pass the Event name defined in workflow configuration with the help of supr_client.track_event method. Variables added in the template should be passed as event properties.

Coding Block Example
Copied ✔

const express = require("express");
const app = express();
const cors = require("cors");
const http = require("http").Server(app);
const PORT = 4000;
const mongoose = require("mongoose");
const dotenv = require("dotenv");
const { Suprsend } = require("@suprsend/node-sdk");
const { Event } = require("@suprsend/node-sdk");

// Rest of the code

const event = new Event(distinct_id, event_name, properties);
// Track event
const response = supr_client.track_event(event);
response.then((res) => console.log("response", res));
    

Event naming guidelines : 

When you create an Event or a property, please ensure that the Event Name or Property Name does not start with $ or ss_, as we have reserved these symbols for our internal events and property names.

When you call supr_client.track_event, the SDK internally makes an HTTP call to SuprSend Platform to register this request, and you'll immediately receive a response indicating the acceptance status.

Coding Block Example
Copied ✔

{
    "success": true, // if true, request was accepted.
    "status": "success",
    "status_code": 202,  // http status code
    "message": "Accepted",
}

{
    "success": false, // error will be present in message
    "status": "fail",
    "status": 400, // http status code
    "message": "error message",
}
    


The below image demonstrates a demo notification sent upon creating a task in Kanban Board to slack

You can access the project's Github repo from here: GitHub - SuprSend/KanBan

Wrapping Up

Throughout the tutorial, we covered the essential steps of designing and building a highly interactive and responsive Kanban board. From setting up the development environment to implementing the board's core features, developers now have a solid foundation to create their own customized Kanban boards tailored to their specific project requirements.

Additionally, by integrating Suprsend's notification capabilities, teams can ensure that all stakeholders stay informed about important updates and changes happening on the Kanban board in real-time. This not only fosters seamless communication but also enables swift decision-making and rapid response to project developments.

Written by:
Sanjeev Kumar
Engineering, SuprSend
ABOUT THE AUTHOR

What’s a Rich Text element?

The rich text element allows you to create and format headings, paragraphs, blockquotes, images, and video all in one place instead of having to add and format them individually. Just double-click and easily create content.

Static and dynamic content editing

A rich text element can be used with static or dynamic content. For static content, just drop it into any page and begin editing. For dynamic content, add a rich text field to any collection and then connect a rich text element to that field in the settings panel. Voila!

How to customize formatting for each rich text

Headings, paragraphs, blockquotes, figures, images, and figure captions can all be styled after a class is added to the rich text element using the "When inside of" nested selector system.

Implement a powerful stack for your notifications

By clicking “Accept All Cookies”, you agree to the storing of cookies on your device to enhance site navigation, analyze site usage, and assist in our marketing efforts. View our Privacy Policy for more information.