Hi there, welcome to the second part of a two-series tutorial on how to create a Fullstack dapp using web3 technologies some of which are:
- Tailwind Css for styling
- The graph for indexing data
- Next Js react framework to build our application
- Solidity for writing smart contracts
- Web3 . strorage for storing our data
- Ethers . js Library for interacting with the blockchain
If you have gone through the first part of this series and want to understand this much better you can get it at Part One of NFT AIR Tutorial
Its been a few weeks since I wrote the first part of this tutorial and I'm sorry about that π, but before starting this part (Frontend aspect) we need to know what we've done and ** what is to be done**;
NFT AIR Dapp Project
- Create a nextJs application β
- Install the necessary dependencies β
- Write our smart contract β
- Test the contract β
- Deploy the Contract β
- Create and connect your Project with your subgraph β
- Install more dependencies
- Getting assets (Pictures) used in the project
- Creating Pages, Components and writing Queries (i.e wallet connection, contract interaction, data retrieval from the graph ectera)
- Testing and Running.
- Create and Upload an art.
So far we are done with 6 out of 11 things to do to create our fullstack dapp project, and that's progress π€π.
It only remains 5 things to do to get our full project ready,
this π
Prerequisites
Like in the previous tutorial you should ensure that you have the following on your machine.
- Text Editor of your choice Installed on your machine (preferably Vs code )
- Have npm or yarn installed
- [Most Important] Have a glass of juice, coffee or bag of chips beside you, because this can get tiring and you should be able to snack and get your energy back ;)
Project BuidLing and Setup
Lets Begin! (Take a sip of that drink or a bite from that snack and lets get buidl-y π )
- Installing more dependencies We've installed quite a few dependencies in the last tutorial, but those pertain to the backend of the Dapp and now we will be installing more dependencies for the frontend aspect of this application. we'll be installing:
- Tailwind Css (Styling)
- web3 storage (Storing and retrieiving data)
- rainbow kit (wallet connection)
- wagmi (React hook for wallet connection)
-
axios
- Installing rainbow kit for wallet connection, wagmi a collection of react hooks for dealing with wallet connection, ethers for blockchain interaction from the frontend (If you already have it installed you can choose to remove it),
yarn add @rainbow-me/rainbowkit wagmi ethers axios urql graphql react-icons
- To use Web3 storage in our project we do not only need to install it dependency but also create an account [here] (https://web3.storage/) and then also create a token, this token is what will be used to upload files to web3 storage. Read more about the [docs] (https://web3.storage/docs/how-tos/generate-api-token/), and urql is used to fetch data from a subgraph.
To install web3 Storage
npm install web3.storage
yarn add web3.storage
After getting your token, you can add it to your constant.js file like this;
export const Token = "YOUR_TOKEN_HERE"
We also need our graph api that we'll be using to query data from our subgraph, and this is gotten from our subgraph page.
Go to the graph hosted service and go to My Dashboard, open up your NFT AIR Subgraph and get your api key from there.
The Api key is below the QUERIES (HTTP) heading copy that and put it in your constants file.
Now your constants file should have 3 constants;
- NFT AIR address
- Token from web3 storage.
- Api key from your subgragh
-To install Tailwind css
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
After installation you should follow the steps on how to continue here in their docs.
Getting Assets for the Dapp
I used some pngs in my dapp and to get that you can go to public folder in my repo and download them.
Creating pages, components and writing queries
Here we start showing UI for the code we have written so far, in form of pages on the website just like the preview below
We will be creating this pages:
- Register/Dashboard (Homepage)
- Feed (Shows list of all art uploaded)
- Starred page (shows starred art of a user)
- Creations page (Shows all your creation)
- Create page ( This is where we create the art and upload them)
- Components:
- Loader component
- Navbar component
We would start with the building of the components first;
- *The Loader component * This shows up when a new page is to loaded, or a current one reloaded. The code is short so you can decide not to make it a component and decide to implement it in every page.
First create a new folder in your root directory with components as its name, and a new file named loader.js with this code in it .
const Loader = () => {
return (
<div className="flex items-center justify-center h-screen bg-white ">
<img src="/loading.png" alt="loading..." />
</div>
)
}
export default Loader
- Navbar Component
This contains the various pages for our dapp and its responsive for both pc and mobile view
import Link from "next/link"
import {useState } from "react";
import { HiMenu } from "react-icons/hi";
import { BiX } from "react-icons/bi";
function Navbar () {
const [openMenu,setOpenMenu] = useState(false)
return (
<nav className='shadow-md fixed w-full z-50 '>
<div className='flex items-center bg-white h-24 rounded-b-lg w-full' >
<div className='flex items items-center w-full mx-5 md:mx-20 justify-between'>
<div className='flex items-center justify-center '>
<h1 className='font-bold text-2xl cursor-pointer'>
NFT <span className='text-green-400'> AIR</span>
</h1>
</div>
<div className='hidden md:block'>
<div className='ml-10 flex items-baseline space-x-8'>
<Link href="/" className=' cusor-pointer '>
<div className='no-underline font-semibold text-black-600 hover:text-orange-600 cursor-pointer'>
Home
</div>
</Link>
<Link href="/Feed" className=' cusor-pointer '>
<div className='no-underline font-semibold text-black-600 hover:text-orange-600 cursor-pointer'>
Feed
</div>
</Link>
<Link href="/starred" className=' cusor-pointer '>
<div className='no-underline font-semibold text-black-600 hover:text-orange-600 cursor-pointer'>
Starred
</div>
</Link>
<Link href="/creations" className=' cusor-pointer '>
<div className='no-underline font-semibold text-black-600 hover:text-orange-600 cursor-pointer'>
Creations
</div>
</Link>
<Link href="/create" className=' cusor-pointer '>
<button className='no-underline bg-green-500 py-2 px-3 rounded-lg font-bold text-teal-50 hover:bg-orange-500 cursor-pointer '>
Create NFT
</button>
</Link>
</div>
</div>
<div className='block md:hidden'>
<button onClick={()=> setOpenMenu(!openMenu)} className=' ml-10 cursor-pointer flex items-center bg-green-500 text-gray-50 hover:bg-orange-500 px-2 py-2 rounded-lg '>
{
!openMenu ?
(
<HiMenu className='font-bold text-2xl' />
)
:
(
<BiX className='font-bold text-2xl' />
)
}
</button>
</div>
</div>
</div>
<div className='md:hidden bg-white rounded-b-lg w-full ' >
<div className=' mx-20 justify-between'>
{
openMenu &&
<div className=' flex flex-column items-center space-y-8 py-3'>
<Link href="/" className=' cusor-pointer '>
<div className='no-underline font-semibold text-lg text-black-600 hover:text-orange-600 block cursor-pointer'>
Home
</div>
</Link>
<Link href="/Feed" className=' cusor-pointer '>
<div className='no-underline font-semibold text-lg text-black-600 hover:text-orange-600 cursor-pointer'>
Feed
</div>
</Link>
<Link href="/starred" className=' cusor-pointer '>
<div className='no-underline font-semibold text-lg text-black-600 hover:text-orange-600 cursor-pointer'>
Starred
</div>
</Link>
<Link href="/creations" className=' cusor-pointer '>
<div className='no-underline font-semibold text-lg text-black-600 hover:text-orange-600 cursor-pointer'>
Creations
</div>
</Link>
<Link href="/create" className=' cusor-pointer '>
<button className='no-underline bg-green-500 py-2 px-3 rounded-lg font-semibold text-lg text-teal-50 hover:bg-orange-500 cursor-pointer '>
Create NFT
</button>
</Link>
</div>
}
</div>
</div>
</nav>
)
}
export default Navbar
Now we are done with the components folder and files, now to get to the pages started.
Go to your pages folder and open _app.js and paste the following code inside :
import '../styles/globals.css'
import '@rainbow-me/rainbowkit/styles.css';
import {
apiProvider,
configureChains,
getDefaultWallets,
RainbowKitProvider,
} from '@rainbow-me/rainbowkit';
import { chain, createClient, WagmiProvider } from 'wagmi';
import Navbar from '../components/Navbar';
import "react-loader-spinner/dist/loader/css/react-spinner-loader.css";
import { useState } from 'react';
import Router from 'next/router';
import Loader from '../components/loader';
const { chains, provider } = configureChains(
[chain.mainnet, chain.polygon, chain.polygonMumbai],
[
apiProvider.alchemy(process.env.ANKR_ID),
apiProvider.fallback()
]
);
const { connectors } = getDefaultWallets({
appName: 'My RainbowKit App',
chains
});
const wagmiClient = createClient({
autoConnect: true,
connectors,
provider
})
function MyApp({ Component, pageProps }) {
const[loader,setLoader]= useState(false)
Router.events.on("routeChangeStart" , (url) => {
setLoader(true)
})
Router.events.on("routeChangeComplete" , (url) => {
setLoader(false)
})
return(
<div >
<WagmiProvider client={wagmiClient}>
<RainbowKitProvider
chains={chains}>
<div className=''>
{
loader ?
(
<Loader/>
)
:
(
<>
<Navbar/>
<div className='h-24 w-full'>
</div>
<Component {...pageProps} />
</>
)
}
</div>
</RainbowKitProvider>
</WagmiProvider>
</div>
)
}
export default MyApp
This is the main page and this is where other pages are initialized "_app.js"
Homepage
We will be using the index.js file under the pages folder as the homepage for our dapp.
import Head from 'next/head'
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useContract, useProvider,useSigner,useAccount,useBalance,useConnect } from 'wagmi'
import {MemeForestAddress,ApiUriv} from '../constant'
import { useEffect, useState } from "react";
import MEME from '../artifacts/contracts/MemeForest.sol/MemeForest.json'
import { createClient } from 'urql'
const MemberQuery= `
query {
memebers{
Name
Adddress
TotalMeme
StarredMemes
Date
}
}
`
const client = createClient({
url: ApiUriv,
})
export default function Home(props) {
const { data} = useAccount()
const person = data?.address;
const [name, setName] = useState("")
const [Address, setAddress] = useState("")
const [loading,setLoading] = useState(false)
const [AMember,setAMember] = useState(false)
const[loadingpage,setLoadingPage] = useState(false)
const provider = useProvider()
const { data: signer } = useSigner()
const contractWithSigner = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: signer,
})
const contractWithProvider = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: provider,
})
useEffect(() => {
PageLoad()
checkIfAMember(props);
}, []);
const PageLoad = async () =>{
try {
setLoadingPage(true)
const delay = ms => new Promise(res => setTimeout(res, ms));
await delay(7000);
setLoadingPage(false)
} catch (e) {
console.log(e)
}
}
const joinMembership = async () => {
try {
setLoading(true)
let _time = new Date().toLocaleString();
if(!name) {
alert("Name is not there")
}
const join = await contractWithSigner.CreateMembers(name,_time)
await join.wait()
setLoading(false)
setAMember(true)
checkIfAMember(props);
} catch (w) {
console.log(w)
}
}
const checkIfAMember = async (props) => {
try {
let data = props.members;
const addresses = ['']
const tx = await Promise.all(data.map(async i => {
addresses.push(i.Adddress)
return addresses
}));
const Address = person.toLowerCase()
setAddress(Address);
const isThere = addresses.includes(Address)
setAMember(isThere)
} catch (e) {
console.log(e)
setAMember(false)
}
}
const renderButton = () => {
if(!AMember) {
return (
<div className='flex items-center w-full h-full z-0'>
<div className=' flex flex-col-reverse md:flex-row items-center md:justify-between w-full h-full '>
<div className='flex flex-column items-center w-full basis-2/5 space-y-6 p-20 mr-4 mt-10'>
<div className='flex items-center text-3xl text-center text-black font-bold'>
<span>Welcome To NFT <span className='text-green-500'> Air </span> </span>
</div>
<div className='text-sm text-gray-400'>
Register to become a Member
</div>
<div className='pt-2 w-full'>
<input className='px-2 py-1 h-10 font-semibold text-sm w-full border rounded-xl ' placeholder='Enter your Name' onChange={e => setName(e.target.value)}/>
</div>
<div className='flex flex-col items-center justify-center w-full'>
{
loading ?
<button className='text-lg text-gray-50 flex items-center justify-center font-semibold w-full py-2 bg-white rounded-xl '>
<img src="/loader.png" alt="loading..." className='w-8 h-8 mt-2' />
</button>
:
<button className='text-lg text-gray-50 font-semibold w-full py-2 bg-green-500 hover:bg-gray-50 hover:text-green-500 border hover:border-slate-100 rounded-xl'
onClick={joinMembership}>
Register
</button>
}
<span className='text-sm text-gray-400 pt-1'> ------------OR------------</span>
<div className=' text-gray-50 text-xs pt-3 flex items-center justify-center'>
<ConnectButton />
</div>
</div>
</div>
<div className='w-full flex items-center justify-center md:justify-around basis-2/5 md:ml-4 md:mt-0'>
<img src='/main-removebg-preview.png' className='w-fit' />
</div>
</div>
</div>
)
}
if (AMember) {
return(
<div>
<div className='text-lg font-semibold w-full'>
{
props.members.map((lists,i) => {
return(
<div key={i} className='text-lg font-semibold'>
{
lists.Adddress == Address &&
<div className='flex flex-col w-full items-center justify-between space-y-20'>
<div className='shadow-sm w-full bg-green-400'>
<div className='flex flex-col items-center w-full'>
<div className='border border-slate-400 flex flex-col md:flex-row items-center w-full'>
<div className="w-full basis-1/5 p-4">
<div className='rounded-lg border border-red-400 h-full p-20' >
01
</div>
</div>
<div className="flex flex-col justify-between w-full basis-4/5 space-y-6 p-10 mr-4 ">
<div className=' flex items-center justify-between font-semibold hover:cursor-pointer '>
<span className='pb-2 pr-5 text-3xl text-white border-b border-white'>
{lists.Name}
</span>
<div className='text-sm'>
<ConnectButton />
</div>
</div>
<div className='flex justify-between flex-col md:flex-row space-y-6 md:space-y-6 text-lg font-thin hover:cursor-pointer text-white pt-2'>
<div className='flex items-end text-sm md:text-lg font-medium '>
{lists.Adddress}
</div>
<div className='flex flex-col text-lg '>
<span className='font-medium'> Date Joined</span>
<span className='font-normal'> {lists.Date}</span>
</div>
</div>
</div>
</div>
</div>
</div>
<div className='flex flex-col items-center w-full p-10'>
<div className='flex flex-col md:flex-row justify-between space-y-6 md:space-x-6 md:space-y-0 hover:cursor-pointer'>
<div className='flex flex-col items-center p-10 text-orange-500 hover:bg-gray-50 rounded-lg border-2 border-green-400'>
<span className='font-medium '> Number Of Starred Memes</span>
<span className='font-normal'> {lists.StarredMemes}</span>
</div>
<div className='flex flex-col items-center p-10 text-orange-500 hover:bg-gray-50 rounded-lg border-2 border-green-400'>
<span className='font-medium'> Number Of Total Memes</span>
<span className='font-normal'> {lists.TotalMeme}</span>
</div>
</div>
</div>
</div>
}
</div>
)
})
}
</div>
</div>
)
}
}
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="By Oleanji" />
<link rel="icon" href="/favicon.ico" />
</Head>
{renderButton()}
</div>
)
}
async function GetData() {
const data = await client.query(MemberQuery).toPromise()
return data.data.memebers
}
export async function getServerSideProps() {
const data = await GetData()
return{
props:{
members:data
}
}
}
The results
Here, we get users to register their address, and in doing so they become members of the dapp and they are able to create art and upload.
Feed
This shows to everyone regardless of your membership status, but you are only able to view and not react if you are not a member.
import Head from 'next/head'
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useContract, useProvider,useSigner,useAccount,useBalance,useConnect } from 'wagmi'
import {MemeForestAddress,Token, ApiUriv} from '../constant'
import { useEffect, useState } from "react";
import MEME from '../artifacts/contracts/MemeForest.sol/MemeForest.json'
import 'bootstrap/dist/css/bootstrap.css'
import axios from "axios"
import { createClient } from 'urql'
import { Web3Storage } from 'web3.storage'
const MemesQuery= `
query {
memes(
orderBy : id,
orderDirection: desc
)
{
id
MemeInfo
Owner
IsStarred
Stars
Likes
Date
FileType
IsDownloadable
StarredAddresses
LikesAddresses
}
}
`
const MemberQuery= `
query {
memebers{
Name
Adddress
TotalMeme
StarredMemes
Date
}
}
`
const client = createClient({
url: ApiUriv,
})
export default function Feed (props) {
const Memeslength = props.memes.length
const { data} = useAccount()
const person = data?.address;
const[loadingStar, setLoadingStar] = useState(false)
const[memberDetails,setMemberDetails] = useState([])
const[loadingLike, setLoadingLike] = useState(false)
const[loadingLikeId, setLoadingLikeId] = useState(0)
const[loadingStarId, setLoadingStarId] = useState(0)
const provider = useProvider()
const { data: signer} = useSigner()
const[loadingpage,setLoadingPage] = useState(false)
const contractWithSigner = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: signer,
})
const contractWithProvider = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: provider,
})
useEffect(() => {
PageLoad();
FechMemeInfo(props);
}, []);
const PageLoad = async () =>{
try {
setLoadingPage(true)
const delay = ms => new Promise(res => setTimeout(res, ms));
await delay(20000);
setLoadingPage(false)
} catch (e) {
console.log(e)
}
}
const StarMeme = async (id,bool) =>{
try {
setLoadingStar(true)
setLoadingStarId(id)
if (bool == true) {
const data= await contractWithSigner.RemoveStarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
else {
const data= await contractWithSigner.StarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
setLoadingStar(false)
} catch (e) {
console.log(e)
}
}
const download = (e,name) => {
try {
const Link = `https://${e}.ipfs.dweb.link/image`
axios({
url: Link, //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute("download", name+".png" );
document.body.appendChild(link);
link.click();
});
} catch (error) {
console.log(error)
}
};
const LikeMeme = async (id,bool) =>{
try {
setLoadingLike(true)
setLoadingLikeId(id)
if (bool == true) {
const data= await contractWithSigner.UnLikeMeme(id)
await data.wait()
await FechMemeInfo(props);
}
else {
const data= await contractWithSigner.LikeMeme(id)
await data.wait()
await FechMemeInfo(props);
}
setLoadingLike(false)
} catch (e) {
console.log(e)
}
}
function getAccessToken () {
return Token;
}
function makeStorageClient () {
return new Web3Storage({ token: getAccessToken() })
}
const FechMemeInfo = async (props) => {
const client = makeStorageClient()
let data = props.memes;
const tx = await Promise.all(data.map(async i => {
const res = await client.get(i.MemeInfo)
if(!res.ok) {
return;
}
const StarAnswer= signer ? await contractWithProvider.WhatDidIStar(i.id,person) : false;
const LikeAnswer= signer ? await contractWithProvider.WhatDidILike(i.id,person) : false;
let files = await res.files()
const info = await axios.get(`https://${files[0].cid}.ipfs.dweb.link`)
let List = {
Name:info.data.nameOfFile,
Description:info.data.DescriptionOfFile,
image:info.data.image,
Owner: i.Owner,
IsStarred:i.IsStarred,
NumberOfStars:i.Stars,
NumberOfLikes:i.Likes,
Date:i.Date,
Id :i.id,
IsDownloadable : i.IsDownloadable,
FileType :i.FileType,
DidMemberStarMe: StarAnswer,
DidMemberLikeMe:LikeAnswer
}
return List
}));
setMemberDetails(tx)
}
const renderButton = () => {
if(Memeslength == 0) {
return (
<div className='flex flex-row items-center justify-center' >
{
loadingpage ?
(
<div className='text-center text-8xl'>
<img src="/loading.png" alt="loading..." />
</div>
)
:
(
<h4 className='text-center'>
There are no Memes For Display
</h4>
)
}
</div>
)
}
if (Memeslength>0){
return(
<>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center' >
<div className='text-center text-8xl'>
<img src="/loading.png" alt="loading..." />
</div>
</div>
)
:
(
<div className='grid xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 px-5 py-2 ' >
{
memberDetails.map((card,i) => {
return(
<div key={i} className='w-full shadow-md p-3 rounded-3xl bg-gray-50 '>
{
<div className='flex flex-col' >
<div className='group flex flex-row items-center justify-center overflow-hidden rounded-lg ' >
<a href={card.File} target='_blank' rel="noreferrer" >
{
(card.FileType == "img/png") ?
(
<img src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." />
)
:
(
<video src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." width="500px" height="500px" controls="controls"/>
)
}
</a>
<div className=' hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-medium text-xs group-hover:inline absolute self-start ' >
{
props.members.map((lists,i) => {
return(
<div key={i} >
{
lists.Adddress == card.Owner &&
<div>
{lists.Name}
</div>
}
</div>
)
})
}
</div>
<div className='hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-thin text-xs group-hover:inline absolute self-end' >
{
card.Date
}
</div>
</div>
<div className='py-2 px-3 border-2 border-gray-500 h-auto mx-2 mt-4 rounded-lg' >
<div className='grid grid-rows grid-flow-col gap-1 ' >
{
card.Name.length > 7 ?
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-xs ' >
{card.Name}
</div>
) :
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-sm '>
{card.Name}
</div>
)
}
{
card.IsDownloadable ?
<div className='row-start-2 row-span-2 flex items-center justify-center rounded-lg shadow-md py-2 hover:shadow-xl transition ease ' >
<a href={`https://${card.image}.ipfs.dweb.link/image`} download target='_blank' rel="noreferrer" onClick={(e) =>download(card.image,card.Name)}>
<img src='./arrow.png' alt='' className='h-5 w-5 mt-1' />
</a>
</div>
:
<div className='row-start-2 h-10 row-span-2 flex items-center justify-center rounded-lg py-2 ' >
</div>
}
</div>
<div className='rounded-md mt-3 text-sm h-auto ' >
{card.Description}
</div>
<div className='flex flex-row justify-between space-x-1'>
<button className='rounded-md border-2 border-black flex mt-3 items-center justify-around h-8 w-24 hover:bg-[#FFFF00] 'onClick={() => StarMeme(card.Id, card.DidMemberStarMe)}>
{
((loadingStarId == card.Id) && loadingStar ) ?
(
<button className='bg-[#FFFF00] rounded-md flex items-center justify-around h-7 w-24'>
<h4>
<img src="/loader.png" alt="loading..." className='w-8 h-8 mt-2' />
</h4>
</button>
)
:
(
(card.DidMemberStarMe == true) ?
(
<>
<img src='./filledStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
:
(
<>
<img src='./strokeStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
)
}
</button>
<button className='rounded-md border-2 border-black flex mt-3 items-center justify-around h-8 w-24 hover:bg-[#ff0000] ' onClick={() => LikeMeme(card.Id, card.DidMemberLikeMe)}
>
{
((loadingLikeId == card.Id) && loadingLike) ?
(
<button className='rounded-md border-2 border-black flex items-center justify-around h-8 w-24 bg-[#FFFF00] ' >
<h4>
<img src='./filledStar.png' alt='STAR' className='w-5 h-5' />
</h4>
</button>
)
:
(
(card.DidMemberLikeMe == true) ?
(
<>
<img src='./filledLove.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfLikes}
</>
)
:
(
<>
<img src='./UnfilledLove.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfLikes}
</>
)
)
}
</button>
</div>
</div>
</div>
}
</div>
)
})
}
</div>
)
}
</>
)
}
}
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="By Oleanji" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className='flex flex-col space-y-6'>
<div className='flex flex-col items-end pt-3 px-2'>
<ConnectButton />
</div>
<div className=''>
{renderButton()}
</div>
</div>
</div>
)
}
async function GetData() {
const data = await client.query(MemesQuery).toPromise()
return (data.data.memes)
}
async function MemInfo() {
const info = await client.query(MemberQuery).toPromise()
return (info.data.memebers)
}
export async function getServerSideProps() {
const data = await GetData()
const info = await MemInfo()
return{
props:{
memes:data,
members:info
}
}
}
Result:
Starred Page
This page will contain the list of starred items of a particular member of the dapp (i.e the art you reacted with a star)
import Head from 'next/head'
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useContract, useProvider,useSigner,useAccount,useBalance,useConnect } from 'wagmi'
import {MemeForestAddress,Token, ApiUriv} from '../constant'
import { useEffect, useState } from "react";
import MEME from '../artifacts/contracts/MemeForest.sol/MemeForest.json'
import axios from "axios"
import { createClient } from 'urql'
import { Web3Storage } from 'web3.storage'
import { useRouter } from 'next/router';
const MemesQuery= `
query {
memes(
orderBy : id,
orderDirection: desc
)
{
id
MemeInfo
Owner
IsStarred
Stars
Likes
Date
FileType
IsDownloadable
StarredAddresses
LikesAddresses
}
}
`
const MemberQuery= `
query {
memebers{
Name
Adddress
TotalMeme
StarredMemes
Date
}
}
`
const client = createClient({
url: ApiUriv,
})
export default function Starred (props) {
const { data} = useAccount()
const person = data?.address;
const[loadingStarId, setLoadingStarId] = useState(0)
const[loadingStar, setLoadingStar] = useState(false)
const[DidIStarMeme, SetDidIStarMeme] =useState(false)
const [AMember,setAMember] = useState(false)
const[memeDetails,setMemeDetails] = useState([])
const[loadingpage,setLoadingPage] = useState(false)
const provider = useProvider()
const { data: signer} = useSigner()
const contractWithSigner = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: signer,
})
const contractWithProvider = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: provider,
});
const router = useRouter()
useEffect(() => {
PageLoad();
checkIfAMember(props);
StarredMemes(props);
}, []);
const PageLoad = async () =>{
try {
setLoadingPage(true)
const delay = ms => new Promise(res => setTimeout(res, ms));
await delay(7000);
setLoadingPage(false)
} catch (e) {
console.log(e)
}
}
const checkIfAMember = async (props) => {
try {
let data = props.members;
const addresses = ['']
const tx = await Promise.all(data.map(async i => {
addresses.push(i.Adddress)
return addresses
}));
const Address = person.toLowerCase()
const isThere = addresses.includes(Address)
setAMember(isThere)
} catch (e) {
console.log(e)
setAMember(false)
}
}
function getAccessToken () {
return Token;
}
function makeStorageClient () {
return new Web3Storage({ token: getAccessToken() })
}
const StarredMemes = async (props) => {
const client = makeStorageClient()
let data = props.memes;
const tx = await Promise.all(data.map(async i => {
const res = await client.get(i.MemeInfo)
if(!res.ok) {
return;
}
const StarredAddress = i.StarredAddresses;
for (let i = 0; i < StarredAddress.length; i++) {
const Address = person.toLowerCase()
const CurrentAddress = StarredAddress[i]
if(Address == CurrentAddress ){
SetDidIStarMeme(true)
}
else{
SetDidIStarMeme(false)
}
}
const StarAnswer= await contractWithProvider.WhatDidIStar(i.id,person);
let files = await res.files()
const info = await axios.get(`https://${files[0].cid}.ipfs.dweb.link`)
let List = {
Name:info.data.nameOfFile,
Description:info.data.DescriptionOfFile,
image:info.data.image,
Owner: i.Owner,
IsStarred:i.IsStarred,
NumberOfStars:i.Stars,
NumberOfLikes:i.Likes,
Date:i.Date,
Id :i.id,
IsDownloadable : i.IsDownloadable,
FileType :i.FileType,
DidMemberStarMe: StarAnswer,
}
return List
}));
setMemeDetails(tx)
}
const StarMeme = async (id,bool) =>{
try {
setLoadingStar(true)
setLoadingStarId(id)
if (bool == true) {
const data= await contractWithSigner.RemoveStarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
else {
const data= await contractWithSigner.StarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
setLoadingStar(false)
} catch (e) {
console.log(e)
}
}
const download = (e,name) => {
axios({
url: e, //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute("download", name+".png" );
document.body.appendChild(link);
link.click();
});
};
const gohome = () => {
router.push('/')
}
const create = () => {
router.push('/create')
}
const renderButton = () => {
if(!AMember){
return (
<div>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center text-8xl '>
<img src="/loading.png" alt="loading..." />
</div>
)
:
(
<div className='flex flex-col items-center justify-center space-y-5 '>
<div className='text-center font-bold text-lg '>
Go Back Home and Register before Seeing Starred Memes
</div>
<img src='/sad.png' className='w-1/6'/>
<button onClick={gohome} className='no-underline bg-green-500 py-2 px-3 rounded-lg font-bold text-teal-50 hover:bg-orange-500 cursor-pointer ' >
Home
</button>
</div>
)
}
</div>
)
}
if(AMember) {
if(memeDetails.length == 0)
{
return (
<div>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center text-8xl '>
<img src="/loading.png" alt="loading..." />
</div>
)
:
(
<div className='flex flex-col items-center justify-center space-y-5 ' >
<div className='text-center font-bold text-lg '>
You have No Starred Memes Go back to Create Memes
</div>
<img src='/sad.png' className='w-1/6'/>
<button onClick={create} className='no-underline bg-green-500 py-2 px-3 rounded-lg font-bold text-teal-50 hover:bg-orange-500 cursor-pointer '>
Create Meme
</button>
</div>
)
}
</div>
)
}
if(memeDetails.length > 0){
return(
<div className='flex flex-col ' >
<h3 className='text-center font-bold text-lg self-center'>
YOUR STARRED NFTS ART(S)
</h3>
<div className='grid xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 px-5 py-2 ' >
{
memeDetails.map((card,i) => {
return(
<>
{
(card.DidMemberStarMe == true)&&
<div key={i} className='w-full shadow-md p-3 rounded-3xl bg-gray-50 '>
<div className='flex flex-col' >
<div className='group flex flex-row items-center justify-center overflow-hidden rounded-lg ' >
<a href={card.File} target='_blank' rel="noreferrer" >
{
(card.FileType == "img/png") ?
(
<img src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." />
)
:
(
<video src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." width="500px" height="500px" controls="controls"/>
)
}
</a>
<div className=' hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-medium text-xs group-hover:inline absolute self-start ' >
{
props.members.map((lists,i) => {
return(
<div key={i} >
{
lists.Adddress == card.Owner &&
<div>
{lists.Name}
</div>
}
</div>
)
})
}
</div>
<div className='hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-thin text-xs group-hover:inline absolute self-end' >
{
card.Date
}
</div>
</div>
<div className='py-2 px-3 border-2 border-gray-500 h-auto mx-2 mt-4 rounded-lg' >
<div className='grid grid-rows grid-flow-col gap-1 ' >
{
card.Name.length > 7 ?
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-xs ' >
{card.Name}
</div>
) :
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-sm '>
{card.Name}
</div>
)
}
{
card.IsDownloadable ?
<div className='row-start-2 row-span-2 flex items-center justify-center rounded-lg shadow-md py-2 hover:shadow-xl transition ease ' >
<a href={`https://${card.image}.ipfs.dweb.link/image`} download target='_blank' rel="noreferrer" onClick={(e) =>download(card.image,card.Name)}>
<img src='./arrow.png' alt='' className='h-5 w-5 mt-1' />
</a>
</div>
:
<div className='row-start-2 h-10 row-span-2 flex items-center justify-center rounded-lg py-2 ' >
</div>
}
</div>
<div className='rounded-md mt-3 text-sm h-auto ' >
{card.Description}
</div>
<div className=''>
<button className='rounded-md border-2 border-black flex mt-3 items-center justify-around h-8 w-full hover:bg-[#FFFF00] 'onClick={() => StarMeme(card.Id, card.DidMemberStarMe)}>
{
((loadingStarId == card.Id) && loadingStar ) ?
(
<button className='bg-[#FFFF00] rounded-md flex items-center justify-around h-7 w-full'>
<h4>
<img src="/loader.png" alt="loading..." className='w-8 h-8 mt-2' />
</h4>
</button>
)
:
(
(card.DidMemberStarMe == true) ?
(
<>
<img src='./filledStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
:
(
<>
<img src='./strokeStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
)
}
</button>
</div>
</div>
</div>
</div>
}
</>
)
})
}
</div>
<div className='flex items-center justify-center space-x-3 py-3 w-full '>
<img src='/sad.png' className='w-[40px]'/>
<div className='px-5 text-center text-sm'>
Thats all of your Starred Nft Art(s)
</div>
</div>
</div>
)
}
}
}
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="By Oleanji" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className='flex flex-col space-y-6'>
<div className='flex flex-col items-end pt-3 px-2'>
<ConnectButton />
</div>
<div>
{renderButton()}
</div>
</div>
</div>
)
}
async function GetData() {
const data = await client.query(MemesQuery).toPromise()
return (data.data.memes)
}
async function MemInfo() {
const info = await client.query(MemberQuery).toPromise()
return (info.data.memebers)
}
export async function getServerSideProps() {
const data = await GetData()
const info = await MemInfo()
return{
props:{
memes:data,
members:info
}
}
}
This shows all your starred items, by checking:
- If you are a member
- how many items have you starred
Result:(if you haven't created any art)
Creations Page
This page shows the creations of a member of the dapp and shows the reactions of others on it.
import Head from 'next/head'
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useContract, useProvider,useSigner,useAccount,useBalance,useConnect } from 'wagmi'
import {MemeForestAddress,Token, ApiUriv} from '../constant'
import { useEffect, useState } from "react";
import MEME from '../artifacts/contracts/MemeForest.sol/MemeForest.json'
import 'bootstrap/dist/css/bootstrap.css'
import axios from "axios"
import { createClient } from 'urql'
import { Web3Storage } from 'web3.storage'
const MemesQuery= `
query {
memes(
orderBy : id,
orderDirection: desc
)
{
id
MemeInfo
Owner
IsStarred
Stars
Likes
Date
FileType
IsDownloadable
StarredAddresses
LikesAddresses
}
}
`
const MemberQuery= `
query {
memebers{
Name
Adddress
TotalMeme
StarredMemes
Date
}
}
`
const client = createClient({
url: ApiUriv,
})
export default function Feed (props) {
const Memeslength = props.memes.length
const Memberslength = props.members.length
const { data} = useAccount()
const person = data?.address;
const [Address, setAddress] = useState("")
const [memes,setMemes] = useState([])
const [aCreator, setIsACreator] = useState(false)
const[loadingStar, setLoadingStar] = useState(false)
const[memberDetails,setMemberDetails] = useState([])
const[loadingLike, setLoadingLike] = useState(false)
const[loadingLikeId, setLoadingLikeId] = useState(0)
const[loadingStarId, setLoadingStarId] = useState(0)
const provider = useProvider()
const { data: signer} = useSigner()
const[loadingpage,setLoadingPage] = useState(false)
const contractWithSigner = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: signer,
})
const contractWithProvider = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: provider,
})
useEffect(() => {
const Newperson = person?.toLocaleLowerCase()
setAddress(Newperson);
PageLoad();
FechMemeInfo(props);
}, []);
const PageLoad = async () =>{
try {
setLoadingPage(true)
const delay = ms => new Promise(res => setTimeout(res, ms));
await delay(20000);
setLoadingPage(false)
} catch (e) {
console.log(e)
}
}
const create = () => {
router.push('/create')
}
const StarMeme = async (id,bool) =>{
try {
setLoadingStar(true)
setLoadingStarId(id)
if (bool == true) {
const data= await contractWithSigner.RemoveStarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
else {
const data= await contractWithSigner.StarMeme(id)
await data.wait()
await FechMemeInfo(props);
}
setLoadingStar(false)
} catch (e) {
console.log(e)
}
}
const download = (e,name) => {
try {
const Link = `https://${e}.ipfs.dweb.link/image`
axios({
url: Link, //your url
method: 'GET',
responseType: 'blob', // important
}).then((response) => {
const url = window.URL.createObjectURL(new Blob([response.data]));
const link = document.createElement('a');
link.href = url;
link.setAttribute("download", name+".png" );
document.body.appendChild(link);
link.click();
});
} catch (error) {
console.log(error)
}
};
const LikeMeme = async (id,bool) =>{
try {
setLoadingLike(true)
setLoadingLikeId(id)
if (bool == true) {
const data= await contractWithSigner.UnLikeMeme(id)
await data.wait()
await FechMemeInfo(props);
}
else {
const data= await contractWithSigner.LikeMeme(id)
await data.wait()
await FechMemeInfo(props);
}
setLoadingLike(false)
} catch (e) {
console.log(e)
}
}
function getAccessToken () {
return Token;
}
function makeStorageClient () {
return new Web3Storage({ token: getAccessToken() })
}
const FechMemeInfo = async (props) => {
const client = makeStorageClient()
let data = props.memes;
const tx = await Promise.all(data.map(async i => {
const res = await client.get(i.MemeInfo)
if(!res.ok) {
return;
}
const StarAnswer= await contractWithProvider.WhatDidIStar(i.id,person);
const LikeAnswer= await contractWithProvider.WhatDidILike(i.id,person);
let files = await res.files()
const info = await axios.get(`https://${files[0].cid}.ipfs.dweb.link`)
let List = {
Name:info.data.nameOfFile,
Description:info.data.DescriptionOfFile,
image:info.data.image,
Owner: i.Owner,
IsStarred:i.IsStarred,
NumberOfStars:i.Stars,
NumberOfLikes:i.Likes,
Date:i.Date,
Id :i.id,
IsDownloadable : i.IsDownloadable,
FileType :i.FileType,
DidMemberStarMe: StarAnswer,
DidMemberLikeMe:LikeAnswer
}
return List
}));
setMemberDetails(tx);
}
const renderButton = () => {
if(Memeslength == 0) {
return (
<div className='flex flex-row items-center justify-center' >
{
loadingpage ?
(
<div className='text-center text-8xl'>
<img src="/loading.png" alt="loading..." />
</div>
)
:
(
<>
<img src='/sad.png' className='w-1/6'/>
<h4>
There are no Memes For Display
</h4>
</>
)
}
</div>
)
}
if (Memeslength>0){
return(
<>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center' >
<div className='text-center text-8xl'>
<img src="/loading.png" alt="loading..." />
</div>
</div>
)
:
(
<div className='flex flex-col ' >
<h3 className='text-center font-bold text-lg self-center'>
YOUR CREATED NFT ART(S)
</h3>
<div className='grid xs:grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4 px-5 py-2 ' >
{
memberDetails.map((card,i) => {
return(
<>
<div key={i} >
{
(card.Owner == Address) &&
<div className='w-full shadow-md p-3 rounded-3xl bg-gray-50 '>
<div className='flex flex-col' >
<div className='group flex flex-row items-center justify-center overflow-hidden rounded-lg ' >
<a href={card.File} target='_blank' rel="noreferrer" >
{
(card.FileType == "img/png") ?
(
<img src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." />
)
:
(
<video src={`https://${card.image}.ipfs.dweb.link/image`} className='w-full rounded-lg h-36 group-hover:scale-150 transition ease duration-300' alt="..." width="500px" height="500px" controls="controls"/>
)
}
</a>
<div className=' hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-medium text-xs group-hover:inline absolute self-start ' >
{
props.members.map((lists,i) => {
return(
<div key={i} >
{
lists.Adddress == card.Owner &&
<div>
{lists.Name}
</div>
}
</div>
)
})
}
</div>
<div className='hidden p-1 rounded-lg bg-gray-700 text-gray-100 font-thin text-xs group-hover:inline absolute self-end' >
{
card.Date
}
</div>
</div>
<div className='py-2 px-3 border-2 border-gray-500 h-auto mx-2 mt-4 rounded-lg' >
<div className='grid grid-rows grid-flow-col gap-1 ' >
{
card.Name.length > 7 ?
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-xs ' >
{card.Name}
</div>
) :
(
<div className='flex items-end row-start-2 row-span-2 rounded-lg font-black text-sm '>
{card.Name}
</div>
)
}
{
card.IsDownloadable ?
<div className='row-start-2 row-span-2 flex items-center justify-center rounded-lg shadow-md py-2 hover:shadow-xl transition ease ' >
<a href={`https://${card.image}.ipfs.dweb.link/image`} download target='_blank' rel="noreferrer" onClick={(e) =>download(card.image,card.Name)}>
<img src='./arrow.png' alt='' className='h-5 w-5 mt-1' />
</a>
</div>
:
<div className='row-start-2 h-10 row-span-2 flex items-center justify-center rounded-lg py-2 ' >
</div>
}
</div>
<div className='rounded-md mt-3 text-sm h-auto ' >
{card.Description}
</div>
<div className='flex flex-row justify-between space-x-1'>
<button className='rounded-md border-2 border-black flex mt-3 items-center justify-around h-8 w-24 hover:bg-[#FFFF00] 'onClick={() => StarMeme(card.Id, card.DidMemberStarMe)}>
{
((loadingStarId == card.Id) && loadingStar ) ?
(
<button className='bg-[#FFFF00] rounded-md flex items-center justify-around h-7 w-24'>
<h4>
<img src="/loader.png" alt="loading..." className='w-8 h-8 mt-2' />
</h4>
</button>
)
:
(
(card.DidMemberStarMe == true) ?
(
<>
<img src='./filledStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
:
(
<>
<img src='./strokeStar.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfStars}
</>
)
)
}
</button>
<button className='rounded-md border-2 border-black flex mt-3 items-center justify-around h-8 w-24 hover:bg-[#ff0000] ' onClick={() => LikeMeme(card.Id, card.DidMemberLikeMe)} >
{
((loadingLikeId == card.Id) && loadingLike) ?
(
<button className='rounded-md border-2 border-black flex items-center justify-around h-8 w-24 bg-[#FFFF00] ' >
<h4>
<img src='./filledStar.png' alt='STAR' className='w-5 h-5' />
</h4>
</button>
)
:
(
(card.DidMemberLikeMe == true) ?
(
<>
<img src='./filledLove.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfLikes}
</>
)
:
(
<>
<img src='./UnfilledLove.png' alt='STAR' className='w-5 h-5' />
{card.NumberOfLikes}
</>
)
)
}
</button>
</div>
</div>
</div>
</div>
}
</div>
</>
)
})
}
</div>
<div className='flex items-center justify-center w-full py-4'>
<img src='/sad.png' className='w-[40px]'/>
<div className='px-5 text-center text-sm'>
Thats all of your Created Nft Art(s)
</div>
</div>
</div>
)
}
</>
)
}
}
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="By Oleanji" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className='flex flex-col space-y-6'>
<div className='flex flex-col items-end pt-3 px-2'>
<ConnectButton />
</div>
<div className=''>
{renderButton()}
</div>
</div>
</div>
)
}
async function GetData() {
const data = await client.query(MemesQuery).toPromise()
return (data.data.memes)
}
async function MemInfo() {
const info = await client.query(MemberQuery).toPromise()
return (info.data.memebers)
}
export async function getServerSideProps() {
const data = await GetData()
const info = await MemInfo()
return{
props:{
memes:data,
members:info
}
}
}
Create Page
This page is used to upload the art of members
import Head from 'next/head'
import { ConnectButton } from '@rainbow-me/rainbowkit';
import { useContract, useProvider,useSigner,useAccount } from 'wagmi'
import {MemeForestAddress, Token, ApiUriv} from '../constant'
import { useEffect, useState } from "react";
import MEME from '../artifacts/contracts/MemeForest.sol/MemeForest.json'
import { createClient } from 'urql'
import { useRouter } from 'next/router';
import { Web3Storage } from 'web3.storage'
const MemberQuery= `
query {
memebers{
Name
Adddress
TotalMeme
StarredMemes
Date
}
}
`
const client = createClient({
url: ApiUriv,
})
export default function Create (props) {
const { data} = useAccount()
const person = data?.address;
const [AMember,setAMember] = useState(false)
const [nameOfFile, setNameOfFile] = useState("")
const [DescriptionOfFile, setDescriptionOfFile] = useState("")
const [Image, setImage] = useState()
const [viewing,setViewing] = useState()
const[loading, setLoading] = useState(false)
const[IsVideo, setIsVideo] = useState(false)
const[IsImage, setIsImage] = useState(false)
const[IsDownloadable, SetIsDownloadable] = useState(false)
const[loadingpage,setLoadingPage] = useState(false)
const[valueExtension, setValueExtension] = useState("")
const [numberOfLoading, setNumberOfLoading] = useState(3)
const { data: signer, isError, isLoading } = useSigner()
const contractWithSigner = useContract({
addressOrName: MemeForestAddress,
contractInterface: MEME.abi,
signerOrProvider: signer,
})
const router = useRouter()
useEffect(() => {
PageLoad()
checkIfAMember(props);
}, []);
const PageLoad = async () =>{
try {
setLoadingPage(true)
const delay = ms => new Promise(res => setTimeout(res, ms));
await delay(7000);
setLoadingPage(false)
} catch (e) {
console.log(e)
}
}
const checkIfAMember = async (props) => {
try {
let data = props.members;
const addresses = ['']
const tx = await Promise.all(data.map(async i => {
addresses.push(i.Adddress)
return addresses
}));
const Address = person.toLowerCase()
const isThere = addresses.includes(Address)
setAMember(isThere)
} catch (e) {
console.log(e)
setAMember(false)
}
}
const CreateMemes = async (memeInfo, valueExt) => {
try {
let time = new Date().toLocaleString();
const create = await contractWithSigner.CreateMemeItems(memeInfo,person,time,valueExt,IsDownloadable)
setNumberOfLoading(1)
await create.wait()
setLoading(false)
Feed();
} catch (error) {
console.log(error)
}
}
function getAccessToken () {
return Token;
}
function makeStorageClient () {
return new Web3Storage({ token: getAccessToken() })
}
const Uploading = async (valueExt) => {
try {
if(!nameOfFile) {
alert("Name of Meme is not there")
}
if(!DescriptionOfFile) {
alert("Description of Meme is not there")
}
if(!Image) {
alert("Put in a picture of your meme")
}
if(nameOfFile && DescriptionOfFile ) {
setLoading(true)
const client = makeStorageClient()
const file = new File([Image], 'image', { type: 'img/png' })
const cid = await client.put([file])
const data = JSON.stringify ({
nameOfFile,
DescriptionOfFile,
image:cid
})
setNumberOfLoading(2)
const blob = new Blob([data], { type: 'application/json' })
const files = [
new File([blob], 'MemeInfo')
]
const MemeInfo = await client.put(files)
CreateMemes(MemeInfo,valueExt);
}
} catch (e) {
console.log(e)
}
}
const gohome = () => {
router.push('/')
}
const Feed = () => {
router.push('/Feed')
}
function OnFileChange(e) {
try {
const file = e.target.files[0]
const fie = e.target.files[0].name
if(fie){
const extension = fie.slice((Math.max(0, fie.lastIndexOf(".")) || Infinity) + 1);
if (extension==="mp4" || extension==="mkv" || extension ==="avi" || extension ==="m4a"){
setIsVideo(true);
setIsImage(false);
setValueExtension("img/mp4")
}
else{
setIsVideo(false);
setIsImage(true);
setValueExtension("img/png")
}
}
if(file){
const image = URL.createObjectURL(file)
setViewing(image)
let reader = new FileReader()
reader.onload = function () {
if(reader.result){
setImage(Buffer.from(reader.result))
}
}
reader.readAsArrayBuffer(file)
}
} catch (error) {
console.log(error )
}
}
const checkbox = () => {
let value = IsDownloadable;
SetIsDownloadable (!value)
}
const renderButton = () =>{
if(!AMember){
return (
<div>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center text-8xl '>
<img src="/loading.png" alt="loading..." />
</div>
)
:
(
<div className='flex flex-col items-center justify-center '>
<div className='text-center font-bold text-lg '>
Go Back Home and Register before Uploading Memes
</div>
<img src='/sad.png' className='w-1/6'/>
<button onClick={gohome} className='no-underline bg-green-500 py-2 px-3 rounded-lg font-bold text-teal-50 hover:bg-orange-500 cursor-pointer ' >
Home
</button>
</div>
)
}
</div>
)
}
if(AMember){
return(
<>
{
loadingpage ?
(
<div className='flex flex-row items-center justify-center' >
<div className='text-center text-8xl'>
<img src="/loading.png" alt="loading..." />
</div>
</div>
)
:
(
<div className='flex flex-col items-center justify-center w-full'>
<h3 className='text-center font-bold text-lg self-center'>
CREATE YOUR NFT ART AND SHOW THE WORLD
</h3>
<div className='flex flex-col md:flex-row items-center justify-center py-2 w-4/5 space-y-8 md:space-x-8'>
<div className=' w-full self-start top-0 py-2'>
<div className='flex flex-col space-y-4 items-center justify-start p-3 '>
<div className=' self-start font-semibold' >
Name:
</div>
<input type='text'
placeholder='Name Of Meme'
onChange={e => setNameOfFile(e.target.value)}
className='p-1 border-2 border-slate-500 mx-4 rounded-lg w-3/4 self-start text-sm '
/>
</div>
<div className='flex flex-col space-y-4 items-center justify-start p-3 '>
<div className=' self-start font-semibold'>
Description:
</div>
<input type='text'
placeholder='Describe your meme'
onChange={e => setDescriptionOfFile(e.target.value)}
className='p-1 border-2 border-slate-500 mx-4 rounded-lg w-3/4 self-start text-sm '
/>
</div>
</div>
<div className='py-2 w-full flex flex-col items-center justify-center'>
{
viewing?
<div className=' border-2 border-slate-400 '>
{
IsImage?
(
<img src={viewing} alt='Your Image' className='w-32 m-4'/>
)
:
(
<video src={viewing} width="500px" height="500px" />
)
}
</div>
:
<div className='w-full h-auto border-2 border-slate-400 flex items-center justify-center '>
<img src='/empty.png' alt='No image Here' className='w-48 m-4'/>
</div>
}
<div className='flex flex-row space-x-3 items-center justify-start p-3 w-full'>
<div className='font-semibold'>
IsDownloadable ?
</div>
<input
type='checkbox'
onChange={() => checkbox() }
/>
</div>
<div className='flex flex-row space-x-3 items-center justify-start p-3 w-full '>
<div className='font-semibold' >
File:
</div>
<input type='file'
onChange={OnFileChange}
className='p-1 border-2 border-slate-500 mx-4 rounded-lg w-full text-sm '
/>
</div>
{
loading ?
(
<button className=' flex items-center justify-center text-center w-full border border-slate-600 border-hidden px-2 py-2 font-semibold text-gray-50 text-lg mt-4 mx-4 bg-green-500 rounded-lg space-x-3 '>
<img src="/loader.png" alt="loading..." className='w-8 h-8 ' />
<span >
{numberOfLoading}
</span>
</button>
) :
(
<button onClick={() => Uploading(valueExtension)}
className='text-center w-full border border-slate-600 border-hidden px-2 py-2 font-semibold text-gray-50 text-sm mt-4 mx-4 bg-green-500 hover:text-green-500 hover:bg-white hover:border hover:border-slate-500 rounded-lg'
>
Create Meme
</button>
)
}
</div>
</div>
</div>
)
}
</>
)
}
}
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="By Oleanji" />
<link rel="icon" href="/favicon.ico" />
</Head>
<div className='flex flex-col space-y-6'>
<div className='flex flex-col items-end pt-3 px-2'>
<ConnectButton />
</div>
<div className=''>
{renderButton()}
</div>
</div>
</div>
)
}
async function MemInfo() {
const info = await client.query(MemberQuery).toPromise()
return (info.data.memebers)
}
export async function getServerSideProps() {
const info = await MemInfo()
return{
props:{
members:info
}
}
}
Result:
You give a name and write description for your art before uploading your it.
TESTING
To run this project open your terminal with the directory pointing to this project and run
yarn dev
This should reun your dapp on this site "http://localhost:3000/"
Check it out and if you run into any errors then write a comment below.
We are finally done with all the tasks to do, I hope by now your drink or snack is almost finished ;) If so, go get another.
Congratulations!!!!!!!!!!!!!!!!!!!!ππππππ you have finished building your Fullstack NFT AIR Dapp.
Now we are done with all
- Create a nextJs application β
- Install the necessary dependencies β
- Write our smart contract β
- Test the contract β
- Deploy the Contract β
- Create and connect your Project with your subgraph β
- Install more dependencies β
- Getting assets (Pictures) used in the projectβ
- Creating Pages, Components and writing Queries (i.e wallet connection, contract interaction, data retrieval from the graph ectera)
- Testing and Running. β
- Create and Upload an art.
Create and Upload Art.
Go to the create page and create your art ππππ
Top comments (2)
This is excellent....congrats
Can you please share project code on the github please