Ce que vous allez construire, voir une sur le réseau de test Rinkeby et le ici… démo dépôt git Introduction Web3 est l'avenir d'Internet, et nous devons tous apprendre et adopter cette technologie. Ses cas d'utilisation continuent d'émerger et ont un impact positif sur le monde. Une magnifique application de la technologie web3 est la création de jetons non fongibles (NFT), qui constituent une solution viable pour numériser les actifs tout en conservant les droits de propriété. Ce tutoriel vous apprendra comment créer un marché NFT rentable et bien conçu avec une fonctionnalité de chat. si vous avez besoin de quelqu'un pour vous aider à apprendre le développement web3 plus rapidement. Réservez vos cours privés avec moi Cela dit, commençons... . Consultez ma chaîne Youtube pour des tutoriels Web3 GRATUITS maintenant Prérequis Vous aurez besoin des outils suivants installés pour écraser avec succès cette version : Nœud Ganache-Cli Truffe Réagir Infure CSS vent arrière SDK CometChat Métamasque Fil Installation des dépendances Vérifiez que NodeJs est déjà installé sur votre machine, et si ce n'est pas le cas, installez-le depuis . Exécutez à nouveau le code sur le terminal pour vous assurer qu'il est installé. Installation de NodeJs ICI Exécutez les commandes ci-dessous dans votre terminal pour installer ces packages critiques globalement. Installation de Yarn, Ganache-cli et Truffle npm i -g yarn npm i -g truffle npm i -g ganache-cli Pour confirmer l'installation, entrez le code suivant dans le terminal. yarn --version && ganache-cli --version && truffle version de démarrage Web 3.0 à l'aide des commandes ci-dessous. Cela garantit que nous sommes tous sur la même longueur d'onde et que nous utilisons le même logiciel. Clonage du projet de démarrage Web3 Clonez le projet git clone https://github.com/Daltonic/timelessNFT Excellent, veuillez remplacer le fichier par ce qui suit : **package.json** { "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" ] } } Après avoir remplacé les packages comme indiqué, exécutez sur votre terminal pour charger tous les packages avec les versions spécifiées. **yarn install** Configuration du SDK CometChat Suivez les étapes ci-dessous pour configurer le ; à la fin, vous devez enregistrer ces clés en tant que variable d'environnement. SDK CometChat Rendez-vous sur le tableau de bord et créez un compte. ÉTAPE 1 : CometChat Connectez-vous au tableau de bord , uniquement après votre inscription. ÉTAPE 2 : CometChat À partir du tableau de bord, ajoutez une nouvelle application appelée ÉTAPE 3 : timelessNFT. Sélectionnez l'application que vous venez de créer dans la liste. ÉTAPE 4 : À partir du démarrage rapide, copiez , et dans votre fichier . Voir l'image et l'extrait de code. ÉTAPE 5 : APP_ID REGION AUTH_KEY .env Remplacez les clés d'espace réservé par leurs valeurs appropriées. REACT_COMET_CHAT REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=****************************** Configuration de l'application Infura Rendez-vous sur et créez un compte. ÉTAPE 1 : Infura À partir du tableau de bord, créez un nouveau projet. ÉTAPE 2 : Copiez l'URL du point de terminaison WebSocket du réseau de test dans votre fichier . ÉTAPE 3 : Rinkeby .env Après cela, entrez votre phrase secrète Metamask et la clé privée de votre compte préféré. Si vous avez suivi les instructions correctement, vos variables d'environnement devraient maintenant ressembler à ceci. ENDPOINT_URL=*************************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=****************************** Consultez la section ci-dessous si vous ne savez pas comment accéder à votre clé privée. Accéder à votre clé privée Metamask Assurez-vous que Rinkeby est sélectionné comme réseau de test dans votre extension de navigateur Metamask. Ensuite, sur le compte préféré, cliquez sur la ligne pointillée verticale et choisissez les détails du compte. Veuillez voir l'image ci-dessous. ÉTAPE 1 : Saisissez votre mot de passe dans le champ prévu à cet effet et cliquez sur le bouton de confirmation, cela vous permettra d'accéder à la clé privée de votre compte. ÉTAPE 2 : Cliquez sur "exporter la clé privée" pour voir votre clé privée. Assurez-vous de ne jamais exposer vos clés sur une page publique telle que . C'est pourquoi nous l'ajoutons en tant que variable d'environnement. ÉTAPE 3 : Github Copiez votre clé privée dans votre fichier . Voir l'image et l'extrait de code ci-dessous. ÉTAPE 4 : .env ENDPOINT_URL=*************************** SECRET_KEY=****************** DEPLOYER_KEY=********************** REACT_APP_COMET_CHAT_REGION=** REACT_APP_COMET_CHAT_APP_ID=************** REACT_APP_COMET_CHAT_AUTH_KEY=****************************** En ce qui concerne votre , vous devez coller votre phrase secrète dans l'espace prévu dans le fichier d'environnement. SECRET_KEY Metamask Le contrat intelligent NFT intemporel Voici le code complet du contrat intelligent ; Passons en revue toutes les fonctions et variables une par une. // 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; } } Dans le code ci-dessous, nous avons informé le compilateur solidity de l'identifiant de licence et des versions de compilateur qualifiées pour compiler ce code. Importations de code et informations contractuelles // 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... } De plus, ce contrat intelligent utilise certains contrats intelligents openzepplin. Vous devez vous assurer de les placer dans le même répertoire que celui illustré dans l'image ci-dessous. openzepplin's ces contrats intelligents comme indiqué dans l'image ci-dessus. Visitez ce lien et téléchargez Déclarations des variables d'état 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; Nous avons spécifié que nous utilisions la bibliothèque de chaînes pour effectuer des opérations à . Ensuite, nous avons également déclaré des mappages pour enregistrer les œuvres d'art NFT frappées et également pour connaître le propriétaire actuel d'un jeton. uint string Ensuite, nous avons précisé les autres variables essentielles pour capturer le compte de l'artiste, les frais de redevance, l'offre actuelle, le total des transactions qui ont été effectuées sur la plateforme et le coût d'un NFT. Mise en place d'événements et de structures 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; Dans le code précédent, nous avons un événement de vente qui émet des données à partir de toute transaction qui se produit sur le contrat intelligent, qu'il s'agisse de frappe ou de transfert NFT. Nous avons conçu une structure de transaction pour collecter des données sur les NFT frappés ou transférés. En utilisant la structure de transaction que nous avons définie, nous avons créé deux variables appelées et . transactions minted Initialisation du constructeur constructor( string memory _name, string memory _symbol, uint256 _royalityFee, address _artist ) ERC721(_name, _symbol) { royalityFee = _royalityFee; artist = _artist; } Le constructeur prend quatre paramètres pour initialiser le contrat intelligent. Un nom symbolique, un symbole, un compte d'artiste et une redevance par transaction. Le nom et le symbole du jeton sont ensuite transmis au contrat intelligent lors du déploiement. ERC721 L'algorithme de la fonction de menthe 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 fonction ci-dessus est responsable de la création de nouveaux jetons sur le contrat intelligent. L'appelant de cette méthode doit fournir quatre paramètres qui incluent ; un titre NFT, une description, un URI de métadonnées et le prix de vente du NFT après la frappe. Des validations sont effectuées pour s'assurer que le NFT frappe et sont effectuées en conséquence avec des paiements effectués pour chaque frappe. De plus, la validation garantit que chaque illustration est liée de manière unique à un jeton et qu'aucun autre jeton ne porte la même illustration. Enfin pour la validation, on s'est assuré que l'appelant de cette méthode n'est pas le déployeur du smart contract, c'est pour s'assurer qu'on ne confond pas trop mal les choses. Ensuite dans la fonction se trouve la règle de partage de paiement. Le pourcentage de redevance va à l'artiste et le reste des éthers va au propriétaire. Ensuite, nous avons enregistré ce NFT dans le tableau créé et émis un événement de vente. Enfin, nous avons frappé le NFT tout en enregistrant l'adresse de l'appelant en tant que propriétaire du jeton. L'algorithme de la fonction de transfert 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 fonction ci-dessus prend un identifiant NFT et effectue un achat du NFT en fonction du prix fixé par le monnayeur (propriétaire). Les validations nécessaires sont effectuées pour empêcher les propriétaires d'acheter leurs NFT et les autres d'acheter avec zéro éther. Ensuite, une redevance est envoyée au compte de l'artiste et le propriétaire actuel du NFT conserve le reste. Chaque transfert de jeton est enregistré dans un tableau de transactions pour garder une trace de toutes les transactions effectuées sur la plateforme. Par la suite, un événement de vente est à nouveau émis pour cet achat afin d'enrichir les données journalisées sur l'EVM. Autres fonctions essentielles // 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; } Et voilà pour le développement du contrat intelligent, nous allons ensuite plonger dans la construction des composants de l'interface utilisateur avec ReactJs. Configuration du script de déploiement Une autre chose à faire avec le contrat intelligent est de configurer le script de déploiement. Sur le projet, dirigez-vous vers le dossier >> et mettez-le à jour avec l'extrait de code ci-dessous. migrations 2_deploy_contracts.js 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] ) } Superbe, nous venons de terminer le smart contract pour notre application ; il est maintenant temps de se lancer dans l'interface DApp. Si vous avez besoin d'un tuteur privé pour vous aider à apprendre le développement de contrats intelligents, . réservez vos cours avec moi Développer le front-end La partie avant est composée de nombreux composants et pièces. Tous les composants, vues et périphériques seront créés par nous. Composant d'en-tête Ce composant a été créé avec tailwind CSS et utilise le bouton rose pour accéder au portefeuille Metamask. Les codes ci-dessous illustrent la programmation. Connect Wallet 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 Composant de héros Ce composant est chargé d'afficher le portefeuille connecté et également de lancer le modal utilisé pour créer un nouveau NFT. De plus, il est responsable de la connexion ou de l'inscription des utilisateurs pour des discussions en tête-à-tête avec un vendeur d'un NFT. Voici le code responsable de ces actions. 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 Volet Oeuvres Ce composant est responsable du rendu de la liste des NFT créés sur la plate-forme à l'aide des cartes CSS Tailwind magnifiquement conçues. Chaque carte a une image NFT, un titre, une description, un prix et un propriétaire. Voir les codes ci-dessous pour sa mise en œuvre. 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 Composante Transactions Ce composant est responsable du rendu de toutes les transactions qui ont eu lieu dans notre contrat intelligent. Une transaction, par exemple, serait qu'Alison achète un NFT à Duke. Cet achat sera capturé dans ce composant comme une transaction. Voir l'extrait ci-dessous. 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 Composant de pied de page Ce composant affiche simplement de beaux liens en bas de page, il ne fait pas grand-chose en termes de fonctionnalités mais complète l'interface utilisateur. Ses codes sont écrits ci-dessous. 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 Fantastique, c'est tout pour les composants évidents, incluons les composants cachés qui ne sont invoqués que via une interface modale. Créer un composant NFT Ce composant est chargé de créer de nouveaux NFT en fournissant une image, un titre, un prix et une description. Une fois que vous avez cliqué sur le bouton , l'image est téléchargée sur IPFS (Inter Planetary File System) et une URL d'image est renvoyée. Mint Now L'URL de l'image renvoyée ainsi que les données NFT fournies dans le formulaire sont envoyées à notre contrat intelligent pour frappe, immédiatement après que l'utilisateur autorise la transaction avec son portefeuille Metamask. A l'issue de la transaction, le NFT est alors répertorié parmi les œuvres d'art, et les acheteurs intéressés peuvent alors les acheter et même modifier leurs prix. Voir le code ci-dessous pour plus de détails. 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 Afficher le composant NFT Ce composant affiche plus d'informations sur un NFT spécifique, offrant au propriétaire un bouton pour modifier le prix et à l'acheteur un bouton pour acheter le NFT ou discuter avec le vendeur. Voir le code ci-dessous pour plus de détails. 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 Mettre à jour le composant NFT Ce composant est chargé de modifier le prix du NFT. Cette action ne peut être effectuée que par le propriétaire du NFT. Bien que cette option soit disponible, des frais d'essence seront nécessaires pour effectuer ces changements. Une fois qu'un NFT échange une main avec un autre acheteur, le nouveau propriétaire peut décider d'augmenter le prix, et c'est pourquoi cette option a été rendue disponible. Voir l'extrait de code ci-dessous. 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 Composant ChatList Ce composant révèle les discussions récentes qu'un utilisateur a eues avec un vendeur ou un acheteur sur la plateforme. Le composant capture également le dernier message envoyé dans leur conversation. Un clic sur chaque conversation mènera à l'interface de chat. Voir le code ci-dessous. 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 Composant de chat Ce composant est chargé d'engager deux utilisateurs dans une conversation en tête-à-tête. L'image ci-dessus montre une conversation entre un acheteur et un vendeur sur la plateforme, depuis deux navigateurs différents. Voir le code ci-dessous pour son implémentation. 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 Bien, maintenant que nous avons inclus ces composants fantastiques, terminons avec ces deux derniers. Composant de chargement Ce composant affiche simplement l'activité et le statut en cours lorsqu'une transaction est en cours. Voir le code ci-dessous. 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 Ce fichier regroupe le composant ci-dessus discuté dans ce travail. C'est ainsi que fonctionne une architecture ReactJs. Voir les codes ci-dessous. Le composant App 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 Fantastique, nous venons de terminer l'intégration des différents composants, scellons-le avec les autres parties de ce projet. Autres fichiers essentiels Cette application utilise un magasin de gestion d'état, un SDK CometChat et un fichier de service contractuel. Examinons-les les uns après les autres. Ce fichier de gestion d'état utilise le package npm react . C'est simple, rapide et plus facile que Redux. Toutes les variables et fonctions globales utilisées dans cette application ont été créées dans ce magasin. Le magasin react-hooks-global-state A la racine du projet, allez dans le répertoire et créez un dossier nommé . Maintenant, dans ce dossier de magasin, créez un fichier appelé et collez les codes ci-dessous à l'intérieur. src store index.js 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, } Ce fichier contient toutes les fonctions essentielles pour communiquer avec le SDK CometChat. Voir les codes ci-dessous. Le service CometChat 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, } Ce fichier contient toutes les fonctions et procédures chargées d'interagir avec le contrat intelligent sur la blockchain à l'aide de la bibliothèque Web3. Voir les codes ci-dessous. Le fichier de service de contrat 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 } et incluez-le dans le dossier assets de votre répertoire racine. Et avec cela, vous avez réussi à inclure tout ce qui est nécessaire pour exécuter cette application. Project Assets Téléchargez ce logo Démarrage du serveur Pour poursuivre cette étape, migrez le contrat intelligent vers le Web afin de pouvoir interagir avec lui. Exécutez le code suivant sur votre terminal. truffle migrate --network rinkeby Le code ci-dessus enverra votre contrat intelligent au serveur à l'aide d'Infura RPC. Vous pouvez également configurer une blockchain locale à l'aide du serveur ganache-cli que nous avons configuré au début de ce didacticiel. Exécutez simplement le code ci-dessous pour l'expédier à votre serveur blockchain local si vous préférez cette façon. Ouvrez un terminal, exécutez et sur un autre terminal, exécutez ou . **ganache-cli -d** **truffle migrate** **truffle deploy** Notez que si vous utilisez comme EVM, vous devez également ajouter le serveur localhost à Metamask et importer les clés privées générées par ganache. . ganache-cli Voir Démarrage de l'environnement de développement pour plus d'informations Si vous avez besoin de mon aide pour résoudre des problèmes sur votre projet, . consultez-moi sur cette page Maintenant, lancez pour démarrer votre application React. Et voilà, vous l'avez avec cette version sur le marché NFT. yarn start . Regardez mes tutoriels Web3 GRATUITS sur Youtube maintenant Conclusion Nous sommes arrivés à la ligne d'arrivée de cette version NFT, je sais que vous avez accumulé une tonne de valeur ajoutée avec moi. Quel que soit votre niveau, si vous souhaitez progresser plus rapidement dans vos compétences en développement web3, entrez . dans mon cours privé Jusqu'à la prochaine fois, continuez à l'écraser! A propos de l'auteur Gospel Darlington est un développeur de blockchain complet avec plus de ans d'expérience dans l'industrie du développement de logiciels. 6+ En combinant le développement de logiciels, l'écriture et l'enseignement, il montre comment créer des applications décentralisées sur des réseaux blockchain compatibles EVM. Ses piles incluent , , , , , , , , etc. JavaScript React Vue Angular Node React Native NextJs Solidity Pour plus d'informations à son sujet, veuillez visiter et suivre sa page sur , , ou sur son . Twitter GitHub LinkedIn site Web