paint-brush
Cómo construir un mercado de NFT rentable con React, Solidity y CometChatpor@daltonic
13,845 lecturas
13,845 lecturas

Cómo construir un mercado de NFT rentable con React, Solidity y CometChat

por Darlington Gospel 74m2022/07/23
Read on Terminal Reader
Read this story w/o Javascript

Demasiado Largo; Para Leer

Este tutorial le enseñará cómo crear un mercado NFT rentable y bien diseñado con funcionalidad de chat. Utilizará la tecnología web3 para crear tokens no fungibles (NFT) y digitalizar activos mientras conserva los derechos de propiedad. Necesitará las siguientes herramientas para aplastar con éxito esta compilación: Nodo, Ganache-Cli y Truffle.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Cómo construir un mercado de NFT rentable con React, Solidity y CometChat
Darlington Gospel  HackerNoon profile picture


Lo que construirá, vea una demostración en la red de prueba de Rinkeby y el repositorio de Git aquí...


Introducción

Web3 es el futuro de Internet y todos debemos aprender y adoptar esta tecnología. Sus casos de uso continúan surgiendo y tienen un impacto positivo en el mundo.


Una magnífica aplicación de la tecnología web3 es la creación de tokens no fungibles (NFT), que son una solución viable para digitalizar activos mientras se conservan los derechos de propiedad.



Este tutorial le enseñará cómo crear un mercado NFT rentable y bien diseñado con funcionalidad de chat.


Reserva tus clases privadas conmigo si necesitas a alguien que te ayude a aprender más rápido el desarrollo web3.


Dicho esto, empecemos...


Echa un vistazo a mi canal de Youtube para obtener tutoriales web3 GRATIS ahora .

Requisito previo

Necesitará las siguientes herramientas instaladas para aplastar con éxito esta compilación:

  • Nodo
  • Ganache-Cli
  • Trufa
  • Reaccionar
  • Infura
  • CSS viento de cola
  • Kit de desarrollo de software CometChat
  • Metamáscara
  • Hilo

Instalación de dependencias

Instalación de NodeJs Verifique que NodeJs ya esté instalado en su máquina, y si no, instálelo desde AQUÍ . Vuelva a ejecutar el código en el terminal para asegurarse de que esté instalado.


Instalación de Yarn, Ganache-cli y Truffle Ejecute los siguientes comandos en su terminal para instalar estos paquetes críticos globalmente.


 npm i -g yarn npm i -g truffle npm i -g ganache-cli


Para confirmar la instalación, ingrese el siguiente código en la terminal.


 yarn --version && ganache-cli --version && truffle version 

Clonación del proyecto de inicio Web3 Clone el proyecto de inicio web 3.0 usando los comandos a continuación. Esto asegura que todos estamos en la misma página y usando el mismo software.


 git clone https://github.com/Daltonic/timelessNFT


Excelente, reemplace el archivo **package.json** con lo siguiente:


 { "name": "TimelessNFT", "private": true, "version": "0.0.0", "scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-scripts eject" }, "dependencies": { "@cometchat-pro/chat": "^3.0.9", "ipfs-http-client": "^56.0.0", "moment": "^2.29.4", "moment-timezone": "^0.5.34", "react": "^17.0.2", "react-dom": "^17.0.2", "react-hooks-global-state": "^1.0.2", "react-icons": "^4.3.1", "react-identicons": "^1.2.5", "react-moment": "^1.1.2", "react-router-dom": "6", "react-scripts": "5.0.0", "web-vitals": "^2.1.4", "web3": "^1.7.1" }, "devDependencies": { "@faker-js/faker": "^6.0.0-alpha.5", "@openzeppelin/contracts": "^4.5.0", "@tailwindcss/forms": "0.4.0", "@truffle/hdwallet-provider": "^2.0.4", "assert": "^2.0.0", "autoprefixer": "10.4.2", "babel-polyfill": "^6.26.0", "babel-preset-env": "^1.7.0", "babel-preset-es2015": "^6.24.1", "babel-preset-stage-2": "^6.24.1", "babel-preset-stage-3": "^6.24.1", "babel-register": "^6.26.0", "buffer": "^6.0.3", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", "crypto-browserify": "^3.12.0", "dotenv": "^16.0.0", "https-browserify": "^1.0.0", "mnemonics": "^1.1.3", "os-browserify": "^0.3.0", "postcss": "8.4.5", "process": "^0.11.10", "react-app-rewired": "^2.1.11", "sharp": "^0.30.1", "stream-browserify": "^3.0.0", "stream-http": "^3.2.0", "tailwindcss": "3.0.18", "url": "^0.11.0" }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] } }


Después de reemplazar los paquetes como se indica, ejecute **yarn install** en su terminal para cargar todos los paquetes con las versiones especificadas.

Configuración del SDK de CometChat

Siga los pasos a continuación para configurar el SDK de CometChat ; al final, debe guardar estas claves como una variable de entorno.


PASO 1: Dirígete al Tablero de CometChat y crea una cuenta.


PASO 2: Inicie sesión en el panel de CometChat , solo después de registrarse.


PASO 3: desde el tablero, agregue una nueva aplicación llamada timelessNFT.


PASO 4: Seleccione la aplicación que acaba de crear de la lista.


PASO 5: Desde el inicio rápido, copie APP_ID , REGION y AUTH_KEY en su archivo .env . Vea la imagen y el fragmento de código.


Reemplace las claves de los marcadores de posición REACT_COMET_CHAT con sus valores apropiados.

 REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************

Configuración de la aplicación Infura

PASO 1: Dirígete a Infura y crea una cuenta.



PASO 2: Desde el tablero crea un nuevo proyecto.



PASO 3: Copie la URL del extremo del WebSocket de la red de prueba de Rinkeby en su archivo .env .



Después de eso, ingrese su frase secreta de Metamask y la clave privada de la cuenta preferida. Si siguió las instrucciones correctamente, sus variables de entorno ahora deberían verse así.


 ENDPOINT_URL=*************************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************


Consulte la sección a continuación si no sabe cómo acceder a su clave privada.

Acceso a su clave privada de Metamask

PASO 1: Asegúrese de que Rinkeby esté seleccionado como la red de prueba en la extensión de su navegador Metamask. Luego, en la cuenta preferida, haga clic en la línea punteada vertical y elija los detalles de la cuenta. Por favor, vea la imagen de abajo.


PASO 2: Ingrese su contraseña en el campo proporcionado y haga clic en el botón de confirmación, esto le permitirá acceder a la clave privada de su cuenta.


PASO 3: Haga clic en "exportar clave privada" para ver su clave privada. Asegúrese de nunca exponer sus claves en una página pública como Github . Es por eso que lo agregamos como una variable de entorno.


PASO 4: Copie su clave privada en su archivo .env . Vea la imagen y el fragmento de código a continuación.



 ENDPOINT_URL=*************************** SECRET_KEY=****************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=******************************


En cuanto a su SECRET_KEY , debe pegar su frase secreta de Metamask en el espacio proporcionado en el archivo de entorno.

El contrato inteligente atemporal de NFT

Aquí está el código de contrato inteligente completo; Repasemos todas las funciones y variables una por una.


 // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; import "./ERC721.sol"; import "./ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract TimelessNFT is ERC721Enumerable, Ownable { using Strings for uint256; mapping(string => uint8) existingURIs; mapping(uint256 => address) public holderOf; address public artist; uint256 public royalityFee; uint256 public supply = 0; uint256 public totalTx = 0; uint256 public cost = 0.01 ether; event Sale( uint256 id, address indexed owner, uint256 cost, string metadataURI, uint256 timestamp ); struct TransactionStruct { uint256 id; address owner; uint256 cost; string title; string description; string metadataURI; uint256 timestamp; } TransactionStruct[] transactions; TransactionStruct[] minted; constructor( string memory _name, string memory _symbol, uint256 _royalityFee, address _artist ) ERC721(_name, _symbol) { royalityFee = _royalityFee; artist = _artist; } function payToMint( string memory title, string memory description, string memory metadataURI, uint256 salesPrice ) external payable { require(msg.value >= cost, "Ether too low for minting!"); require(existingURIs[metadataURI] == 0, "This NFT is already minted!"); require(msg.sender != owner(), "Sales not allowed!"); uint256 royality = (msg.value * royalityFee) / 100; payTo(artist, royality); payTo(owner(), (msg.value - royality)); supply++; minted.push( TransactionStruct( supply, msg.sender, salesPrice, title, description, metadataURI, block.timestamp ) ); emit Sale( supply, msg.sender, msg.value, metadataURI, block.timestamp ); _safeMint(msg.sender, supply); existingURIs[metadataURI] = 1; holderOf[supply] = msg.sender; } function payToBuy(uint256 id) external payable { require(msg.value >= minted[id - 1].cost, "Ether too low for purchase!"); require(msg.sender != minted[id - 1].owner, "Operation Not Allowed!"); uint256 royality = (msg.value * royalityFee) / 100; payTo(artist, royality); payTo(minted[id - 1].owner, (msg.value - royality)); totalTx++; transactions.push( TransactionStruct( totalTx, msg.sender, msg.value, minted[id - 1].title, minted[id - 1].description, minted[id - 1].metadataURI, block.timestamp ) ); emit Sale( totalTx, msg.sender, msg.value, minted[id - 1].metadataURI, block.timestamp ); minted[id - 1].owner = msg.sender; } function changePrice(uint256 id, uint256 newPrice) external returns (bool) { require(newPrice > 0 ether, "Ether too low!"); require(msg.sender == minted[id - 1].owner, "Operation Not Allowed!"); minted[id - 1].cost = newPrice; return true; } function payTo(address to, uint256 amount) internal { (bool success, ) = payable(to).call{value: amount}(""); require(success); } function getAllNFTs() external view returns (TransactionStruct[] memory) { return minted; } function getNFT(uint256 id) external view returns (TransactionStruct memory) { return minted[id - 1]; } function getAllTransactions() external view returns (TransactionStruct[] memory) { return transactions; } }


Importaciones de código e información del contrato En el código a continuación, informamos al compilador de solidity el identificador de licencia y las versiones del compilador calificadas para compilar este código.


 // SPDX-License-Identifier: MIT pragma solidity >=0.7.0 <0.9.0; import "./ERC721.sol"; import "./ERC721Enumerable.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract TimelessNFT is ERC721Enumerable, Ownable { // codes goes in here... }


Además, este contrato inteligente hace uso de algunos contratos inteligentes openzepplin's openzepplin. Debe asegurarse de colocarlos en el mismo directorio que se ve en la imagen a continuación.


Visite este enlace y descargue estos contratos inteligentes como se muestra en la imagen de arriba.


Declaraciones de variables de estado

 using Strings for uint256; mapping(string => uint8) existingURIs; mapping(uint256 => address) public holderOf; address public artist; uint256 public royalityFee; uint256 public supply = 0; uint256 public totalTx = 0; uint256 public cost = 0.01 ether;


Especificamos que estamos usando la biblioteca de cadenas para realizar operaciones de uint a string . A continuación, también declaramos asignaciones para registrar obras de arte NFT acuñadas y también para conocer al propietario actual de un token.


Luego, especificamos las otras variables esenciales para capturar la cuenta del artista, las tarifas de regalías, el suministro actual, las transacciones totales que se han realizado en la plataforma y el costo de acuñación de un NFT.


Montaje de eventos y estructuras.

 event Sale( uint256 id, address indexed owner, uint256 cost, string metadataURI, uint256 timestamp ); struct TransactionStruct { uint256 id; address owner; uint256 cost; string title; string description; string metadataURI; uint256 timestamp; } TransactionStruct[] transactions; TransactionStruct[] minted;


En el código anterior, tenemos un evento de ventas que emite datos de cualquier transacción que ocurra en el contrato inteligente, ya sea en acuñación o transferencia NFT.


Diseñamos una estructura de transacción para recopilar datos sobre NFT acuñados o transferidos. Usando la estructura de transacción que definimos, creamos dos variables llamadas transactions y minted .


Inicializando el constructor

 constructor( string memory _name, string memory _symbol, uint256 _royalityFee, address _artist ) ERC721(_name, _symbol) { royalityFee = _royalityFee; artist = _artist; }


El constructor toma cuatro parámetros para inicializar el contrato inteligente. Un nombre simbólico, un símbolo, una cuenta de artista y una tarifa de regalías por transacción. Luego, el nombre y el símbolo del token se transfieren al contrato inteligente ERC721 durante la implementación.


El algoritmo de la función mint

 function payToMint( string memory title, string memory description, string memory metadataURI, uint256 salesPrice ) external payable { require(msg.value >= cost, "Ether too low for minting!"); require(existingURIs[metadataURI] == 0, "This NFT is already minted!"); require(msg.sender != owner(), "Sales not allowed!"); uint256 royality = (msg.value * royalityFee) / 100; payTo(artist, royality); payTo(owner(), (msg.value - royality)); supply++; minted.push( TransactionStruct( supply, msg.sender, salesPrice, title, description, metadataURI, block.timestamp ) ); emit Sale( supply, msg.sender, msg.value, metadataURI, block.timestamp ); _safeMint(msg.sender, supply); existingURIs[metadataURI] = 1; holderOf[supply] = msg.sender; }


La función anterior es responsable de acuñar nuevos tokens en el contrato inteligente. La persona que llama a este método debe proporcionar cuatro parámetros que incluyen; un título NFT, descripción, URI de metadatos y el precio de venta del NFT después de la acuñación.


Se realizan validaciones para asegurar que el NFT está acuñando y se hacen en consecuencia con los pagos realizados por cada acuñación. Además, la validación garantiza que cada obra de arte esté vinculada de forma única con un token, y que ningún otro token tenga la misma obra de arte. Por último, para la validación, nos aseguramos de que la persona que llama a este método no sea el implementador del contrato inteligente, esto es para garantizar que no mezclemos demasiado las cosas.


Lo siguiente en la función es la regla de pago compartido. El porcentaje de regalías va al artista y el resto de los éteres al propietario.


Luego, grabamos ese NFT dentro de la matriz acuñada y emitimos un evento de ventas. Por último, acuñamos el NFT mientras registramos la dirección de la persona que llama como propietario del token.


El algoritmo de la función de transferencia NFT

 function payToBuy(uint256 id) external payable { require(msg.value >= minted[id - 1].cost, "Ether too low for purchase!"); require(msg.sender != minted[id - 1].owner, "Operation Not Allowed!"); uint256 royality = (msg.value * royalityFee) / 100; payTo(artist, royality); payTo(minted[id - 1].owner, (msg.value - royality)); totalTx++; transactions.push( TransactionStruct( totalTx, msg.sender, msg.value, minted[id - 1].title, minted[id - 1].description, minted[id - 1].metadataURI, block.timestamp ) ); emit Sale( totalTx, msg.sender, msg.value, minted[id - 1].metadataURI, block.timestamp ); minted[id - 1].owner = msg.sender; }


La función anterior toma una identificación de NFT y realiza una compra del NFT de acuerdo con el precio establecido por el minter (propietario).


Se realizan las validaciones necesarias para impedir que los propietarios compren sus NFT y que otros compren con cero éteres.


Luego, se envía una tarifa de regalías a la cuenta del artista y el propietario actual del NFT se queda con el resto.


Cada transferencia de token se registra en una matriz de transacciones para realizar un seguimiento de todas las transacciones realizadas en la plataforma.


Posteriormente, se vuelve a emitir un evento de venta por esta compra para enriquecer los datos registrados en el EVM.


Otras funciones esenciales

 // changes the price of an NFT function changePrice(uint256 id, uint256 newPrice) external returns (bool) { require(newPrice > 0 ether, "Ether too low!"); require(msg.sender == minted[id - 1].owner, "Operation Not Allowed!"); minted[id - 1].cost = newPrice; return true; } // sends ethers to a specific account function payTo(address to, uint256 amount) internal { (bool success, ) = payable(to).call{value: amount}(""); require(success); } // returns all minted NFTs function getAllNFTs() external view returns (TransactionStruct[] memory) { return minted; } // returns a specific NFT by token id function getNFT(uint256 id) external view returns (TransactionStruct memory) { return minted[id - 1]; } // returns all transactions function getAllTransactions() external view returns (TransactionStruct[] memory) { return transactions; }


Y ahí lo tiene para desarrollar el contrato inteligente, luego nos sumergiremos en la construcción de los componentes de la interfaz de usuario con ReactJs.

Configuración del script de implementación

Una cosa más que hacer con el contrato inteligente es configurar el script de implementación.

En el proyecto, diríjase a la carpeta de migraciones >> 2_deploy_contracts.js y actualícelo con el fragmento de código a continuación.


 const TimelessNFT = artifacts.require('TimelessNFT') module.exports = async (deployer) => { const accounts = await web3.eth.getAccounts() await deployer.deploy( TimelessNFT, 'Timeless NFTs', 'TNT', 10, accounts[1] ) }


Excelente, acabamos de terminar el contrato inteligente para nuestra aplicación; ahora es el momento de comenzar con la interfaz DApp. Si necesita un tutor privado que lo ayude a aprender el desarrollo de contratos inteligentes, reserve sus clases conmigo .

Desarrollando la interfaz

La parte delantera se compone de numerosos componentes y piezas. Todos los componentes, vistas y periféricos serán creados por nosotros.


Componente de encabezado


Este componente fue creado con Tailwind CSS y usa el botón rosa Connect Wallet para acceder a la billetera Metamask. Los códigos a continuación demuestran la programación.


 import { useGlobalState } from '../store' import timelessLogo from '../assets/timeless.png' import { connectWallet } from '../TimelessNFT' const Header = () => { const [connectedAccount] = useGlobalState('connectedAccount') return ( <nav className="w-4/5 flex md:justify-center justify-between items-center py-4 mx-auto"> <div className="md:flex-[0.5] flex-initial justify-center items-center"> <img className="w-32 cursor-pointer" src={timelessLogo} alt="Timeless Logo" /> </div> <ul className="md:flex-[0.5] text-white md:flex hidden list-none flex-row justify-between items-center flex-initial" > <li className="mx-4 cursor-pointer">Market</li> <li className="mx-4 cursor-pointer">Artist</li> <li className="mx-4 cursor-pointer">Features</li> <li className="mx-4 cursor-pointer">Community</li> </ul> {!connectedAccount ? ( <button className="shadow-xl shadow-black text-white bg-[#e32970] hover:bg-[#bd255f] md:text-xs p-2 rounded-full cursor-pointer" onClick={connectWallet} > Connect Wallet </button> ) : ( <></> )} </nav> ) } export default Header


Componente héroe


Este componente es responsable de mostrar la billetera conectada y también de lanzar el modal utilizado para crear un nuevo NFT. Además, es responsable de iniciar o registrar usuarios para chats uno a uno con un vendedor de un NFT. Aquí está el código responsable de estas acciones.


 import Identicon from 'react-identicons' import { setGlobalState, useGlobalState, truncate } from '../store' import { getConversations, loginWithCometChat, signUpWithCometChat } from '../CometChat' import ChatList from './ChatList' import { useState } from 'react' const Hero = () => { const [connectedAccount] = useGlobalState('connectedAccount') const [currentUser] = useGlobalState('currentUser') const [recentOpened] = useGlobalState('recentOpened') const [conversations, setConversations] = useState([]) const onCreatedNFT = () => { if (currentUser?.uid.toLowerCase() != connectedAccount.toLowerCase()) return alert('Please login to receive chats from buyers!') setGlobalState('modal', 'scale-100') } const onLunchRecent = () => { getConversations().then((convs) => { setConversations(convs) setGlobalState('recentOpened', true) }) } return ( <div className="flex flex-col md:flex-row w-4/5 justify-between items-center mx-auto py-10" > <div className="md:w-3/6 w-full"> <div> <h1 className="text-white text-5xl font-bold"> Buy and Sell <br /> Digital Arts, <br /> <span className="text-gradient">NFTs</span> Collections </h1> <p className="text-gray-500 font-semibold text-sm mt-3"> Mint and collect the hottest NFTs around. </p> </div> <div className="flex flex-row mt-5"> {connectedAccount ? ( <> <button className="shadow-xl shadow-black text-white bg-[#e32970] hover:bg-[#bd255f] rounded-full cursor-pointer p-2" onClick={onCreatedNFT} > Create NFT </button> <> {currentUser?.uid.toLowerCase() == connectedAccount.toLowerCase() ? ( <button className="text-white border border-gray-500 hover:border-[#e32970] hover:bg-[#bd255f] cursor-pointer rounded-full p-2 mx-3" onClick={onLunchRecent} > Recent Chats </button> ) : ( <> <button className="text-white border border-gray-500 hover:border-[#e32970] hover:bg-[#bd255f] cursor-pointer rounded-full p-2 mx-3" onClick={() => loginWithCometChat(connectedAccount)} > Login for Chat </button> <button className="text-white border border-gray-500 hover:border-[#e32970] hover:bg-[#bd255f] cursor-pointer rounded-full p-2 mx-3" onClick={() => signUpWithCometChat(connectedAccount, connectedAccount)} > Signup for Chat </button> </> )} </> </> ) : null} </div> <div className="w-3/4 flex justify-between items-center mt-5"> <div> <p className="text-white font-bold">1231k</p> <small className="text-gray-300">User</small> </div> <div> <p className="text-white font-bold">152k</p> <small className="text-gray-300">Artwork</small> </div> <div> <p className="text-white font-bold">200k</p> <small className="text-gray-300">Artist</small> </div> </div> </div> <div className="shadow-xl shadow-black md:w-2/5 w-full mt-10 md:mt-0 rounded-md overflow-hidden bg-gray-800" > <img src="https://images.cointelegraph.com/images/1434_aHR0cHM6Ly9zMy5jb2ludGVsZWdyYXBoLmNvbS91cGxvYWRzLzIwMjEtMDYvNGE4NmNmOWQtODM2Mi00YmVhLThiMzctZDEyODAxNjUxZTE1LmpwZWc=.jpg" alt="NFT Art" className="h-60 w-full object-cover" /> <div className="flex justify-start items-center p-3"> <Identicon string={ connectedAccount ? connectedAccount.toLowerCase() : 'Connect Your Wallet' } size={50} className="h-10 w-10 object-contain rounded-full mr-3" /> <div> <p className="text-white font-semibold"> {connectedAccount ? truncate(connectedAccount, 4, 4, 11) : 'Connect Your Wallet'} </p> <small className="text-pink-800 font-bold">@you</small> </div> </div> </div> {recentOpened ? <ChatList users={conversations} /> : null} </div> ) } export default Hero


Componente de obras de arte

Este componente es responsable de representar la lista de NFT acuñadas en la plataforma utilizando las tarjetas CSS de viento de cola bellamente diseñadas. Cada tarjeta tiene una imagen NFT, título, descripción, precio y propietario. Consulte los códigos a continuación para su implementación.


 import { useEffect, useState } from 'react' import { setGlobalState, useGlobalState, truncate } from '../store' const Artworks = () => { const [nfts] = useGlobalState('nfts') const [end, setEnd] = useState(4) const [count] = useState(4) const [collection, setCollection] = useState([]) const setNFT = (nft) => { setGlobalState('nft', nft) setGlobalState('showModal', 'scale-100') } const getCollection = () => { return nfts.slice(0, end) } useEffect(() => { setCollection(getCollection()) }, [nfts, end]) return ( <div className="bg-[#151c25] gradient-bg-artworks"> <div className="w-4/5 py-10 mx-auto"> <h4 className="text-white text-3xl font-bold uppercase text-gradient"> {collection.length > 0 ? 'Latest Artworks' : 'No Artworks Yet'} </h4> <div className="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-6 md:gap-4 lg:gap-3 py-2.5"> {collection.map((nft) => ( <div key={nft.id} className="w-full shadow-xl shadow-black rounded-md overflow-hidden bg-gray-800 my-2 p-3" > <img src={nft.metadataURI} alt={truncate(nft.title, 6)} className="h-60 w-full object-cover shadow-lg shadow-black rounded-lg mb-3" /> <h4 className="text-white font-semibold">{nft.title}</h4> <p className="text-gray-400 text-xs my-1"> {truncate(nft.description)} </p> <div className="flex justify-between items-center mt-3 text-white"> <div className="flex flex-col"> <small className="text-xs">Current Price</small> <p className="text-sm font-semibold">{nft.cost} ETH</p> </div> <button onClick={() => setNFT(nft)} className="shadow-lg shadow-black text-white text-sm bg-[#e32970] hover:bg-[#bd255f] cursor-pointer rounded-full px-1.5 py-1" > View Details </button> </div> </div> ))} </div> {collection.length > 0 && nfts.length > collection.length ? ( <div className="text-center my-5"> <button className="shadow-xl shadow-black text-white bg-[#e32970] hover:bg-[#bd255f] rounded-full cursor-pointer p-2" onClick={() => setEnd(end + count)} > Load More </button> </div> ) : null} </div> </div> ) } export default Artworks


Componente de transacciones

Este componente es responsable de representar todas las transacciones que tuvieron lugar en nuestro contrato inteligente. Una transacción, por ejemplo, sería Alison comprando un NFT de Duke. Esta compra se capturará en este componente como una transacción. Vea el fragmento a continuación.


 import { useEffect, useState } from 'react' import { BiTransfer } from 'react-icons/bi' import { MdOpenInNew } from 'react-icons/md' import { useGlobalState, truncate } from '../store' const Transactions = () => { const [transactions] = useGlobalState('transactions') const [end, setEnd] = useState(3) const [count] = useState(3) const [collection, setCollection] = useState([]) const getCollection = () => { return transactions.slice(0, end) } useEffect(() => { setCollection(getCollection()) }, [transactions, end]) return ( <div className="bg-[#151c25]"> <div className="w-4/5 py-10 mx-auto"> <h4 className="text-white text-3xl font-bold uppercase text-gradient"> {collection.length > 0 ? 'Latest Transactions' : 'No Transaction Yet'} </h4> <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 md:gap-4 lg:gap-2 py-2.5"> {collection .map((tx) => ( <div key={tx.id} className="flex justify-between items-center border border-pink-500 text-gray-400 w-full shadow-xl shadow-black rounded-md overflow-hidden bg-gray-800 my-2 p-3" > <div className="rounded-md shadow-sm shadow-pink-500 p-2"> <BiTransfer /> </div> <div> <h4 className="text-sm">{tx.title} Transfered</h4> <small className="flex flex-row justify-start items-center"> <span className="mr-1">Received by</span> <a href="#" className="text-pink-500 mr-2"> {truncate(tx.owner, 4, 4, 11)} </a> <a href="#"> <MdOpenInNew /> </a> </small> </div> <p className="text-sm font-medium">{tx.cost}ETH</p> </div> ))} </div> {collection.length > 0 && transactions.length > collection.length ? ( <div className="text-center my-5"> <button className="shadow-xl shadow-black text-white bg-[#e32970] hover:bg-[#bd255f] rounded-full cursor-pointer p-2" onClick={() => setEnd(end + count)} > Load More </button> </div> ) : null} </div> </div> ) } export default Transactions


Componente de pie de página


Este componente simplemente muestra algunos hermosos enlaces en la parte inferior de la página, no hace mucho cuando se trata de funcionalidades pero complementa la interfaz de usuario. Sus códigos están escritos a continuación.


 import timelessLogo from '../assets/timeless.png' const Footer = () => ( <div className="w-full flex md:justify-center justify-between items-center flex-col p-4 gradient-bg-footer"> <div className="w-full flex sm:flex-row flex-col justify-between items-center my-4"> <div className="flex flex-[0.25] justify-center items-center"> <img src={timelessLogo} alt="logo" className="w-32" /> </div> <div className="flex flex-1 justify-evenly items-center flex-wrap sm:mt-0 mt-5 w-full"> <p className="text-white text-base text-center mx-2 cursor-pointer"> Market </p> <p className="text-white text-base text-center mx-2 cursor-pointer"> Artist </p> <p className="text-white text-base text-center mx-2 cursor-pointer"> Features </p> <p className="text-white text-base text-center mx-2 cursor-pointer"> Community </p> </div> <div className="flex flex-[0.25] justify-center items-center"> <p className="text-white text-right text-xs">&copy;2022 All rights reserved</p> </div> </div> </div> ) export default Footer


Fantástico, eso es todo por los componentes obvios, incluyamos los componentes ocultos que solo se invocan a través de una interfaz modal.


Crear componente NFT


Este componente tiene el deber de acuñar nuevos NFT proporcionando una imagen, título, precio y descripción. Una vez que se hace clic en el botón Mint Now , la imagen se carga en IPFS (Sistema de archivos interplanetarios) y se devuelve una URL de imagen.


La URL de la imagen devuelta junto con los datos NFT proporcionados en el formulario se envían a nuestro contrato inteligente para la acuñación, inmediatamente después de que el usuario autorice la transacción con su billetera Metamask.


Al completar la transacción, el NFT se incluye entre las obras de arte y los compradores interesados pueden comprarlas e incluso cambiar sus precios. Consulte el código a continuación para obtener más detalles.


 import { useGlobalState, setGlobalState, setLoadingMsg, setAlert, } from '../store' import { mintNFT } from '../TimelessNFT' import { useState } from 'react' import { FaTimes } from 'react-icons/fa' import { create } from 'ipfs-http-client' const client = create('https://ipfs.infura.io:5001/api/v0') const CreateNFT = () => { const [modal] = useGlobalState('modal') const [title, setTitle] = useState('') const [price, setPrice] = useState('') const [description, setDescription] = useState('') const [fileUrl, setFileUrl] = useState('') const [imgBase64, setImgBase64] = useState(null) const onChange = async (e) => { const reader = new FileReader() if (e.target.files[0]) reader.readAsDataURL(e.target.files[0]) reader.onload = (readerEvent) => { const file = readerEvent.target.result setImgBase64(file) setFileUrl(e.target.files[0]) } } const handleSubmit = async (e) => { e.preventDefault() if (!title || !price || !description) return setGlobalState('modal', 'scale-0') setGlobalState('loading', { show: true, msg: 'Uploading IPFS data...' }) try { const created = await client.add(fileUrl) const metadataURI = `https://ipfs.infura.io/ipfs/${created.path}` const nft = { title, price, description, metadataURI } setLoadingMsg('Intializing transaction...') mintNFT(nft).then((res) => { if (res) { setFileUrl(metadataURI) resetForm() setAlert('Minting completed...', 'green') window.location.reload() } }) } catch (error) { console.log('Error uploading file: ', error) setAlert('Minting failed...', 'red') } } const closeModal = () => { setGlobalState('modal', 'scale-0') resetForm() } const resetForm = () => { setFileUrl('') setImgBase64(null) setTitle('') setPrice('') setDescription('') } return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 ${modal}`} > <div className="bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <form className="flex flex-col"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold text-gray-400">Add NFT</p> <button type="button" onClick={closeModal} className="border-0 bg-transparent focus:outline-none" > <FaTimes className="text-gray-400" /> </button> </div> <div className="flex flex-row justify-center items-center rounded-xl mt-5"> <div className="shrink-0 rounded-xl overflow-hidden h-20 w-20"> <img alt="NFT" className="h-full w-full object-cover cursor-pointer" src={ imgBase64 || 'https://images.unsplash.com/photo-1580489944761-15a19d654956?ixlib=rb-1.2.1&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1361&q=80' } /> </div> </div> <div className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5"> <label className="block"> <span className="sr-only">Choose profile photo</span> <input type="file" accept="image/png, image/gif, image/jpeg, image/webp" className="block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:rounded-full file:border-0 file:text-sm file:font-semibold file:bg-[#19212c] file:text-gray-400 hover:file:bg-[#1d2631] cursor-pointer focus:ring-0 focus:outline-none" onChange={onChange} required /> </label> </div> <div className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5"> <input className="block w-full text-sm text-slate-500 bg-transparent border-0 focus:outline-none focus:ring-0" type="text" name="title" placeholder="Title" onChange={(e) => setTitle(e.target.value)} value={title} required /> </div> <div className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5"> <input className="block w-full text-sm text-slate-500 bg-transparent border-0 focus:outline-none focus:ring-0" type="number" step={0.01} min={0.01} name="price" placeholder="Price (Eth)" onChange={(e) => setPrice(e.target.value)} value={price} required /> </div> <div className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5"> <textarea className="block w-full text-sm resize-none text-slate-500 bg-transparent border-0 focus:outline-none focus:ring-0 h-20" type="text" name="description" placeholder="Description" onChange={(e) => setDescription(e.target.value)} value={description} required ></textarea> </div> <button type="submit" onClick={handleSubmit} className="flex flex-row justify-center items-center w-full text-white text-md bg-[#e32970] hover:bg-[#bd255f] py-2 px-5 rounded-full drop-shadow-xl border border-transparent hover:bg-transparent hover:text-[#e32970] hover:border hover:border-[#bd255f] focus:outline-none focus:ring mt-5" > Mint Now </button> </form> </div> </div> ) } export default CreateNFT


Mostrar componente NFT

Este componente muestra más información sobre un NFT específico, ofreciendo al propietario un botón para cambiar el precio y al comprador un botón para comprar el NFT o chatear con el vendedor. Consulte el código a continuación para obtener más detalles.


 import Chat from './Chat' import Identicon from 'react-identicons' import { FaTimes } from 'react-icons/fa' import { buyNFT } from '../TimelessNFT' import { useGlobalState, setGlobalState, truncate, setAlert } from '../store' import { useState } from 'react' import { getMessages } from '../CometChat' const ShowNFT = () => { const [showModal] = useGlobalState('showModal') const [chatOpened] = useGlobalState('chatOpened') const [currentUser] = useGlobalState('currentUser') const [connectedAccount] = useGlobalState('connectedAccount') const [nft] = useGlobalState('nft') const [messages, setMessages] = useState([]) const onChangePrice = () => { setGlobalState('nft', nft) setGlobalState('showModal', 'scale-0') setGlobalState('updateModal', 'scale-100') } const onChatSeller = () => { if (currentUser?.uid.toLowerCase() != connectedAccount.toLowerCase()) return alert('Please login to receive chats from buyers!') getMessages(nft.owner).then((msgs) => { setMessages( msgs.filter((msg) => { return ( !!!msg?.deletedAt && !!!msg?.action && (msg?.conversationId == `${msg?.rawMessage.receiver}_user_${msg?.rawMessage.sender}` || msg?.conversationId == `${msg?.rawMessage.sender}_user_${msg?.rawMessage.receiver}`) ) }) ) setGlobalState('nft', nft) setGlobalState('chatOpened', true) }) } const handleNFTPurchase = () => { setGlobalState('showModal', 'scale-0') setGlobalState('loading', { show: true, msg: 'Initializing NFT transfer...', }) try { buyNFT(nft).then((res) => { if (res) { setAlert('Transfer completed...', 'green') window.location.reload() } }) } catch (error) { console.log('Error transfering NFT: ', error) setAlert('Purchase failed...', 'red') } } return ( <div> {chatOpened ? ( <Chat receiver={nft.owner} chats={messages} /> ) : ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 ${showModal}`} > <div className="bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <div className="flex flex-col"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold text-gray-400">Buy NFT</p> <button type="button" onClick={() => setGlobalState('showModal', 'scale-0')} className="border-0 bg-transparent focus:outline-none" > <FaTimes className="text-gray-400" /> </button> </div> <div className="flex flex-row justify-center items-center rounded-xl mt-5"> <div className="shrink-0 rounded-xl overflow-hidden h-40 w-40"> <img className="h-full w-full object-cover cursor-pointer" src={nft?.metadataURI} alt={nft?.title} /> </div> </div> <div className="flex flex-col justify-start rounded-xl mt-5"> <h4 className="text-white font-semibold">{nft?.title}</h4> <p className="text-gray-400 text-xs my-1">{nft?.description}</p> <div className="flex justify-between items-center mt-3 text-white"> <div className="flex justify-start items-center"> <Identicon string={nft?.owner.toLowerCase()} size={50} className="h-10 w-10 object-contain rounded-full mr-3" /> <div className="flex flex-col justify-center items-start"> <small className="text-white font-bold">@owner</small> <small className="text-pink-800 font-semibold"> {nft?.owner ? truncate(nft.owner, 4, 4, 11) : '...'} </small> </div> </div> <div className="flex flex-col"> <small className="text-xs">Current Price</small> <p className="text-sm font-semibold">{nft?.cost} ETH</p> </div> </div> </div> {connectedAccount != nft?.owner ? ( <div className="flex flex-row justify-between items-center"> <button className="flex flex-row justify-center items-center w-full text-white text-md bg-[#e32970] hover:bg-[#bd255f] py-2 px-5 rounded-full drop-shadow-xl border border-transparent hover:bg-transparent hover:text-[#e32970] hover:border hover:border-[#bd255f] focus:outline-none focus:ring mt-5" onClick={handleNFTPurchase} > Purchase Now </button> <button className="flex flex-row justify-center items-center w-full text-white text-md bg-transparent py-2 px-5 rounded-full drop-shadow-xl border border-transparent hover:bg-transparent hover:text-[#e32970] focus:outline-none focus:ring mt-5" onClick={onChatSeller} > Chat with Seller </button> </div> ) : ( <button className="flex flex-row justify-center items-center w-full text-white text-md bg-[#e32970] hover:bg-[#bd255f] py-2 px-5 rounded-full drop-shadow-xl border border-transparent hover:bg-transparent hover:text-[#e32970] hover:border hover:border-[#bd255f] focus:outline-none focus:ring mt-5" onClick={onChangePrice} > Change Price </button> )} </div> </div> </div> )} </div> ) } export default ShowNFT


Actualizar componente NFT

Este componente tiene la tarea de cambiar el precio del NFT. Esta acción solo puede ser realizada por el propietario del NFT. Si bien esta opción está disponible, se cobrará una tarifa de gasolina para efectuar estos cambios. Una vez que un NFT intercambia una mano con otro comprador, el nuevo propietario puede decidir aumentar el precio, y es por eso que esta opción está disponible. Vea el fragmento de código a continuación.


 import { useGlobalState, setGlobalState, setLoadingMsg, setAlert, } from '../store' import { updateNFT } from '../TimelessNFT' import { useState } from 'react' import { FaTimes } from 'react-icons/fa' const UpdateNFT = () => { const [modal] = useGlobalState('updateModal') const [nft] = useGlobalState('nft') const [price, setPrice] = useState('') const handleSubmit = async (e) => { e.preventDefault() if (!price || price <= 0) return setGlobalState('modal', 'scale-0') setGlobalState('loading', { show: true, msg: 'Initiating price update...' }) try { setLoadingMsg('Price updating...') setGlobalState('updateModal', 'scale-0') updateNFT({...nft, cost: price}).then((res) => { if (res) { setAlert('Price updated...', 'green') window.location.reload() } }) } catch (error) { console.log('Error updating file: ', error) setAlert('Update failed...', 'red') } } return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 ${modal}`} > <div className="bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <form className="flex flex-col"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold text-gray-400">{nft?.title}</p> <button type="button" onClick={() => setGlobalState('updateModal', 'scale-0')} className="border-0 bg-transparent focus:outline-none" > <FaTimes className="text-gray-400" /> </button> </div> <div className="flex flex-row justify-center items-center rounded-xl mt-5"> <div className="shrink-0 rounded-xl overflow-hidden h-20 w-20"> <img alt="NFT" className="h-full w-full object-cover cursor-pointer" src={nft?.metadataURI} /> </div> </div> <div className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5"> <input className="block w-full text-sm text-slate-500 bg-transparent border-0 focus:outline-none focus:ring-0" type="number" step={0.01} min={0.01} name="price" placeholder="Price (Eth)" onChange={(e) => setPrice(e.target.value)} required /> </div> <button type="submit" onClick={handleSubmit} className="flex flex-row justify-center items-center w-full text-white text-md bg-[#e32970] hover:bg-[#bd255f] py-2 px-5 rounded-full drop-shadow-xl border border-transparent hover:bg-transparent hover:text-[#e32970] hover:border hover:border-[#bd255f] focus:outline-none focus:ring mt-5" > Update Now </button> </form> </div> </div> ) } export default UpdateNFT


Componente de lista de chat

Este componente revela los chats recientes que un usuario ha realizado con un vendedor o comprador en la plataforma. El componente también captura el último mensaje que se envió en su conversación. Un clic en cada conversación te llevará a la interfaz de chat. Vea el código a continuación.


 import Chat from './Chat' import Moment from 'react-moment' import { useState } from 'react' import { FaTimes } from 'react-icons/fa' import { setGlobalState, truncate, useGlobalState } from '../store' import { getMessages } from '../CometChat' const ChatList = ({ users }) => { const [messages, setMessages] = useState([]) const [receiver, setReceiver] = useState('') const [recentChatOpened] = useGlobalState('recentChatOpened') const [connectedAccount] = useGlobalState('connectedAccount') const onEnterChat = (receiver) => { setReceiver(receiver) getMessages(receiver).then((msgs) => { setMessages( msgs.filter((msg) => { return ( !!!msg?.deletedAt && !!!msg?.action && msg?.conversationId == `${msg?.rawMessage.receiver}_user_${msg?.rawMessage.sender}` ) }) ) setGlobalState('recentChatOpened', true) }) } return ( <div> {recentChatOpened ? ( <Chat receiver={receiver} chats={messages} /> ) : ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 scale-100`} > <div className="bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl w-11/12 md:w-2/5 h-7/12 p-6"> <div className="flex flex-col text-gray-400"> <div className="flex flex-row justify-between items-center"> <p className="font-semibold text-gray-400">Conversations</p> <button type="button" onClick={() => setGlobalState('recentOpened', false)} className="border-0 bg-transparent focus:outline-none" > <FaTimes /> </button> </div> <div className="h-[calc(100vh_-_20rem)] overflow-y-auto sm:pr-4 my-3"> {users.map((user, i) => ( <button key={i} className="flex flex-row justify-between w-full items-center bg-gray-800 hover:bg-gray-900 rounded-md px-4 py-3 my-1 cursor-pointer transform transition-transform duration-300" onClick={() => onEnterChat(user?.lastMessage.sender.uid)} > <div className="flex flex-col text-left"> <h4 className="text-sm text-[#e32970] font-semiBold"> @{truncate(user?.lastMessage.sender.uid, 4, 4, 11)} </h4> <p className="text-xs"> {user?.lastMessage.text} </p> </div> <Moment className="text-xs font-bold" unix date={user?.lastMessage.sentAt} format="YYYY/MM/D hh:mm A" /> </button> ))} </div> </div> </div> </div> )} </div> ) } export default ChatList


Componente de chat

Este componente es responsable de involucrar a dos usuarios en un chat uno a uno. La imagen de arriba muestra una conversación entre un comprador y un vendedor en la plataforma, desde dos navegadores diferentes. Vea el código a continuación para su implementación.


 import Identicon from 'react-identicons' import { useGlobalState, setGlobalState, truncate } from '../store' import { sendMessage, CometChat } from '../CometChat' import { useEffect, useState } from 'react' import { FaTimes } from 'react-icons/fa' const Chat = ({ receiver, chats }) => { const [connectedAccount] = useGlobalState('connectedAccount') const [message, setMessage] = useState('') const [messages, setMessages] = useState(chats) const handleSubmit = async (e) => { e.preventDefault() sendMessage(receiver, message).then((msg) => { setMessages((prevState) => [...prevState, msg]) setMessage('') scrollToEnd() }) } const listenForMessage = (listenerID) => { CometChat.addMessageListener( listenerID, new CometChat.MessageListener({ onTextMessageReceived: (message) => { setMessages((prevState) => [...prevState, message]) scrollToEnd() }, }) ) } const onClose = () => { setGlobalState('chatOpened', false) setGlobalState('recentChatOpened', false) } const scrollToEnd = () => { const element = document.getElementById('messages-container') element.scrollTop = element.scrollHeight } useEffect(() => { listenForMessage(receiver) }, [receiver]) return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 scale-100`} > <div className="bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl w-5/6 h-5/6 p-6"> <div className="flex flex-col text-gray-400"> <div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-center items-center"> <div className="shrink-0 rounded-full overflow-hidden h-10 w-10 mr-3"> <Identicon string={receiver.toLowerCase()} size={50} className="h-full w-full object-cover cursor-pointer rounded-full" /> </div> <p className="font-bold">@{receiver ? truncate(receiver, 4, 4, 11) : '...'}</p> </div> <button type="button" onClick={onClose} className="border-0 bg-transparent focus:outline-none" > <FaTimes /> </button> </div> <div id="messages-container" className="h-[calc(100vh_-_20rem)] overflow-y-auto sm:pr-4 my-3" > {messages.map((msg, i) => msg?.receiverId?.toLowerCase() == connectedAccount.toLowerCase() ? ( <div key={i} className="flex flex-row justify-start items-center mt-5" > <div className="flex flex-col justify-start items-start"> <h4 className="text-[#e32970]"> @{receiver ? truncate(receiver, 4, 4, 11) : '...'} </h4> <p className="text-xs">{msg.text}</p> </div> </div> ) : ( <div key={i} className="flex flex-row justify-end items-center mt-5" > <div className="flex flex-col justify-start items-end"> <h4 className="text-[#e32970]">@you</h4> <p className="text-xs">{msg.text}</p> </div> </div> ) )} </div> <form onSubmit={handleSubmit} className="flex flex-row justify-between items-center bg-gray-800 rounded-xl mt-5" > <input className="block w-full text-sm resize-none text-slate-500 bg-transparent border-0 focus:outline-none focus:ring-0 h-20" type="text" name="message" placeholder="Write message..." onChange={(e) => setMessage(e.target.value)} value={message} required /> </form> </div> </div> </div> ) } export default Chat


Genial, ahora que hemos incluido esos fantásticos componentes, terminemos con estos dos últimos.


Componente de carga

Este componente simplemente muestra la actividad y el estado actual cuando una transacción está en proceso. Vea el código a continuación.


 import { useGlobalState } from '../store' const Loading = () => { const [loading] = useGlobalState('loading') return ( <div className={`fixed top-0 left-0 w-screen h-screen flex items-center justify-center bg-black bg-opacity-50 transform transition-transform duration-300 ${loading.show ? 'scale-100' : 'scale-0'}`} > <div className="flex flex-col justify-center items-center bg-[#151c25] shadow-xl shadow-[#e32970] rounded-xl min-w-min px-10 pb-2" > <div className="flex flex-row justify-center items-center"> <div className="lds-dual-ring scale-50"></div> <p className="text-lg text-white">Processing...</p> </div> <small className="text-white">{loading.msg}</small> </div> </div> ) } export default Loading


El componente de la aplicación Este archivo agrupa el componente anterior discutido en este trabajo. Así es como funciona una arquitectura ReactJs. Consulte los códigos a continuación.


 import Alert from './components/Alert' import Artworks from './components/Artworks' import CreateNFT from './components/CreateNFT' import Footer from './components/Footer' import Header from './components/Header' import Hero from './components/Hero' import Loading from './components/Loading' import ShowNFT from './components/ShowNFT' import Transactions from './components/Transactions' import UpdateNFT from './components/UpdateNFT' import { isUserLoggedIn } from './CometChat' import { loadWeb3 } from './TimelessNFT' import { useEffect } from 'react' const App = () => { useEffect(() => { loadWeb3() isUserLoggedIn() }, []) return ( <div className="min-h-screen"> <div className="gradient-bg-hero"> <Header /> <Hero /> </div> <Artworks /> <Transactions /> <CreateNFT /> <UpdateNFT /> <ShowNFT /> <Footer /> <Alert /> <Loading /> </div> ) } export default App


Fantástico, acabamos de completar la integración de los diversos componentes, sellémoslo con las otras partes de este proyecto.

Otros archivos esenciales

Esta aplicación utiliza un almacén de administración de estado, un SDK de CometChat y un archivo de servicio de contrato. Echemos un vistazo a ellos uno tras otro.


El archivo de administración de estado Store This usa el paquete npm react-hooks-global-state . Es simple, rápido y más fácil que Redux. Todas las variables y funciones globales utilizadas en esta aplicación se crearon en esta tienda.


En la raíz del proyecto, vaya al directorio src y cree una carpeta llamada store . Ahora, dentro de esta carpeta de la tienda, cree un archivo llamado index.js y pegue los códigos a continuación dentro de él.


 import { createGlobalState } from 'react-hooks-global-state' const { setGlobalState, useGlobalState, getGlobalState } = createGlobalState({ modal: 'scale-0', updateModal: 'scale-0', mintModal: '', alert: { show: false, msg: '', color: '' }, loading: { show: false, msg: '' }, showModal: 'scale-0', chatOpened: false, recentChatOpened: false, recentOpened: false, chatList: 'scale-0', connectedAccount: '', currentUser: null, nft: null, nfts: [], transactions: [], contract: null, }) const setAlert = (msg, color = 'green') => { setGlobalState('loading', false) setGlobalState('alert', { show: true, msg, color }) setTimeout(() => { setGlobalState('alert', { show: false, msg: '', color }) }, 6000) } const setLoadingMsg = (msg) => { const loading = getGlobalState('loading') setGlobalState('loading', { ...loading, msg }) } const truncate = (text, startChars, endChars, maxLength) => { if (text.length > maxLength) { var start = text.substring(0, startChars) var end = text.substring(text.length - endChars, text.length) while (start.length + end.length < maxLength) { start = start + '.' } return start + end } return text } export { useGlobalState, setGlobalState, getGlobalState, setAlert, setLoadingMsg, truncate, }


El servicio CometChat Este archivo contiene todas las funciones esenciales para comunicarse con el SDK de CometChat. Consulte los códigos a continuación.


 import { CometChat } from '@cometchat-pro/chat' import { setGlobalState } from './store' const CONSTANTS = { APP_ID: process.env.REACT_APP_COMET_CHAT_APP_ID, REGION: process.env.REACT_APP_COMET_CHAT_REGION, Auth_Key: process.env.REACT_APP_COMET_CHAT_AUTH_KEY, } const initCometChat = async () => { const appID = CONSTANTS.APP_ID const region = CONSTANTS.REGION const appSetting = new CometChat.AppSettingsBuilder() .subscribePresenceForAllUsers() .setRegion(region) .build() await CometChat.init(appID, appSetting) .then(() => console.log('Initialization completed successfully')) .catch((error) => error) } const loginWithCometChat = async (UID) => { const authKey = CONSTANTS.Auth_Key await CometChat.login(UID, authKey) .then((user) => setGlobalState('currentUser', user)) .catch((error) => { alert(error.message) console.log(error) }) return true } const signUpWithCometChat = async (UID, name) => { let authKey = CONSTANTS.Auth_Key const user = new CometChat.User(UID) user.setName(name) await CometChat.createUser(user, authKey) .then((user) => { alert('Signed up successfully, click login now!') console.log('Logged In: ', user) }) .catch((error) => { alert(error.message) console.log(error) }) return true } const logOutWithCometChat = async () => { return await CometChat.logout() .then(() => console.log('Logged Out Successfully')) .catch((error) => error) } const isUserLoggedIn = async () => { await CometChat.getLoggedinUser() .then((user) => setGlobalState('currentUser', user)) .catch((error) => console.log('error:', error)) } const getMessages = async (UID) => { const limit = 30 const messagesRequest = new CometChat.MessagesRequestBuilder() .setUID(UID) .setLimit(limit) .build() return await messagesRequest .fetchPrevious() .then((messages) => messages) .catch((error) => error) } const sendMessage = async (receiverID, messageText) => { const receiverType = CometChat.RECEIVER_TYPE.USER const textMessage = new CometChat.TextMessage( receiverID, messageText, receiverType ) return await CometChat.sendMessage(textMessage) .then((message) => message) .catch((error) => error) } const getConversations = async () => { const limit = 30 const conversationsRequest = new CometChat.ConversationsRequestBuilder() .setLimit(limit) .build() return await conversationsRequest .fetchNext() .then((conversationList) => conversationList) } export { initCometChat, loginWithCometChat, signUpWithCometChat, logOutWithCometChat, getMessages, sendMessage, getConversations, isUserLoggedIn, CometChat, }


El archivo de servicio de contrato Este archivo contiene todas las funciones y procedimientos responsables de interactuar con el contrato inteligente en la cadena de bloques utilizando la biblioteca Web3. Consulte los códigos a continuación.


 import Web3 from 'web3' import { setGlobalState, getGlobalState, setAlert } from './store' import TimelessNFT from './abis/TimelessNFT.json' const { ethereum } = window const connectWallet = async () => { try { if (!ethereum) return alert('Please install Metamask') const accounts = await ethereum.request({ method: 'eth_requestAccounts' }) setGlobalState('connectedAccount', accounts[0]) } catch (error) { setAlert(JSON.stringify(error), 'red') } } const structuredNfts = (nfts) => { return nfts .map((nft) => ({ id: Number(nft.id), owner: nft.owner, cost: window.web3.utils.fromWei(nft.cost), title: nft.title, description: nft.description, metadataURI: nft.metadataURI, timestamp: nft.timestamp, })) .reverse() } const loadWeb3 = async () => { try { if (!ethereum) return alert('Please install Metamask') window.web3 = new Web3(ethereum) window.web3 = new Web3(window.web3.currentProvider) const web3 = window.web3 const accounts = await web3.eth.getAccounts() setGlobalState('connectedAccount', accounts[0]) const networkId = await web3.eth.net.getId() const networkData = TimelessNFT.networks[networkId] if (networkData) { const contract = new web3.eth.Contract( TimelessNFT.abi, networkData.address ) const nfts = await contract.methods.getAllNFTs().call() const transactions = await contract.methods.getAllTransactions().call() setGlobalState('nfts', structuredNfts(nfts)) setGlobalState('transactions', structuredNfts(transactions)) setGlobalState('contract', contract) } else { window.alert('TimelessNFT contract not deployed to detected network.') } } catch (error) { alert('Please connect your metamask wallet!') } } const mintNFT = async ({ title, description, metadataURI, price }) => { try { price = window.web3.utils.toWei(price.toString(), 'ether') const contract = getGlobalState('contract') const account = getGlobalState('connectedAccount') const mintPrice = window.web3.utils.toWei('0.01', 'ether') await contract.methods .payToMint(title, description, metadataURI, price) .send({ from: account, value: mintPrice }) return true } catch (error) { setAlert(error.message, 'red') } } const buyNFT = async ({ id, cost }) => { try { cost = window.web3.utils.toWei(cost.toString(), 'ether') const contract = getGlobalState('contract') const buyer = getGlobalState('connectedAccount') await contract.methods.payToBuy(Number(id)).send({ from: buyer, value: cost }) return true } catch (error) { setAlert(error.message, 'red') } } const updateNFT = async ({ id, cost }) => { try { cost = window.web3.utils.toWei(cost.toString(), 'ether') const contract = getGlobalState('contract') const buyer = getGlobalState('connectedAccount') await contract.methods.changePrice(Number(id), cost).send({ from: buyer }) return true } catch (error) { setAlert(error.message, 'red') } } export { loadWeb3, connectWallet, mintNFT, buyNFT, updateNFT }


Activos del proyecto Descargue este logotipo e inclúyalo dentro de la carpeta de activos en su directorio raíz. Y con eso, ha incluido con éxito todo lo que se necesita para ejecutar esta aplicación.

Puesta en marcha del servidor

Para continuar con este paso, migre el contrato inteligente a la Web para que pueda interactuar con él. Ejecute el siguiente código en su terminal.


 truffle migrate --network rinkeby


El código anterior enviará su contrato inteligente al servidor utilizando Infura RPC.


También puede configurar una cadena de bloques local utilizando el servidor ganache-cli que configuramos al comienzo de este tutorial. Simplemente ejecute el código a continuación para enviarlo a su servidor de cadena de bloques local si lo prefiere de esa manera.


Abra una terminal, ejecute **ganache-cli -d** y en una terminal diferente, ejecute **truffle migrate** o **truffle deploy** .


Tenga en cuenta que si está utilizando ganache-cli como su EVM, también debe agregar el servidor localhost a Metamask e importar las claves privadas generadas por ganache. Consulte Inicio del entorno de desarrollo para obtener orientación .


Si necesitas mi ayuda para resolver problemas en tu proyecto, consúltame en esta página .

Ahora, ejecute yarn start para iniciar su aplicación de reacción. Y ahí lo tienes con esta compilación en el mercado de NFT.


Mira mis tutoriales web3 GRATIS en Youtube ahora .

Conclusión

Hemos llegado a la línea de meta de esta compilación de NFT, sé que ha obtenido una tonelada de creación de valor junto conmigo.


Seas del nivel que seas, si quieres crecer más rápido en tus habilidades de desarrollo web3, entra en mi clase privada .


¡Hasta la próxima, sigue aplastándolo!


Sobre el Autor

Gospel Darlington es un desarrollador de blockchain de pila completa con más de 6+ años de experiencia en la industria del desarrollo de software.


Al combinar el desarrollo de software, la escritura y la enseñanza, demuestra cómo crear aplicaciones descentralizadas en redes de cadena de bloques compatibles con EVM.


Sus pilas incluyen JavaScript , React , Vue , Angular , Node , React Native , NextJs , Solidity y más.


Para obtener más información sobre él, visite y siga su página en Twitter , GitHub , LinkedIn o en su sitio web .