paint-brush
Cách xây dựng thị trường NFT có lợi nhuận với React, Solidity và CometChatby@daltonic
13,782
13,782

Cách xây dựng thị trường NFT có lợi nhuận với React, Solidity và CometChat

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

Hướng dẫn này sẽ dạy bạn cách tạo thị trường NFT có lợi nhuận và được thiết kế tốt với chức năng trò chuyện. Nó sẽ sử dụng công nghệ web3 để tạo mã thông báo không thể thay thế (NFT) và số hóa tài sản trong khi vẫn giữ quyền sở hữu. Bạn sẽ cần các công cụ sau để phá hủy thành công bản dựng này: Node, Ganache-Cli và Truffle.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail

Coin Mentioned

Mention Thumbnail
featured image - Cách xây dựng thị trường NFT có lợi nhuận với React, Solidity và CometChat
Darlington Gospel  HackerNoon profile picture


Những gì bạn sẽ xây dựng, hãy xem bản demo trên mạng thử nghiệm Rinkeby và git repo tại đây…


Giới thiệu

Web3 là tương lai của Internet, và tất cả chúng ta phải học hỏi và nắm bắt công nghệ này. Các trường hợp sử dụng của nó tiếp tục xuất hiện và có tác động tích cực đến thế giới.


Một ứng dụng tuyệt vời của công nghệ web3 là tạo ra các mã thông báo không thể thay thế (NFT), là một giải pháp khả thi để số hóa tài sản trong khi vẫn giữ được quyền sở hữu.



Hướng dẫn này sẽ dạy bạn cách tạo thị trường NFT có lợi nhuận và được thiết kế tốt với chức năng trò chuyện.


Đặt các lớp học riêng của bạn với tôi nếu bạn cần ai đó giúp bạn học cách phát triển web3 nhanh hơn.


Với điều đó đã nói, chúng ta hãy bắt đầu ...


Xem kênh Youtube của tôi để biết các hướng dẫn về web3 MIỄN PHÍ ngay bây giờ .

Điều kiện tiên quyết

Bạn sẽ cần cài đặt các công cụ sau để phá vỡ thành công bản dựng này:

  • Nút
  • Ganache-Cli
  • Truffle
  • Phản ứng
  • Infura
  • CSS Tailwind
  • SDK CometChat
  • Metamask
  • Sợi

Cài đặt phụ thuộc

Cài đặt NodeJs Kiểm tra xem NodeJs đã được cài đặt trên máy của bạn chưa và nếu chưa, hãy cài đặt nó từ ĐÂY . Chạy lại mã trên thiết bị đầu cuối để đảm bảo nó đã được cài đặt.


Cài đặt Yarn, Ganache-cli và Truffle Chạy các lệnh bên dưới trong thiết bị đầu cuối của bạn để cài đặt các gói quan trọng này trên toàn cầu.


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


Để xác nhận cài đặt, hãy nhập mã sau vào thiết bị đầu cuối.


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

Sao chép dự án khởi động Web3 Sao chép dự án khởi động web 3.0 bằng cách sử dụng các lệnh bên dưới. Điều này đảm bảo rằng tất cả chúng ta đều ở trên cùng một trang và sử dụng cùng một phần mềm.


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


Tuyệt vời, vui lòng thay thế tệp **package.json** bằng tệp sau:


 { "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" ] } }


Sau khi thay thế các gói theo chỉ dẫn, hãy chạy **yarn install** trên thiết bị đầu cuối của bạn để tải tất cả các gói với các phiên bản được chỉ định.

Định cấu hình SDK CometChat

Làm theo các bước bên dưới để định cấu hình SDK CometChat ; cuối cùng, bạn phải lưu các khóa này dưới dạng một biến môi trường.


BƯỚC 1: Truy cập Bảng điều khiển CometChat và tạo tài khoản.


BƯỚC 2: Đăng nhập vào bảng điều khiển CometChat , chỉ sau khi đăng ký.


BƯỚC 3: Từ trang tổng quan, thêm một ứng dụng mới có tên là TimelessNFT.


BƯỚC 4: Chọn ứng dụng bạn vừa tạo từ danh sách.


BƯỚC 5: Từ Bắt đầu nhanh, sao chép APP_ID , REGIONAUTH_KEY vào tệp .env của bạn. Xem hình ảnh và đoạn mã.


Thay thế các khóa chỗ REACT_COMET_CHAT bằng các giá trị thích hợp của chúng.

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

Cấu hình ứng dụng Infura

BƯỚC 1: Truy cập Infura và tạo một tài khoản.



BƯỚC 2: Từ bảng điều khiển, tạo một dự án mới.



BƯỚC 3: Sao chép URL điểm cuối WebSocket của mạng thử nghiệm Rinkeby vào tệp .env của bạn.



Sau đó, nhập cụm từ bí mật Metamask của bạn và khóa cá nhân của tài khoản ưa thích. Nếu bạn làm theo đúng hướng dẫn, các biến môi trường của bạn bây giờ sẽ trông như thế này.


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


Xem phần bên dưới nếu bạn không biết cách truy cập khóa riêng tư của mình.

Truy cập khóa cá nhân Metamask của bạn

BƯỚC 1: Đảm bảo Rinkeby được chọn làm mạng thử nghiệm trong tiện ích mở rộng trình duyệt Metamask của bạn. Sau đó, trên tài khoản ưa thích, hãy nhấp vào đường chấm dọc và chọn chi tiết tài khoản. Vui lòng xem hình ảnh bên dưới.


BƯỚC 2: Nhập mật khẩu của bạn vào trường được cung cấp và nhấp vào nút xác nhận, thao tác này sẽ cho phép bạn truy cập vào khóa cá nhân tài khoản của mình.


BƯỚC 3: Nhấp vào "xuất khóa cá nhân" để xem khóa cá nhân của bạn. Đảm bảo rằng bạn không bao giờ để lộ khóa của mình trên một trang công khai như Github . Đó là lý do tại sao chúng tôi thêm nó như một biến môi trường.


BƯỚC 4: Sao chép khóa cá nhân của bạn vào tệp .env của bạn. Xem hình ảnh và đoạn mã bên dưới.



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


Đối với SECRET_KEY của bạn, bạn được yêu cầu dán cụm từ bí mật Metamask của mình vào khoảng trống được cung cấp trong tệp môi trường.

Hợp đồng thông minh NFT vượt thời gian

Đây là mã hợp đồng thông minh hoàn chỉnh; Chúng ta hãy xem xét lần lượt tất cả các hàm và biến.


 // 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; } }


Nhập mã và thông tin hợp đồng Trong đoạn mã dưới đây, chúng tôi đã thông báo cho trình biên dịch solidity về mã nhận dạng giấy phép và các phiên bản trình biên dịch đủ điều kiện để biên dịch mã này.


 // 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... }


Ngoài ra, hợp đồng thông minh này sử dụng một số hợp đồng thông minh ERC721 openzepplin's openzepplin. Bạn phải đảm bảo rằng bạn đặt chúng trong cùng một thư mục như trong hình dưới đây.


Truy cập liên kết này và tải xuống các hợp đồng thông minh này như được hiển thị trong hình ảnh ở trên.


Khai báo biến trạng thái

 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;


Chúng tôi đã chỉ rõ rằng chúng tôi đang sử dụng thư viện chuỗi để thực hiện các thao tác uint tới string . Tiếp theo, chúng tôi cũng khai báo các ánh xạ để ghi lại các tác phẩm nghệ thuật NFT đúc và cũng để biết chủ sở hữu hiện tại của mã thông báo.


Sau đó, chúng tôi chỉ định các biến số thiết yếu khác để nắm bắt tài khoản nghệ sĩ, phí bản quyền, nguồn cung cấp hiện tại, tổng số giao dịch đã được thực hiện trên nền tảng và chi phí của một NFT.


Thiết lập các sự kiện và cấu trúc

 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;


Trong mã trước, chúng tôi có một sự kiện bán hàng phát ra dữ liệu từ bất kỳ giao dịch nào xảy ra trên hợp đồng thông minh, cho dù đó là khi đúc tiền hay chuyển giao NFT.


Chúng tôi đã thiết kế một cấu trúc giao dịch để thu thập dữ liệu về các NFT được đúc hoặc chuyển giao. Sử dụng cấu trúc giao dịch mà chúng tôi đã xác định, chúng tôi tạo ra hai biến được gọi là transactionsminted .


Khởi tạo hàm tạo

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


Hàm tạo nhận bốn tham số để khởi tạo hợp đồng thông minh. Tên mã thông báo, biểu tượng, tài khoản nghệ sĩ và phí bản quyền cho mỗi giao dịch. Tên và biểu tượng mã thông báo sau đó được chuyển vào hợp đồng thông minh ERC721 trong quá trình triển khai.


Thuật toán hàm bạc hà

 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; }


Chức năng trên chịu trách nhiệm tạo ra các mã thông báo mới trên hợp đồng thông minh. Người gọi phương thức này phải cung cấp bốn tham số bao gồm; tiêu đề NFT, mô tả, URI siêu dữ liệu và giá bán của NFT sau khi đúc.


Việc xác nhận được thực hiện để đảm bảo rằng NFT đang được đúc và được thực hiện tương ứng với các khoản thanh toán được thực hiện cho mỗi lần đúc. Ngoài ra, việc xác thực đảm bảo rằng mỗi tác phẩm nghệ thuật được liên kết duy nhất với một mã thông báo và không có mã thông báo nào khác mang cùng một tác phẩm nghệ thuật. Cuối cùng để xác thực, chúng tôi đảm bảo rằng người gọi phương thức này không phải là người triển khai hợp đồng thông minh, điều này nhằm đảm bảo rằng chúng tôi không trộn lẫn mọi thứ quá tệ.


Tiếp theo trong chức năng là quy tắc chia sẻ thanh toán. Phần trăm tiền bản quyền thuộc về nghệ sĩ và phần còn lại của ê-kip thuộc về chủ sở hữu.


Sau đó, chúng tôi ghi lại NFT đó bên trong mảng đúc và tạo ra một sự kiện bán hàng. Cuối cùng, chúng tôi đã đúc NFT trong khi ghi lại địa chỉ của người gọi là chủ sở hữu của mã thông báo.


Thuật toán hàm truyền 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; }


Hàm trên lấy một id NFT và thực hiện mua NFT theo giá đã đặt của người khai thác (chủ sở hữu).


Các xác nhận cần thiết được thực hiện để cản trở chủ sở hữu mua NFT của họ và những người khác mua bằng không ete.


Tiếp theo, một khoản phí bản quyền được gửi đến tài khoản nghệ sĩ và chủ sở hữu hiện tại của NFT sẽ giữ phần còn lại.


Mỗi lần chuyển mã thông báo được ghi lại trong một mảng giao dịch để theo dõi tất cả các giao dịch được thực hiện trên nền tảng.


Sau đó, một sự kiện bán hàng một lần nữa được phát ra cho giao dịch mua này để làm phong phú thêm dữ liệu đã ghi trên EVM.


Các chức năng cần thiết khác

 // 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; }


Và bạn đã có nó để phát triển hợp đồng thông minh, tiếp theo chúng ta sẽ đi sâu vào việc xây dựng các thành phần giao diện người dùng với ReactJs.

Định cấu hình Tập lệnh Triển khai

Một điều nữa cần làm với hợp đồng thông minh là định cấu hình tập lệnh triển khai.

Trên dự án, hãy chuyển đến thư mục di chuyển >> 2_deploy_contracts.js và cập nhật nó bằng đoạn mã bên dưới.


 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] ) }


Tuyệt vời, chúng tôi vừa hoàn thành hợp đồng thông minh cho ứng dụng của mình; bây giờ là lúc bắt đầu trên giao diện DApp. Nếu bạn cần một gia sư riêng để giúp bạn học cách phát triển hợp đồng thông minh, hãy đăng ký các lớp học của bạn với tôi .

Phát triển giao diện người dùng

Mặt trước được tạo thành từ nhiều thành phần và bộ phận. Tất cả các thành phần, khung nhìn và thiết bị ngoại vi sẽ do chúng tôi tạo ra.


Thành phần tiêu đề


Thành phần này được tạo bằng CSS tailwind và sử dụng nút Connect Wallet màu hồng để truy cập ví Metamask. Các mã dưới đây minh họa chương trình.


 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


Thành phần anh hùng


Thành phần này chịu trách nhiệm hiển thị ví được kết nối và cũng để khởi chạy phương thức được sử dụng để tạo NFT mới. Ngoài ra, nó chịu trách nhiệm đăng nhập hoặc đăng ký người dùng cho các cuộc trò chuyện trực tiếp với người bán NFT. Đây là mã chịu trách nhiệm cho các hành động này.


 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


Thành phần tác phẩm nghệ thuật

Thành phần này chịu trách nhiệm hiển thị danh sách các NFT được tạo trên nền tảng bằng cách sử dụng các thẻ CSS tailwind được chế tạo đẹp mắt. Mỗi thẻ có một hình ảnh NFT, tiêu đề, mô tả, giá cả và chủ sở hữu. Xem các mã bên dưới để triển khai 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


Thành phần giao dịch

Thành phần này chịu trách nhiệm hiển thị tất cả các giao dịch đã diễn ra trong hợp đồng thông minh của chúng tôi. Ví dụ, một giao dịch sẽ là Alison mua một NFT từ Duke. Giao dịch mua này sẽ được ghi lại trong thành phần này như một giao dịch. Xem đoạn mã dưới đây.


 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


Thành phần chân trang


Thành phần này chỉ đơn giản là hiển thị một số liên kết đẹp ở cuối trang, nó không có tác dụng gì nhiều khi nói đến chức năng nhưng bổ sung cho giao diện người dùng. Mã của nó được viết dưới đây.


 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


Tuyệt vời, đó là đối với các thành phần hiển nhiên, hãy bao gồm các thành phần ẩn chỉ được gọi thông qua một giao diện phương thức.


Thành phần CreateNFT


Thành phần này có nhiệm vụ tạo ra các NFT mới bằng cách cung cấp hình ảnh, tiêu đề, giá cả và mô tả. Sau khi nhấp vào nút Mint Now , hình ảnh được tải lên IPFS (Hệ thống tệp liên hành tinh) và URL hình ảnh được trả về.


URL hình ảnh trả về cùng với dữ liệu NFT được cung cấp trong biểu mẫu được gửi đến hợp đồng thông minh của chúng tôi để đúc tiền, ngay sau khi người dùng cho phép giao dịch bằng ví Metamask của họ.


Sau khi hoàn thành giao dịch, NFT sau đó được liệt kê trong số các tác phẩm nghệ thuật và những người mua quan tâm sau đó có thể mua chúng và thậm chí thay đổi giá của chúng. Xem mã bên dưới để biết chi tiết.


 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


Thành phần ShowNFT

Thành phần này hiển thị thêm thông tin về một NFT cụ thể, cung cấp cho chủ sở hữu một nút để thay đổi giá và người mua một nút để mua NFT hoặc trò chuyện với người bán. Xem mã bên dưới để biết thêm chi tiết.


 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


Thành phần UpdateNFT

Thành phần này có nhiệm vụ thay đổi giá của NFT. Chỉ chủ sở hữu NFT mới có thể thực hiện hành động này. Mặc dù tùy chọn này khả dụng, nhưng sẽ mất một số phí xăng để thực hiện những thay đổi này. Khi NFT trao đổi một tay với người mua khác, chủ sở hữu mới có thể quyết định tăng giá và đó là lý do tại sao tùy chọn này được cung cấp. Xem đoạn mã bên dưới.


 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


Thành phần ChatList

Thành phần này tiết lộ các cuộc trò chuyện gần đây mà người dùng đã thực hiện với người bán hoặc người mua trên nền tảng. Thành phần này cũng ghi lại tin nhắn cuối cùng được gửi trong cuộc trò chuyện của họ. Một cú nhấp chuột vào mỗi cuộc trò chuyện sẽ dẫn đến giao diện trò chuyện. Xem mã bên dưới.


 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


Thành phần trò chuyện

Thành phần này chịu trách nhiệm thu hút hai người dùng trong cuộc trò chuyện trực tiếp. Hình ảnh trên cho thấy cuộc trò chuyện giữa người mua và người bán trên nền tảng, từ hai trình duyệt khác nhau. Xem đoạn mã dưới đây để biết cách triển khai.


 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


Tuyệt vời, bây giờ chúng tôi đã bao gồm những thành phần tuyệt vời đó, hãy kết thúc nó với hai thành phần cuối cùng này.


Đang tải thành phần

Thành phần này chỉ hiển thị hoạt động và trạng thái hiện tại khi một giao dịch đang diễn ra. Xem mã bên dưới.


 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


Thành phần ứng dụng Tệp này tập hợp thành phần trên được thảo luận trong tác phẩm này. Đây chỉ là cách kiến trúc ReactJs hoạt động. Xem các mã bên dưới.


 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


Tuyệt vời, chúng tôi vừa hoàn thành việc tích hợp các thành phần khác nhau, hãy kết hợp nó với các phần khác của dự án này.

Các tệp cần thiết khác

Ứng dụng này sử dụng cửa hàng quản lý nhà nước, SDK CometChat và tệp dịch vụ hợp đồng. Chúng ta hãy xem xét chúng lần lượt.


Cửa hàng Tệp quản lý trạng thái này sử dụng gói npm react react-hooks-global-state . Nó đơn giản, nhanh chóng và dễ dàng hơn Redux. Tất cả các biến và hàm toàn cục được sử dụng trong ứng dụng này đã được tạo trong cửa hàng này.


Tại thư mục gốc của dự án, hãy chuyển đến thư mục src và tạo một thư mục có tên là store . Bây giờ, trong thư mục lưu trữ này, hãy tạo một tệp có tên là index.js và dán các mã bên dưới vào bên trong nó.


 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, }


Dịch vụ CometChat Tệp này chứa tất cả các chức năng cần thiết để giao tiếp với SDK CometChat. Xem các mã bên dưới.


 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, }


Tệp Dịch vụ Hợp đồng Tệp này chứa tất cả các chức năng và thủ tục chịu trách nhiệm tương tác với hợp đồng thông minh trên blockchain bằng cách sử dụng thư viện Web3. Xem các mã bên dưới.


 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 }


Tài sản dự án Tải xuống biểu trưng này và đưa nó vào bên trong thư mục tài sản trong thư mục gốc của bạn. Và cùng với đó, bạn đã bao gồm thành công tất cả những gì cần thiết để chạy ứng dụng này.

Khởi động máy chủ

Để tiếp tục bước này, hãy di chuyển hợp đồng thông minh sang Web để bạn có thể tương tác với nó. Chạy mã sau trên thiết bị đầu cuối của bạn.


 truffle migrate --network rinkeby


Đoạn mã trên sẽ gửi hợp đồng thông minh của bạn đến máy chủ bằng Infura RPC.


Bạn cũng có thể thiết lập một blockchain cục bộ bằng cách sử dụng máy chủ ganache-cli mà chúng tôi đã thiết lập ở phần đầu của hướng dẫn này. Chỉ cần chạy mã bên dưới để gửi nó đến máy chủ blockchain cục bộ của bạn nếu bạn thích cách đó.


Mở một lần chạy đầu cuối **ganache-cli -d** và trên một lần chạy đầu cuối khác **truffle migrate** hoặc **truffle deploy** .


Lưu ý, nếu bạn đang sử dụng ganache-cli làm EVM của mình, bạn cũng phải thêm máy chủ localhost vào Metamask và nhập các khóa riêng do ganache tạo ra. Xem Khởi động Môi trường Phát triển để biết hướng dẫn .


Nếu bạn cần sự trợ giúp của tôi để giải quyết các vấn đề trong dự án của bạn, hãy tham khảo ý kiến của tôi trên trang này .

Bây giờ, chạy yarn start để khởi động ứng dụng phản ứng của bạn. Và bạn đã có nó với bản dựng này trên thị trường NFT.


Xem hướng dẫn web3 MIỄN PHÍ của tôi trên Youtube ngay bây giờ .

Sự kết luận

Chúng ta đã đi đến kết thúc của quá trình xây dựng NFT này, tôi biết bạn đã có rất nhiều giá trị xây dựng cùng với tôi.


Dù bạn ở cấp độ nào, nếu bạn muốn phát triển nhanh hơn về kỹ năng phát triển web3 của mình, hãy tham gia lớp học riêng của tôi .


Cho đến lần sau, hãy tiếp tục nghiền nát nó!


Thông tin về các Tác giả

Mừng Darlington là một nhà phát triển chuỗi khối đầy đủ với hơn 6+ năm kinh nghiệm trong ngành phát triển phần mềm.


Bằng cách kết hợp Phát triển phần mềm, viết và giảng dạy, anh ấy trình bày cách xây dựng các ứng dụng phi tập trung trên các mạng blockchain tương thích với EVM.


Các ngăn xếp của anh ấy bao gồm JavaScript , React , Vue , Angular , Node , React Native , NextJs , Solidity , v.v.


Để biết thêm thông tin về anh ấy, vui lòng truy cập và theo dõi trang của anh ấy trên Twitter , GitHub , LinkedIn hoặc trên trang web của anh ấy.