Now as continuing from part 2 ....
First we will create the context so that we can interact our ui with blockchain and installing the required packages.
cd blogDapp
mkdir context
yarn add ethers react-nice-avatar timeago-react react-icons
Now create a new file named config.js in root folder...And add the contract address which was deployed and the contract ABI from the artifacts folder in backend folder..
export const contractAddress = "Add your Address";
export const contractABI = ["Add_The_Abi"]
Next create a file insite context folder named context.js
import { createContext, useEffect, useState } from "react";
import { ethers } from "ethers";
import { contractABI, contractAddress } from "../config";
import { useRouter } from "next/router";
export const appContext = createContext();
export const AppProvider = ({ children }) => {
const [account, setAccount] = useState("");
const [loading, setLoading] = useState(false);
const [balance, setBalance] = useState(0);
const [posts, setPosts] = useState([]);
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [tag, setTag] = useState("");
const [currentPost, setCurrentPost] = useState([]);
const [commentContent, setCommentContent] = useState("");
const [comments, setComments] = useState([]);
const checkIfWalletIsConnected = async () => {
try {
if (!ethereum) {
console.log("Make sure you have metamask!");
return;
}
const accounts = await ethereum.request({ method: "eth_accounts" });
if (accounts.length !== 0) {
const account = accounts[0];
setAccount(account);
} else {
console.log("No authorized account found");
setAccount(null);
}
const provider = new ethers.providers.Web3Provider(ethereum);
const amount = await provider.getBalance(account);
setBalance(ethers.utils.formatEther(amount));
ethereum.on("accountsChanged", (accounts) => {
if (accounts.length !== 0) {
const account = accounts[0];
setAccount(account);
} else {
console.log("No authorized account found");
setAccount(null);
}
});
} catch (error) {}
};
const connectWallet = async () => {
try {
if (!ethereum) {
alert("Get MetaMask!");
return;
}
const accounts = await ethereum.request({
method: "eth_requestAccounts",
});
console.log("Connected", accounts[0]);
setAccount(accounts[0]);
} catch (error) {
console.log(error);
}
};
const createPosts = async () => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const transaction = await contract.createPost(title, content, tag);
setLoading(true);
await transaction.wait();
setLoading(false);
} catch (error) {
console.log(error);
}
};
const getPosts = async () => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const get = await contract.getPosts();
setPosts(get);
} catch (error) {
console.log(error);
}
};
const router = useRouter();
const likePost = async (id) => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const transaction = await contract.LikePost(id);
setLoading(true);
await transaction.wait();
setLoading(false);
router.push("/");
} catch (error) {
console.log(error);
}
};
const showPost = async (id) => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const get = await contract.getPost(id);
setCurrentPost(get);
} catch (error) {}
};
const writeComments = async (id) => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const tnx = await contract.writeComment(id, commentContent);
setLoading(true);
await tnx.wait();
setLoading(false);
} catch (error) {
console.log(error);
}
};
const readComments = async (id) => {
try {
const provider = new ethers.providers.Web3Provider(ethereum);
const signer = provider.getSigner();
const contract = new ethers.Contract(
contractAddress,
contractABI,
signer
);
const read = await contract.getPostComments(id);
setComments(read);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
checkIfWalletIsConnected();
getPosts();
});
return (
<appContext.Provider
value={{
connectWallet,
account,
balance,
createPosts,
setContent,
setTitle,
setTag,
posts,
likePost,
showPost,
currentPost,
loading,
readComments,
writeComments,
setCommentContent,
comments,
}}
>
{children}
</appContext.Provider>
);
};
Create the folder components in the root folder and then follow along:
Now adding the below codes to the required files
components/Navbar.js
import React, { useContext } from "react";
import { appContext } from "../context/context";
import { FaEthereum } from "react-icons/fa";
import { BiSearchAlt2 } from "react-icons/bi";
import { FaConnectdevelop } from "react-icons/fa";
import { useRouter } from "next/router";
const Navbar = () => {
const router = useRouter();
const { connectWallet, account, balance } = useContext(appContext);
return (
<header className={styles.wrapper}>
<div className={styles.container}>
<div className={styles.left}>
<div className={styles.logo} onClick={() => router.push("/")}>
DEV
</div>
<div className={styles.mid}>
<input
type="text"
placeholder="Search"
className={styles.input}
autoComplete="off"
autoCorrect="off"
spellCheck="false"
/>
<BiSearchAlt2 className="hidden md:inline-flex h-6 w-6" />
</div>
</div>
<div className={styles.right}>
<div className={styles.rightInner}>{balance} ETH</div>
<div>
{account ? (
<div className={styles.rightInner}>
<div>
<FaEthereum />
</div>
<div>
{account.slice(0, 8)}...{account.slice(-8, account.length)}
</div>
</div>
) : (
<div className={styles.rightInner} onClick={connectWallet}>
<FaConnectdevelop />
<div>Connect Wallet</div>
</div>
)}
</div>
</div>
</div>
</header>
);
};
export default Navbar;
const styles = {
wrapper: `bg-white shadow-md h-16 items-center py-2 sticky top-0 z-50`,
container: `flex items-center justify-between max-w-7xl mx-auto`,
left: `ml-3 flex space-x-3`,
rightInner: `bg-gray-100 p-2 rounded-md flex space-x-3 items-center cursor-pointer font-semibold`,
right: `flex items-center space-x-3 mr-3 hidden md:inline-flex`,
logo: `p-2 bg-black text-white font-bold text-lg rounded-md cursor-pointer`,
input: `bg-transparent ml-2 p-2 rounded-md `,
mid: `flex items-center space-x-3 bg-gray-100 px-2 rounded-md hidden md:inline-flex`,
};
components/Sidebar.js
import React from "react";
import { RiHomeHeartFill } from "react-icons/ri";
import { BiSearchAlt2 } from "react-icons/bi";
import { IoIosNotificationsOutline } from "react-icons/io";
import { BiMessageSquareDetail } from "react-icons/bi";
import { BsBookmarkStar } from "react-icons/bs";
import { IoListSharp } from "react-icons/io5";
import { BsPerson } from "react-icons/bs";
import { CgMoreO } from "react-icons/cg";
import { BsGithub } from "react-icons/bs";
import { BsTwitter } from "react-icons/bs";
import { BsLinkedin } from "react-icons/bs";
const Sidebar = () => {
return (
<div className={styles.wrapper}>
<div className={styles.container}>
<div className={`${styles.icon} text-green-500 font-extrabold`}>
<RiHomeHeartFill />
<div className={styles.name}>Home</div>
</div>
<div className={styles.icon}>
<BiSearchAlt2 />
<div className={styles.name}>Explore</div>
</div>
<div className={styles.icon}>
<IoIosNotificationsOutline />
<div className={styles.name}>Notifications</div>
</div>
<div className={styles.icon}>
<BiMessageSquareDetail />
<div className={styles.name}>Messages</div>
</div>
<div className={styles.icon}>
<BsBookmarkStar />
<div className={styles.name}>Bookmarks</div>
</div>
<div className={styles.icon}>
<IoListSharp />
<div className={styles.name}>Lists</div>
</div>
<div className={styles.icon}>
<BsPerson />
<div className={styles.name}>Profile</div>
</div>
<div className={styles.icon}>
<CgMoreO />
<div className={styles.name}>More</div>
</div>
</div>
<div className={styles.lower}>
<div className={styles.icon}>
<BsGithub />
</div>
<div className={styles.icon}>
<BsTwitter />
</div>
<div className={styles.icon}>
<BsLinkedin />
</div>
</div>
</div>
);
};
export default Sidebar;
const styles = {
wrapper: `mt-5`,
container: `flex flex-col gap-7 `,
icon: `text-2xl text-gray-700 h-6 space-x-2 flex items-center font-semibold hover:scale-110 hover:text-green-500 transition-all duration-300 cursor-pointer ease-in-out`,
name: `text-xl font-bold text-gray-700 hidden md:inline-flex`,
lower: `flex flex-row gap-7 mt-[330px] items-center hidden md:inline-flex`,
};
components/Survey.js
import React from "react";
import Image from "next/image";
import survey from "../public/survey.png";
const Survey = () => {
const Listing = [
{
name: "Listings",
seeAll: true,
data: [
{
title: "Sidekick Live Debugger is now Open Source",
description: "Product",
},
{
title: "Shakti Packers and Movers Chandigarh",
description: "Collab",
},
{
title: "Free Laravel & Tailwind CSS Dashboard Template",
description: "Forsale",
},
{
title:
"The Tailwind Site Creator, Design Tool, & Component Library 🎨",
description: "events",
},
{
title:
"Front-end Foxes Day 2022 - Free Virtual Conference - September 13",
description: "Product",
},
],
},
{
name: "#discuss",
seeAll: false,
data: [
{
title: "Sidekick Live Debugger is now Open Source",
description: "Product",
},
{
title: "Shakti Packers and Movers Chandigarh",
description: "Collab",
},
{
title: "Free Laravel & Tailwind CSS Dashboard Template",
description: "Forsale",
},
{
title:
"The Tailwind Site Creator, Design Tool, & Component Library 🎨",
description: "events",
},
{
title:
"Front-end Foxes Day 2022 - Free Virtual Conference - September 13",
description: "Product",
},
],
},
{
name: "#Product",
seeAll: false,
data: [
{
title: "Sidekick Live Debugger is now Open Source",
description: "Product",
},
{
title: "Shakti Packers and Movers Chandigarh",
description: "Collab",
},
{
title: "Free Laravel & Tailwind CSS Dashboard Template",
description: "Forsale",
},
{
title:
"The Tailwind Site Creator, Design Tool, & Component Library 🎨",
description: "events",
},
{
title:
"Front-end Foxes Day 2022 - Free Virtual Conference - September 13",
description: "Product",
},
],
},
];
return (
<div className={styles.wrapper}>
<div className={styles.parts}>
<Image src={survey} alt="image" height={600} width={800} />
<div>
<div className="text-xl font-bold">
Tell us your thoughts about DEV!
</div>
<p className="text-blue-500 font-semibold text-lg mt-2">
Take the DEV Satisfaction Survey
</p>
</div>
</div>
<div>
<div className="flex flex-col gap-5">
{Listing.map((item, index) => {
return (
<div className={styles.parts} key={index}>
<div className="flex justify-between w-full items-center ">
<div className="font-bold text-xl">{item.name}</div>
{item.seeAll && (
<div className="text-xl font-bold text-blue-500">
See All
</div>
)}
</div>
<section className="">
{item?.data?.map((data, index) => {
return (
<div
className="border-t-2 flex flex-col space-y-3 py-2"
key={index}
>
<div className="text-lg font-bold">{data.title}</div>
<div className="text-sm text-gray-500 pb-2">
{data.description}
</div>
</div>
);
})}
</section>
</div>
);
})}
</div>
</div>
</div>
);
};
export default Survey;
const styles = {
wrapper: `hidden lg:inline-flex flex flex-col my-3 max-w-[360px] gap-4 overflow-y-scroll h-[870px] `,
parts: `border border-gray-300 rounded-md py-6 px-4 bg-white flex flex-col space-y-4`,
};
components/Create.js
import { useRouter } from "next/router";
import React, { useContext } from "react";
import { appContext } from "../context/context";
const create = () => {
const router = useRouter();
const { createPosts, setContent, setTitle, setTag, loading } =
useContext(appContext);
return (
<div className="w-full mx-36 flex flex-col gap-8">
<div className="flex items-center justify-between w-full">
<div
className="font-bold text-xl ml-10 border-b-2 border-blue-500"
onClick={() => router.push("/")}
>
All Posts
</div>
<button
onClick={() => router.push("/createPost")}
className="text-xl font-semibold px-4 py-2 rounded-xl bg-white cursor-default hover:text-orange-500 hover:scale-95 ease-in-out duration-300 transition-all hidden lg:inline-flex"
>
Create Post
</button>
</div>
<div>
<div className="text-2xl font-bold text-blue-500 py-3">Title</div>
<input
className="rounded-md px-5 py-2 text-xl"
placeholder="Title"
onChange={(e) => setTitle(e.target.value)}
/>
<div className="text-2xl font-bold text-blue-500 py-3">Tag</div>
<input
className="rounded-md px-5 py-2 text-xl"
placeholder="Tags"
onChange={(e) => setTag(e.target.value)}
/>
<div className="text-2xl font-bold text-blue-500 py-3">Content</div>
<textarea
className="w-full h-full rounded-md px-5 py-2 text-xl"
placeholder="Start Typing...."
onChange={(e) => setContent(e.target.value)}
/>
</div>
{loading ? (
<div className="mt-[250px] w-1/4 text-xl font-bold px-4 py-2 rounded-xl bg-white cursor-default hover:text-orange-500 hover:scale-95 ease-in-out duration-300 transition-all">
Loading...
</div>
) : (
<button
onClick={createPosts}
className="mt-[250px] w-1/4 text-xl font-bold px-4 py-2 rounded-xl bg-white cursor-default hover:text-orange-500 hover:scale-95 ease-in-out duration-300 transition-all"
>
Post
</button>
)}
</div>
);
};
export default create;
components/Posts.js
import { useRouter } from "next/router";
import React, { useContext } from "react";
import { appContext } from "../context/context";
import Avatar, { genConfig } from "react-nice-avatar";
import TimeAgo from "timeago-react";
const Posts = () => {
const router = useRouter();
const { posts, likePost } = useContext(appContext);
const config = genConfig("hi@dapi.to");
const formatDate = (timestamp) => {
let date = new Date(timestamp * 1000);
let month = date.getMonth() + 1;
let dt = date.getDate();
let year = date.getFullYear();
let seconds = "0" + date.getSeconds();
let hours = date.getHours();
let minutes = "0" + date.getMinutes();
return (
year +
"-" +
month +
"-" +
dt +
" " +
hours +
":" +
minutes +
":" +
seconds
);
};
return (
<div className="w-full md:ml-16 ml-5 mr-2 mb-8 md:mr-4 overflow-y-scroll max-h-[780px] md:max-h-[870px] ">
<div className="flex items-center justify-between w-full">
<div
className="font-bold text-xl ml-10 border-b-2 cursor-pointer border-blue-500 hidden md:inline-flex"
onClick={() => router.push("/")}
>
All Posts
</div>
<button
onClick={() => router.push("/createPost")}
className="text-xl mr-5 font-semibold px-4 py-2 cursor-pointer rounded-xl bg-white hover:text-orange-500 hover:scale-95 ease-in-out duration-300 transition-all"
>
Create Post
</button>
</div>
<div className="my-5 space-y-4">
{posts.map((post) => (
<div
key={post.id}
className="flex flex-col gap-5 bg-white rounded-md px-4 py-5 cursor-pointer"
onClick={() =>
router.push({
pathname: "/showPost",
query: { id: `${post.id}` },
})
}
>
<div className="flex space-x-4 items-center text-lg font-normal md:flex-row flex-col">
<Avatar
className="w-16 h-16 border-2 border-emerald-500"
{...config}
/>
<div className="flex items-center justify-between w-full">
<div className="flex space-x-5 mt-5">
<div className="font-medium">
{post.author.slice(0, 8)}....
{post.author.slice(-8, post.author.length)}
</div>
<div className="text-blue-500 font-semibold hidden md:inline-flex">
{/* {formatDate(post.timestamp.toNumber())} */}
<TimeAgo
datetime={formatDate(post.timestamp.toNumber())}
locale="vi"
/>
</div>
</div>
<button
className="cursor-pointer items-center mt-5 "
onClick={() => likePost(post.id)}
>
<div>❤ {post.likeCount.toNumber()}</div>
</button>
</div>
</div>
<div className="text-md font-semibold ml-5 ">#{post.tag}</div>
<div className="mx-4 font-bold text-3xl">{post.title}</div>
<div className="mx-4 font-lg text-lg">
{post.content.length > 18 ? (
<div>{post.content.slice(0, 18)}......</div>
) : (
<div>{post.content}</div>
)}
</div>
</div>
))}
</div>
</div>
);
};
export default Posts;
Now creating the below files in pages folder:
pages/index.js
import React, { useContext } from "react";
import { appContext } from "../context/context";
import Navbar from "../components/Navbar";
import Sidebar from "../components/Sidebar";
import Posts from "../components/Posts";
import Survey from "../components/Survey";
const index = () => {
const {} = useContext(appContext);
return (
<div className={styles.wrapper}>
<Navbar />
<div className={styles.container}>
<div className={styles.main}>
<Sidebar />
<Posts />
<Survey />
</div>
</div>
</div>
);
};
export default index;
const styles = {
wrapper: `w-full h-screen bg-gray-100 overflow-hidden`,
container: `max-w-7xl mx-auto`,
main: `flex justify-between mx-3 my-2`,
};
pages/createPost.js
import React, { useContext } from "react";
import { appContext } from "../context/context";
import Navbar from "../components/Navbar";
import Sidebar from "../components/Sidebar";
import Create from "../components/Create";
const createPost = () => {
const {} = useContext(appContext);
return (
<div className={styles.wrapper}>
<Navbar />
<div className={styles.container}>
<div className={styles.main}>
<Sidebar />
<Create />
</div>
</div>
</div>
);
};
export default createPost;
const styles = {
wrapper: `w-full h-screen bg-gray-100 overflow-hidden `,
container: `max-w-7xl mx-auto`,
main: `flex justify-between mx-3 my-2`,
};
pages/showPost.js
import { useRouter } from "next/router";
import React, { useContext, useState, useEffect } from "react";
import { appContext } from "../context/context";
import Navbar from "../components/Navbar";
import Sidebar from "../components/Sidebar";
import Avatar, { genConfig } from "react-nice-avatar";
import TimeAgo from "timeago-react";
const showPost = () => {
const router = useRouter();
const {
posts,
likePost,
showPost,
currentPost,
readComments,
writeComments,
setCommentContent,
comments,
} = useContext(appContext);
const { id } = router.query;
const config = genConfig("hi@dapi.to");
const gettingPost = async (id) => {
showPost(id);
};
const gettingComments = async (id) => {
readComments(id);
};
const formatDate = (timestamp) => {
let date = new Date(timestamp * 1000);
let month = date.getMonth() + 1;
let dt = date.getDate();
let year = date.getFullYear();
let seconds = "0" + date.getSeconds();
let hours = date.getHours();
let minutes = "0" + date.getMinutes();
return (
year +
"-" +
month +
"-" +
dt +
" " +
hours +
":" +
minutes +
":" +
seconds
);
};
useEffect(() => {
gettingPost(id);
gettingComments(id);
});
return (
<div className={styles.wrapper}>
<Navbar />
<div className={styles.container}>
<div className={styles.main}>
<Sidebar />
<div className=" w-9/12 mx-2 pb-40 md:mx-5 my-8 space-y-5 overflow-y-scroll max-h-[840px]">
<div className="flex gap-3 items-center flex-col md:flex-row md:gap-8">
<Avatar
className="w-16 h-16 border-2 border-emerald-500"
{...config}
/>
<div className="font-semibold text-md">
Author:{" "}
{/* {currentPost.author.slice(0, 8)}....
{currentPost.author.slice(-8, currentPost.author.length)} */}
{currentPost.author}
</div>
<div className="font-semibold text-md text-emerald-500">
{/* {formatDate(currentPost.timestamp)} */}
<TimeAgo
datetime={formatDate(currentPost.timestamp)}
locale="vi"
/>
</div>
</div>
<div className="text-lg text-orange-800 ml-4">
#{currentPost.tag}
</div>
<div className="text-3xl font-extrabold my-5">
{currentPost.title}
</div>
<div className="text-md">{currentPost.content}</div>
<div>
{/* comments */}
<div className="mt-10 border-t-2 pt-10 mr-3 ">
<div className="flex space-x-4 items-center flex-col md:flex-row gap-8 md:gap-5">
<div className="text-lg font-semibold">Write Comments :</div>
<div className="flex space-x-5">
<input
className="rounded-md px-5 py-2 text-xl w-1/2 md:w-full"
placeholder="Add Comment...."
onChange={(e) => setCommentContent(e.target.value)}
/>
<button
className="text-xl font-bold px-4 py-2 rounded-xl bg-white cursor-default hover:text-orange-500 hover:scale-95 ease-in-out duration-300 transition-all"
onClick={() => writeComments(currentPost.id)}
>
Submit
</button>
</div>
</div>
<div className="mt-8">
{comments.map((comment, index) => (
<div className="border-b-2 py-8" key={index}>
<div>
<div className="flex items-center space-x-5 flex-col md:flex-row space-y-3 ">
<Avatar
className="w-8 h-8 border-2 border-emerald-500"
{...config}
/>
<div className="items-center flex font-semibold pb-3">
{comment.commentor.slice(0, 8)}...
{comment.commentor.slice(
-8,
comment.commentor.length
)}
</div>
<div className="pb-3 hidden md:inline-flex text-green-500 font-medium">
<TimeAgo
datetime={formatDate(comment.time)}
locale="vi"
/>
</div>
</div>
<div className="text-md mx-4 my-4">{comment.main}</div>
</div>
</div>
))}
</div>
</div>
{/* end comments */}
</div>
</div>
</div>
</div>
</div>
);
};
export default showPost;
const styles = {
wrapper: `w-full h-screen bg-gray-100 overflow-hidden `,
container: `max-w-7xl mx-auto `,
main: `flex justify-between mx-3 my-2`,
};
Now Finally run your app
yarn run dev
Thank you for following up throughout the tutorial...Hope you like the explanation....
Top comments (0)