Empowering Web3 UX through Push Protocol's Notification System
A Step-by-Step Guide to Implementing Web3 Notifications
Welcome to our step-by-step guide on integrating Push Protocol's notification system into your NextJS web application. In this blog, we'll cover the process of initializing a user, creating a notification channel, and implementing the APIs for sending and receiving notifications.
Initializing the User
Let's start by initializing the user with the following code snippet:
import { PushAPI, CONSTANTS } from "@pushprotocol/restapi";
const userAlice = await PushAPI.initialize(signer, {
env: CONSTANTS.ENV.STAGING,
});
This code seamlessly initializes the user, creating a Push user profile object. If the profile doesn't exist, it is promptly generated, laying the foundation for subsequent function calls within the Push network.
Create Channel API
Next, let's create a channel using the provided code:
// userAlice.channel.create({options})
const response = await userAlice.channel.create({
name: "Test Channel",
description: "Test Description",
icon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAz0lEQVR4AcXBsU0EQQyG0e+saWJ7oACiKYDMEZVs6GgSpC2BIhzRwAS0sgk9HKn3gpFOAv3v3V4/3+4U4Z1q5KTy42Ql940qvFONnFSGmCFmiN2+fj7uCBlihpgh1ngwcvKfwjuVIWaIGWKNB+GdauSk8uNkJfeNKryzYogZYoZY40m5b/wlQ8wQM8TayMlKeKcaOVkJ71QjJyuGmCFmiDUe+HFy4VyEd57hx0mV+0ZliBlihlgL71w4FyMnVXhnZeSkiu93qheuDDFDzBD7BcCyMAOfy204AAAAAElFTkSuQmCC",
url: "https://push.org",
});
This API is crucial for establishing a communication channel, allowing the system to send notifications. Pay attention to the parameters such as name, description, icon (base64 encoded), and URL, as they define the channel's characteristics.
NextJS Integration with TypeScript
To seamlessly integrate these functionalities into a NextJS frontend with TypeScript, we have a React component named Channel
. This component serves as a unified form for creating a channel. You can follow along with the provided code, adjusting the types based on your application needs.
// @ts-nocheck
import React, { useState, useEffect } from 'react';
import { PushAPI, CONSTANTS } from "@pushprotocol/restapi";
interface ChannelProps {
signer: any; // Adjust the type of signer based on your application
}
const Channel: React.FC<ChannelProps> = ({ signer }) => {
// State to hold form data
const [formData, setFormData] = useState({
name: '',
description: '',
url: '',
icon: ''
// delegateAddresses: null,
});
// State to added delegates
// const [addedDelegates, setAddedDelegates] = useState([]);
// Function to handle form input changes
const handleInputChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value,
});
};
// Function to handle form submission
const handleSubmit = async () => {
const push_signer = await PushAPI.initialize(signer, { env: CONSTANTS.ENV.STAGING });
try {
if (signer) {
const response = await push_signer.channel.create({
name: formData.name,
description: formData.description,
url: formData.url,
icon: formData.icon
});
console.log("Channel Created", response);
// Rest of your code...
} else {
console.error('Signer or signer.channel.create is undefined');
}
} catch (error) {
// Handle errors here
console.error(error);
}
};
// useEffect to log the form data whenever it changes (for debugging purposes)
useEffect(() => {
console.log('Form Data:', formData);
}, [formData]);
useEffect(() => { handleSubmit }, [signer]);
// useEffect to log the added delegates whenever it changes (for debugging purposes)
// useEffect(() => {
// console.log('Added Delegates:', addedDelegates);
// }, [addedDelegates]);
return (
<div className="flex flex-col items-center">
<label className="mb-2" htmlFor="name">
Channel Name:
</label>
<input
type="text"
id="name"
name="name"
value={formData.name}
onChange={handleInputChange}
className="border p-2 mb-4"
/>
<label className="mb-2" htmlFor="description">
Channel Description:
</label>
<textarea
id="description"
name="description"
value={formData.description}
onChange={handleInputChange}
className="border p-2 mb-4"
/>
<label className="mb-2" htmlFor="url">
URL
</label>
<textarea
id="url"
name="url"
value={formData.url}
onChange={handleInputChange}
className="border p-2 mb-4"
/>
<label className="mb-2" htmlFor="icon">
ICON
</label>
<textarea
id="icon"
name="icon"
value={formData.icon}
onChange={handleInputChange}
className="border p-2 mb-4"
/>
{/* <label className="mb-2" htmlFor="delegateAddresses">
Delegate Addresses (comma-separated):
</label>
<input
type="text"
id="delegateAddresses"
name="delegateAddresses"
value={formData.delegateAddresses}
onChange={handleInputChange}
className="border p-2 mb-4"
/> */}
<button
onClick={handleSubmit}
className="bg-blue-500 text-white p-2 rounded hover:bg-blue-700"
>
Save
</button>
</div>
);
};
export default Channel;
Sending Notifications
Now, let's explore the APIs for sending notifications:
// @ts-nocheck
import React, { useState } from 'react';
const NotificationSender = ({ onNotificationSent }) => {
const [notificationBody, setNotificationBody] = useState('');
const handleInputChange = (e) => {
setNotificationBody(e.target.value);
};
const handleSendNotification = async () => {
try {
const sendNotifRes = await userAlice.channel.send(["*"], {
notification: { title: "test", body: notificationBody },
});
onNotificationSent(sendNotifRes);
setNotificationBody('');
} catch (error) {
console.error(error);
}
};
return (
// Render the notification sending form
);
};
export default NotificationSender;
This component allows users to input a notification body and send it through the Push protocol. The onNotificationSent
callback is invoked to notify the parent component.
Viewing Notifications
Finally, let's implement the API for fetching notifications:
import React, { useState } from 'react';
const NotificationDisplay = ({ notification }) => {
const [shownNotification, setShownNotification] = useState(notification);
// useEffect to update shownNotification whenever a new notification is received
return (
// Render the notification display component
);
};
export default NotificationDisplay;
Surprise code
The provided code exemplifies a sophisticated notification system integrated seamlessly into a React application. Leveraging the capabilities of Push Protocol's notification system, this solution empowers users to receive real-time notifications in a Web3 environment.
The implementation involves initializing a user through the Ethereum provider, creating a channel, and establishing a connection to the notification stream. As the stream connects, a simulated notification is sent, showcasing the system's capability to capture, process, and display notifications. The progress information dynamically updates, providing users with valuable insights into the notification process.
Moreover, the code incorporates the UIWeb/NotificationItem component to elegantly render notifications. The NotificationInterface function encapsulates the user interface, offering a user-friendly experience by displaying wallet-specific notification items.
This solution serves as a powerful tool for developers seeking to enhance user engagement by integrating a robust and efficient notification system into their Web3 applications. With its comprehensive features, including real-time updates, streamlined UI rendering, and clear progress tracking, this code is poised to elevate the overall user experience in the Web3 ecosystem.
import { useState, useEffect, useRef } from "react";
import { ethers } from "ethers";
import { PushAPI, CONSTANTS } from "@pushprotocol/restapi";
import { NotificationItem, chainNameType } from "@pushprotocol/uiweb";
interface NotificationItemData {
cta: string;
title: string;
message: string;
app: string;
icon: string;
image: string;
url: string;
blockchain: string;
notification: string;
}
interface NotifItem extends NotificationItemData {
key: number;
}
export function Notification2(props: any) {
const [wallet, setWallet] = useState<string>(
"0xD8634C39BBFd4033c0d3289C4515275102423681"
);
const [progressTexts, setProgressTexts] = useState<string[]>([]);
const [notifItems, setNotifItems] = useState<NotifItem[]>([]);
const walletRef = useRef<HTMLInputElement | null>(null);
useEffect(() => {
if (walletRef.current) {
walletRef.current.value = wallet;
}
}, [wallet]);
const triggerNotification = async () => {
// Demo only supports MetaMask (or other browser based wallets) and gets provider that injects as window.ethereum into each page
const provider = new ethers.providers.Web3Provider(window.ethereum);
// Switch to sepolia
await provider.send("wallet_switchEthereumChain", [{ chainId: "0xAA36A7" }]);
// Get provider
await provider.send("eth_requestAccounts", []);
// Grabbing signer from provider
const signer = provider.getSigner();
// Initialize user for push
const userAlice = await PushAPI.initialize(signer, { env: CONSTANTS.ENV.STAGING });
// establish connection to stream
const stream = await userAlice.initStream([CONSTANTS.STREAM.CONNECT, CONSTANTS.STREAM.NOTIF]);
// Listen for stream connection
stream.on(CONSTANTS.STREAM.CONNECT, async (data) => {
console.log("STREAM CONNECTED");
let text = ['Stream Connected...', 'Sending Simulated Notification...', 'Wait for few moments for stream to capture notif and display...', 'Waiting for you to sign notification payload...'];
setProgressTexts(text);
await userAlice.channel.send([userAlice.account], {
notification: {
title: 'GM Builders!',
body: `_Simulated notification_ listened by stream and rendered with **@UIWeb/NotificationItem** with the latest timestamp - ${new Date().valueOf()} [timestamp: ${new Date().valueOf()}]`
},
payload: {
title: 'GM Builders!',
body: `_Simulated notification_ listened by stream and rendered with **@UIWeb/NotificationItem** with the latest timestamp - ${new Date().valueOf()} [timestamp: ${new Date().valueOf()}]`,
cta: 'https://push.org',
embed: 'https://push.org/assets/images/cover-image-8485332aa8d3f031e142a1180c71b341.webp',
}
});
text.push('Message generated and sent. Waiting for the stream to pick it up...');
setProgressTexts(text);
});
// Listen for notifications
stream.on(CONSTANTS.STREAM.NOTIF, (item) => {
let text = progressTexts;
console.log(item);
text.push('Notification Received...');
text.push(JSON.stringify(item));
setProgressTexts(text);
// create notification item compatible with UIWeb/NotificationItem
const compatibleNotifItem = {
title: item.message.payload.title,
message: item.message.payload.body,
image: item.message.payload.embed,
cta: item.message.payload.cta,
icon: item.channel.icon,
app: item.channel.name,
url: item.channel.url,
blockchain: item.source,
notification: item.message.notification,
};
setNotifItems([compatibleNotifItem]);
});
// connect stream
stream.connect();
};
function NotificationInterface() {
const inputStyle = {
padding: "10px",
margin: "10px 0",
width: "100%",
boxSizing: "border-box",
};
const textareaStyle = {
...inputStyle,
height: "100px",
resize: "vertical",
};
const buttonStyle = {
padding: "10px 20px",
backgroundColor: "#dd44b9",
color: "#FFF",
border: "none",
borderRadius: "5px",
cursor: "pointer",
marginTop: "20px",
};
return (
<div style={{ width: "auto", margin: "20px auto" }}>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div style={{ flex: 1 }}>
<h2>
Push based mechanism for displaying notifications on frontend
</h2>
<p />
<label>
Put any wallet address and click on fetch notifications to see the
live results. Click to expand <b>Live Editor</b> tab to see the
code and play with it. For this demo, You will need Metamask (or
equivalent browser injected wallet), you will also need to sign a
transaction to see the notifications.
</label>
<p />
</div>
</div>
<div>
<hr />
<h3>Progress (will show progress information once Trigger Notification is clicked)</h3>
{progressTexts.map((text, idx) => {
return (
<React.Fragment key={idx}>
<span>{text}</span>
<br />
</React.Fragment>
);
})}
</div>
<hr />
<button style={buttonStyle} onClick={triggerNotification}>
Trigger Notification
</button>
<p />
<p />
{notifItems.length > 0 ? (
<h3>{`Notification Items for ${wallet}`}</h3>
) : (
<></>
)}
{notifItems.map((notifItemSingular, idx) => {
const {
cta,
title,
message,
app,
icon,
image,
url,
blockchain,
notification,
} = notifItemSingular;
return (
<NotificationItem
key={idx} // any unique id
notificationTitle={title}
notificationBody={message}
cta={cta}
app={app}
icon={icon}
image={image}
url={url}
theme={"light"} // or can be dark
chainName={blockchain as chainNameType} // if using Typescript
/>
);
})}
</div>
);
}
return (
<>
<NotificationInterface />
</>
);
}
export default Notification2;
Conclusion
In conclusion, the incorporation of the Notification2 component results in a refined and structured presentation of received notifications, significantly augmenting the overall user experience. This component plays a pivotal role in streamlining the notification handling process within your NextJS web application.
To sum up, by adhering to this guide and seamlessly integrating the provided code snippets, you can empower your NextJS web application with a resilient notification system. This not only enriches your application's functionality but also ensures a seamless and engaging user experience within the dynamic landscape of Web3 environments.
Thank you for exploring the implementation of a powerful notification system in your NextJS web application through this blog. If you found the insights valuable, please consider sharing it with your network. For more tech-related content and updates, follow me on Hashnode. Your support is appreciated!
Cheers
Shaikh Rumman Fardeen
Push Protocol Ambassador
Follow me:
Linkedin : https://www.linkedin.com/in/srummanf
Github : https://github.com/srummanf
Twitter : https://twitter.com/srummanf