
Как создать интернет-магазин игр Web3 с помощью React, Solidity и CometChat
24 октября 2022 г.Разработка Web3 – это официально новый способ создания веб-приложений, и если вы еще этого не сделали, вам нужно наверстать упущенное. Чтобы освоить создание веб-3-приложений, нужно понять смарт-контракты, интерфейсную структуру, такую как React, и понять, как связать смарт-контракт с интерфейсом.
Из этого руководства вы узнаете, как создать децентрализованный интернет-магазин web3 для продажи игровых предметов с использованием нативной валюты ETH.
Это приложение включает в себя уровень смарт-контракта, внешний интерфейс, в котором происходят все взаимодействия со смарт-контрактом, и функцию анонимного чата с использованием SDK CometChat.
Если вы готовы сокрушить эту сборку, давайте начнем.
Что вы будете создавать: см. демонстрацию в тесте Гёрли. сеть и репозиторий git здесь.
Необходимое условие
Для сборки вместе со мной вам потребуются следующие инструменты:
- NodeJs (очень важно)
- Эфиры
- Каска
- Реагировать
- Попутный ветер CSS
- Кометчат SDK
- Метамаска
- Пряжа
Установка зависимостей
Клонируйте начальный проект из этого репозитория Git на свой компьютер. Кроме того, не забудьте заменить его на название вашего предпочтительного проекта. См. приведенную ниже команду.
git clone https://github.com/Daltonic/tailwind_ethers_starter_kit <PROJECT_NAME>
cd <PROJECT_NAME>
Теперь откройте проект в VS Code или в предпочитаемом вами редакторе кода. Найдите файл package.json
и обновите его с помощью приведенных ниже кодов.
{
"name": "GameShop",
"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",
"deploy": "yarn hardhat run scripts/deploy.js --network localhost"
},
"dependencies": {
"@cometchat-pro/chat": "^3.0.10",
"@nomiclabs/hardhat-ethers": "^2.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.3",
"ethereum-waffle": "^3.4.4",
"ethers": "^5.6.9",
"hardhat": "^2.10.1",
"ipfs-http-client": "^57.0.3",
"moment": "^2.29.4",
"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",
"react-toastify": "^9.0.8",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@openzeppelin/contracts": "^4.5.0",
"@tailwindcss/forms": "0.4.0",
"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",
"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"
]
}
}
Заменив приведенные выше коды и сохранив их в файле package.json, выполните приведенную ниже команду, чтобы установить все перечисленные выше пакеты.
yarn install
Настройка SDK CometChat
Выполните следующие действия, чтобы настроить CometChat SDK; в конце вы должны сохранить эти ключи как переменную среды.
ШАГ 1: Перейдите на Панель управления CometChat и создайте учетную запись.
ШАГ 2: Войдите в панель управления CometChat только после регистрации.
ШАГ 3: На панели управления добавьте новое приложение под названием GameShop
.ШАГ 4: Выберите только что созданное приложение из списка.
ШАГ 5:
Из краткого руководства скопируйте APP_ID
, REGION
и AUTH_KEY
в свой файл .env
. См. изображение и фрагмент кода.
Замените ключи-заполнители REACT_COMET_CHAT
их соответствующими значениями.
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Файл **.env**
должен быть создан в корне вашего проекта.
Настройка приложения Alchemy
ШАГ 1:
Перейдите на сайт Alchemy и создайте учетную запись.
ШАГ 2: На панели инструментов создайте новый проект.
ШАГ 3:
Скопируйте URL-адрес конечной точки WebSocket или HTTPS тестовой сети Goerli
в свой файл .env
.
После этого введите закрытый ключ предпочитаемой учетной записи Metamask в DEPLOYER_KEY
в переменных среды и сохраните. Если вы правильно следовали инструкциям, ваши переменные среды теперь должны выглядеть так.
ENDPOINT_URL=***************************
DEPLOYER_KEY=**********************
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
См. раздел ниже, если вы не знаете, как получить доступ к своему закрытому ключу.
Извлечение закрытого ключа метамаски
ШАГ 1: Убедитесь, что в расширении браузера Metamask в качестве тестовой сети выбран Goerli, ~~Rinkeby~~ и старые тестовые сети устарели.
Затем в нужной учетной записи щелкните вертикальную пунктирную линию и выберите данные учетной записи. Пожалуйста, смотрите изображение ниже.
ШАГ 2: Введите свой пароль в соответствующее поле и нажмите кнопку подтверждения, это позволит вам получить доступ к закрытому ключу вашей учетной записи.
ШАГ 3:
Нажмите "экспортировать закрытый ключ", чтобы увидеть свой закрытый ключ. Убедитесь, что вы никогда не публикуете свои ключи на общедоступной странице, такой как Github
. Вот почему мы добавляем его как переменную среды.
ШАГ 4:
Скопируйте свой закрытый ключ в файл .env
. См. изображение и фрагмент кода ниже:
ENDPOINT_URL=***************************
DEPLOYER_KEY=**********************
REACT_APP_COMET_CHAT_REGION=**
REACT_APP_COMET_CHAT_APP_ID=**************
REACT_APP_COMET_CHAT_AUTH_KEY=******************************
Настройка скрипта Hardhat
В корне проекта откройте файл hardhat.config.js
и замените его содержимое следующими настройками.
require("@nomiclabs/hardhat-waffle");
require('dotenv').config()
module.exports = {
defaultNetwork: "localhost",
networks: {
hardhat: {
},
localhost: {
url: "http://127.0.0.1:8545"
},
goerli: {
url: process.env.ENDPOINT_URL,
accounts: [process.env.DEPLOYER_KEY]
}
},
solidity: {
version: '0.8.11',
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
paths: {
sources: "./src/contracts",
artifacts: "./src/abis"
},
mocha: {
timeout: 40000
}
}
Приведенный выше скрипт инструктирует каску по этим трем важным правилам.
* Сети: этот блок содержит настройки для выбранных вами сетей. При развертывании hardhat потребует от вас указать сеть для доставки смарт-контрактов.
* Solidity: здесь описывается версия компилятора, которую будет использовать каска для компиляции кода вашего смарт-контракта в bytecodes
и abi
.
* Пути: это просто информирует hardhat о расположении ваших смарт-контрактов, а также о местоположении для дампа вывода компилятора, который является abi.
Служебный файл блокчейна
Теперь, когда у нас настроены вышеуказанные конфигурации, давайте создадим смарт-контракт для этой сборки. В своем проекте перейдите в каталог **src**
и создайте новую папку с именем **contracts**
.
Внутри этой папки контрактов создайте новый файл с именем **Shop.sol**
, этот файл будет содержать всю логику, регулирующую деятельность смарт-контракта. Скопируйте, вставьте и сохраните приведенные ниже коды в файле **Shop.sol**
. См. полный код ниже.
//SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract Shop {
enum OrderEnum {
PLACED,
DELEVIRED,
CANCELED,
REFUNDED
}
struct ProductStruct {
uint id;
string sku;
address seller;
string name;
string imageURL;
string description;
uint price;
uint timestamp;
bool deleted;
uint stock;
}
struct OrderStruct {
uint pid;
uint id;
string sku;
string name;
string imageURL;
address buyer;
address seller;
uint qty;
uint total;
uint timestamp;
string destination;
string phone;
OrderEnum status;
}
struct CartStruct {
uint id;
uint qty;
}
struct BuyerStruct {
address buyer;
uint price;
uint qty;
uint timestamp;
}
struct ShopStats {
uint products;
uint orders;
uint sellers;
uint sales;
uint paid;
uint balance;
}
address public owner;
ShopStats public stats;
uint public fee;
ProductStruct[] products;
mapping(address => ProductStruct[]) productsOf;
mapping(uint => OrderStruct[]) ordersOf;
mapping(address => ShopStats) public statsOf;
mapping(uint => BuyerStruct[]) buyersOf;
mapping(uint => bool) public productExist;
mapping(uint => mapping(uint => bool)) public orderExist;
event Sale(
uint256 id,
address indexed buyer,
address indexed seller,
uint256 price,
uint256 timestamp
);
constructor(uint _fee) {
owner = msg.sender;
fee = _fee;
}
function createProduct(
string memory sku,
string memory name,
string memory description,
string memory imageURL,
uint price,
uint stock
) public payable returns (bool) {
require(msg.value >= fee, "Insufficient fund");
require(bytes(sku).length > 0, "sku cannot be empty");
require(bytes(name).length > 0, "name cannot be empty");
require(bytes(description).length > 0, "description cannot be empty");
require(bytes(imageURL).length > 0, "image URL cannot be empty");
require(price > 0, "price cannot be zero");
require(stock > 0, "stock cannot be zero");
productExist[stats.products] = true;
statsOf[msg.sender].products++;
stats.sellers++;
ProductStruct memory product;
product.id = stats.products++;
product.sku = sku;
product.seller = msg.sender;
product.name = name;
product.imageURL = imageURL;
product.description = description;
product.price = price;
product.stock = stock;
product.timestamp = block.timestamp;
products.push(product);
return true;
}
function updateProduct(
uint id,
string memory name,
string memory description,
string memory imageURL,
uint price,
uint stock
) public returns (bool) {
require(products[id].seller == msg.sender, "Unauthorize Personel");
require(bytes(name).length > 0, "name cannot be empty");
require(bytes(description).length > 0, "description cannot be empty");
require(price > 0, "price cannot be zero");
require(stock > 0, "stock cannot be zero");
ProductStruct memory product;
product.id = id;
product.seller = msg.sender;
product.name = name;
product.imageURL = imageURL;
product.description = description;
product.price = price;
product.stock = stock;
products[id] = product;
updateOrderDetails(product);
return true;
}
function updateOrderDetails(ProductStruct memory product) internal {
for(uint i=0; i < ordersOf[product.id].length; i++) {
OrderStruct memory order = ordersOf[product.id][i];
order.name = product.name;
order.imageURL = product.imageURL;
ordersOf[product.id][i] = order;
}
}
function deleteProduct(uint id) public returns (bool) {
require(products[id].seller == msg.sender, "Unauthorize Personel");
products[id].deleted = true;
return true;
}
function getProduct(uint id) public view returns (ProductStruct memory) {
require(productExist[id], "Product not found");
return products[id];
}
function getProducts() public view returns (ProductStruct[] memory) {
return products;
}
function createOrder(
uint[] memory ids,
uint[] memory qtys,
string memory destination,
string memory phone
) public payable returns (bool) {
require(msg.value >= totalCost(ids, qtys), "Insufficient amount");
require(bytes(destination).length > 0, "destination cannot be empty");
require(bytes(phone).length > 0, "phone cannot be empty");
stats.balance += totalCost(ids, qtys);
for(uint i = 0; i < ids.length; i++) {
if(productExist[ids[i]] && products[ids[i]].stock >= qtys[i]) {
products[ids[i]].stock -= qtys[i];
statsOf[msg.sender].orders++;
stats.orders++;
OrderStruct memory order;
order.pid = products[ids[i]].id;
order.id = ordersOf[order.pid].length; // order Id resolved
order.sku = products[ids[i]].sku;
order.buyer = msg.sender;
order.seller = products[ids[i]].seller;
order.name = products[ids[i]].name;
order.imageURL = products[ids[i]].imageURL;
order.qty = qtys[i];
order.total = qtys[i] * products[ids[i]].price;
order.timestamp = block.timestamp;
order.destination = destination;
order.phone = phone;
ordersOf[order.pid].push(order);
orderExist[order.pid][order.id] = true;
emit Sale(
order.id,
order.buyer,
order.seller,
order.total,
block.timestamp
);
}
}
return true;
}
function totalCost(uint[] memory ids, uint[] memory qtys) internal view returns (uint) {
uint total;
for(uint i = 0; i < ids.length; i++) {
total += products[i].price * qtys[i];
}
return total;
}
function deliverOrder(uint pid, uint id) public returns (bool) {
require(orderExist[pid][id], "Order not found");
OrderStruct memory order = ordersOf[pid][id];
require(order.seller == msg.sender, "Unauthorized Entity");
require(order.status != OrderEnum.DELEVIRED, "Order already delievered");
order.status = OrderEnum.DELEVIRED;
ordersOf[pid][id] = order;
stats.balance -= order.total;
statsOf[order.seller].paid += order.total;
statsOf[order.seller].sales++;
stats.sales++;
payTo(order.seller, order.total);
buyersOf[id].push(
BuyerStruct(
order.buyer,
order.total,
order.qty,
block.timestamp
)
);
return true;
}
function cancelOrder(uint pid, uint id) public returns (bool) {
require(orderExist[pid][id], "Order not found");
OrderStruct memory order = ordersOf[pid][id];
require(order.buyer == msg.sender, "Unauthorized Entity");
require(order.status != OrderEnum.CANCELED, "Order already canceled");
order.status = OrderEnum.CANCELED;
products[order.pid].stock += order.qty;
ordersOf[pid][id] = order;
payTo(order.buyer, order.total);
return true;
}
function getOrders() public view returns (OrderStruct[] memory props) {
props = new OrderStruct[](stats.orders);
for(uint i=0; i < stats.orders; i++) {
for(uint j=0; j < ordersOf[i].length; j++) {
props[i] = ordersOf[i][j];
}
}
}
function getOrder(uint pid, uint id) public view returns (OrderStruct memory) {
require(orderExist[pid][id], "Order not found");
return ordersOf[pid][id];
}
function getBuyers(uint pid) public view returns (BuyerStruct[] memory buyers) {
require(productExist[pid], "Product does not exist");
return buyersOf[pid];
}
function payTo(address to, uint256 amount) internal {
(bool success1, ) = payable(to).call{value: amount}("");
require(success1);
}
}
Теперь давайте объясним, что происходит в приведенном выше смарт-контракте. У нас есть следующее:
* OrderEnum: это перечисляемое число описывает различные статусы, которые проходит заказ в своем жизненном цикле. Например, заказ может быть размещен, доставлен, отменен и т. д. * ProductStruct: эта структура моделирует детали каждого продукта, которые будут храниться в этом смарт-контракте. Например, артикул, наличие, цена и так далее. * OrderStruct: эта структура включает в себя детали каждого заказа, размещенного в магазине, такие как идентификатор заказа, покупатель, количество товаров и многое другое. * CartStruct: эта структура содержит данные, которые собирает корзина для каждого товара, который будет отправлен в виде заказа в этом магазине. * BuyerStruct: эта структура говорит о том, какие данные должны собираться всякий раз, когда покупатель покупает продукт в нашем магазине. * ShopStats: это структура, которая детализирует статистику нашего магазина. Эта структура содержит такую информацию, как количество продавцов, продуктов, заказов и продаж.
Для переменных состояния у нас есть следующее.
- Владелец: эта переменная состояния содержит учетную запись исполнителя этого смарт-контракта.
- Статистика: содержит информацию о текущей статистике нашего магазина.
- Плата. Указывает, сколько будет взиматься плата за создание продукта на этой платформе.
- Продукты. Содержит набор продуктов, добавленных на эту платформу.
- ProductsOf. Сюда входят товары, добавленные конкретным продавцом в наш магазин.
- OrdersOf: содержит список заказов, приобретенных конкретным покупателем в магазине.
- StatsOf: содержит статистику каждого покупателя или продавца на платформе.
- BuyersOf: содержит информацию о покупателях определенного продукта.
- ProductExist: проверяет, есть ли товар в нашем магазине.
- OrderExist: проверяет, найден ли заказ в нашем магазине.
Что касается функций, у нас есть следующее.
- Создать товар: добавляет новую функцию в магазин, используя предоставленную информацию о продукте, такую как название, описание и цена.
- UpdateProduct: изменяет существующую информацию о продукте новыми данными, полученными через параметры функции.
- UpdateOrderDetails: эта функция отправляет обновление продукта для каждого уже полученного заказа.
- Удалить продукт: это переводит существующий продукт в удаленное состояние и делает его недоступным для покупки.
- GetProduct: возвращает весь список товаров в нашем магазине.
- GetProducts: возвращает определенный продукт из нашего магазина, ориентируясь на его идентификатор.
- CreateOrder: эта функция отменяет заказ, она доступна только покупателю такого продукта.
- Общая стоимость: рассчитывается общая стоимость каждого заказанного товара.
- DeliverOrder: эта функция доставляет заказ, она доступна только продавцу такого товара.
- CancelOrder: эта функция помечает заказ как отмененный и доступна только для покупателя такого продукта.
- GetOrders: возвращает всю коллекцию заказов, размещенных в этом магазине.
- GetOrder: возвращает конкретный заказ по его идентификатору.
- GetBuyers: возвращает коллекцию покупателей определенного продукта.
- PayTo: при вызове отправляет определенную сумму на определенный адрес.
Если вы новичок в Solidity, у меня есть полный БЕСПЛАТНЫЙ курс на YouTube под названием Mastering Solidity Basics. Так что смотрите, ставьте лайки и подписывайтесь!
https://www.youtube.com/watch?v=11DsTLhI_i4?embedable=true
Настройка сценария развертывания
yarn hardhat node # Terminal #1
yarn hardhat run scripts/deploy.js --network localhost # Terminal #2
Разработка внешнего интерфейса
Компонент ShopStats
Добавление игры в магазин
Компоненты корзины
И у вас есть это для всех кусков компонентов.
Страницы
На этой странице объединены баннер, статистика магазина и компоненты карточек, см. коды ниже.
Скопируйте страницу, создав компонент ниже в папке представлений. См. приведенные ниже коды.
Продавцы, не подписавшиеся на эту услугу, не могут получать чаты от своих клиентов. См. код ниже.
Страница продавца и статистики
Фантастика, это все для страниц, давайте перейдем к другим важным компонентам этого приложения.
Настройка других компонентов
Приведенные выше коды обеспечат правильное представление всех компонентов и страниц.
Служба управления состоянием Вам понадобится библиотека управления состоянием для работы с блокчейном и связывания всех различных компонентов вместе. Для простоты мы используем react-hooks-global-state.
Перейдите к **проекту**
>>
**src**
и создайте новую папку с именем store< /сильный>. В папке этого магазина создайте новый файл с именем **index.jsx**
, вставьте в него приведенные ниже коды и сохраните его.
import { createGlobalState } from 'react-hooks-global-state'
const { setGlobalState, useGlobalState, getGlobalState } = createGlobalState({
chatModal: 'scale-0',
deleteModal: 'scale-0',
updateModal: 'scale-0',
modal: 'scale-0',
menu: 'scale-0',
connectedAccount: '',
currentUser: null,
contract: null,
stats: null,
myStats: null,
buyers: [],
orders: [],
sales: [],
products: [],
product: null,
cart: [],
summary: { total: 0, grand: 0, tax: 0, qtys: [], ids: [] },
})
const truncate = (text, startChars, endChars, maxLength) => {
if (text.length > maxLength) {
let start = text.substring(0, startChars)
let 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, truncate }
Все данные, поступающие из блокчейна, будут храниться в указанном выше файле и использоваться во всем приложении.
Служба блокчейн
Этот файл содержит все процедуры EthersJs для связи с вашим смарт-контрактом, который живет в блокчейне. В папке src создайте файл с именем **Blockchain.services.jsx**
, вставьте приведенные ниже коды и сохраните.
import abi from './abis/src/contracts/Shop.sol/Shop.json'
import address from './abis/contractAddress.json'
import { getGlobalState, setGlobalState } from './store'
import { ethers } from 'ethers'
import { logOutWithCometChat } from './Chat.Service'
const toWei = (num) => ethers.utils.parseEther(num.toString())
const { ethereum } = window
const contractAddress = address.address
const contractAbi = abi.abi
const fee = toWei('0.002')
const getEtheriumContract = () => {
const connectedAccount = getGlobalState('connectedAccount')
if (connectedAccount) {
const provider = new ethers.providers.Web3Provider(ethereum)
const signer = provider.getSigner()
const contract = new ethers.Contract(contractAddress, contractAbi, signer)
return contract
} else {
return getGlobalState('contract')
}
}
const isWallectConnected = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_accounts' })
window.ethereum.on('chainChanged', (chainId) => {
window.location.reload()
})
window.ethereum.on('accountsChanged', async () => {
setGlobalState('connectedAccount', accounts[0].toLowerCase())
await logOutWithCometChat()
await isWallectConnected()
})
if (accounts.length) {
setGlobalState('connectedAccount', accounts[0].toLowerCase())
} else {
alert('Please connect wallet.')
console.log('No accounts found.')
}
} catch (error) {
reportError(error)
}
}
const connectWallet = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const accounts = await ethereum.request({ method: 'eth_requestAccounts' })
setGlobalState('connectedAccount', accounts[0].toLowerCase())
} catch (error) {
reportError(error)
}
}
const createProduct = async ({
sku,
name,
description,
imageURL,
price,
stock,
}) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
price = toWei(price)
await contract.createProduct(
sku,
name,
description,
imageURL,
price,
stock,
{
from: connectedAccount,
value: fee._hex,
},
)
} catch (error) {
reportError(error)
}
}
const updateProduct = async ({
id,
name,
description,
imageURL,
price,
stock,
}) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
price = toWei(price)
await contract.updateProduct(
id,
name,
description,
imageURL,
price,
stock,
{
from: connectedAccount,
},
)
} catch (error) {
reportError(error)
}
}
const deleteProduct = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
await contract.deleteProduct(id, { from: connectedAccount })
} catch (error) {
reportError(error)
}
}
const createOrder = async ({ ids, qtys, phone, destination, grand }) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
grand = toWei(grand)
await contract.createOrder(ids, qtys, destination, phone, {
from: connectedAccount,
value: grand._hex,
})
} catch (error) {
reportError(error)
}
}
const loadProducts = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
const products = await contract.getProducts()
const stats = await contract.stats()
const myStats = await contract.statsOf(connectedAccount)
setGlobalState('products', structuredProducts(products))
setGlobalState('stats', structureStats(stats))
setGlobalState('myStats', structureStats(myStats))
} catch (error) {
reportError(error)
}
}
const loadProduct = async (id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = getEtheriumContract()
const product = await contract.getProduct(id)
const buyers = await contract.getBuyers(id)
setGlobalState('product', structuredProducts([product])[0])
setGlobalState('buyers', structuredBuyers(buyers))
} catch (error) {
reportError(error)
}
}
const loadOrders = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const contract = getEtheriumContract()
const orders = await contract.getOrders()
setGlobalState('orders', structuredOrders(orders))
} catch (error) {
reportError(error)
}
}
const loadStats = async () => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
const myStats = await contract.statsOf(connectedAccount)
setGlobalState('myStats', structureStats(myStats))
} catch (error) {
reportError(error)
}
}
const delieverOrder = async (pid, id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
await contract.deliverOrder(pid, id, { from: connectedAccount })
} catch (error) {
reportError(error)
}
}
const cancelOrder = async (pid, id) => {
try {
if (!ethereum) return alert('Please install Metamask')
const connectedAccount = getGlobalState('connectedAccount')
const contract = getEtheriumContract()
await contract.cancelOrder(pid, id, { from: connectedAccount })
} catch (error) {
reportError(error)
}
}
const reportError = (error) => {
console.log(error.message)
throw new Error('No ethereum object.')
}
const structuredProducts = (products) =>
products
.map((product) => ({
id: Number(product.id),
sku: product.sku,
seller: product.seller.toLowerCase(),
name: product.name,
description: product.description,
imageURL: product.imageURL,
stock: Number(product.stock),
price: parseInt(product.price._hex) / 10 ** 18,
deleted: product.deleted,
timestamp: new Date(product.timestamp).getTime(),
}))
.reverse()
const structuredOrders = (orders) =>
orders
.map((order) => ({
pid: Number(order.pid),
id: Number(order.id),
name: order.name,
sku: order.sku,
seller: order.seller.toLowerCase(),
buyer: order.buyer.toLowerCase(),
destination: order.destination,
phone: order.phone,
imageURL: order.imageURL,
qty: Number(order.qty),
status: Number(order.status),
total: parseInt(order.total._hex) / 10 ** 18,
timestamp: new Date(order.timestamp.toNumber()).getTime(),
}))
.reverse()
const structuredBuyers = (buyers) =>
buyers
.map((buyer) => ({
buyer: buyer.buyer.toLowerCase(),
qty: Number(buyer.qty),
price: parseInt(buyer.price._hex) / 10 ** 18,
timestamp: new Date(buyer.timestamp.toNumber() * 1000).toDateString(),
}))
.reverse()
const structureStats = (stats) => ({
balance: Number(stats.balance),
orders: Number(stats.orders),
products: Number(stats.products),
sales: Number(stats.sales),
paid: Number(stats.paid._hex),
sellers: Number(stats.sellers),
})
export {
isWallectConnected,
connectWallet,
createProduct,
updateProduct,
deleteProduct,
loadProducts,
loadProduct,
createOrder,
loadOrders,
loadStats,
delieverOrder,
cancelOrder,
}
Служба корзины Этот файл содержит коды, которые калибруют нашу систему корзины, он гарантирует, что каждое изменение цены и количества товаров будет отражено в подпункте и общей сумме нашей корзины.
В каталоге **src**
создайте новый файл с именем **Cart.Services.jsx**
, скопируйте приведенные ниже коды, вставьте их в него и сохраните.
import { getGlobalState, setGlobalState } from './store'
const addToCart = (product) => {
const products = getGlobalState('cart')
if (!products.find((p) => product.id == p.id)) {
setGlobalState('cart', [...products, { ...product, qty: 1 }])
localStorage.setItem(
'cart',
JSON.stringify([...products, { ...product, qty: 1 }]),
)
summarizeCart()
}
}
const remFromCart = (product) => {
let products = getGlobalState('cart')
products = products.filter((p) => p.id != product.id)
setGlobalState('cart', products)
localStorage.setItem('cart', JSON.stringify(products))
summarizeCart()
}
const updateCart = (product) => {
const products = getGlobalState('cart')
products.forEach((p) => {
if (p.id == product.id) p = product
})
setGlobalState('cart', products)
localStorage.setItem('cart', JSON.stringify(products))
summarizeCart()
}
const clearCart = () => {
setGlobalState('cart', [])
localStorage.removeItem('cart')
summarizeCart()
}
const summarizeCart = () => {
const products = getGlobalState('cart')
const summary = getGlobalState('summary')
products.forEach((p, i) => {
summary.total += p.qty * p.price
if (summary.ids.includes(p.id)) {
summary.qtys[i] = p.qty
} else {
summary.ids[i] = p.id
summary.qtys[i] = p.qty
}
})
summary.tax = 0.002
summary.grand = summary.total + summary.tax
setGlobalState('summary', summary)
summary.total = 0
// summary.grand = 0
}
const checkStorage = () => {
let products = JSON.parse(localStorage.getItem('cart'))
if (products?.length) {
setGlobalState('cart', JSON.parse(localStorage.getItem('cart')))
summarizeCart()
}
}
export { addToCart, remFromCart, updateCart, checkStorage, clearCart }
Служба чата
Этот файл содержит коды для взаимодействия с CometChat SDK. В папке **src**
создайте новый файл с именем **Chat.Services.jsx**
. Теперь скопируйте приведенные ниже коды, вставьте их в файл и сохраните.
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
return await CometChat.login(UID, authKey)
.then((user) => {
setGlobalState('currentUser', user)
return true
})
.catch((error) => error)
}
const signUpWithCometChat = async (UID, name) => {
let authKey = CONSTANTS.Auth_Key
const user = new CometChat.User(UID)
user.setName(name)
return await CometChat.createUser(user, authKey)
.then((user) => {
console.log('Signed In: ', user)
return true
})
.catch((error) => error)
}
const logOutWithCometChat = async () => {
return await CometChat.logout()
.then(() => setGlobalState('currentUser', null))
.catch((error) => error)
}
const isUserLoggedIn = async () => {
await CometChat.getLoggedinUser()
.then((user) => setGlobalState('currentUser', user))
.catch((error) => console.log('error:', error))
}
const getUser = async (UID) => {
return await CometChat.getUser(UID)
.then((user) => user)
.catch((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)
.catch((error) => error)
}
export {
initCometChat,
loginWithCometChat,
signUpWithCometChat,
logOutWithCometChat,
getMessages,
sendMessage,
getConversations,
isUserLoggedIn,
getUser,
CometChat,
}
Наконец, нажмите на ссылку ниже, чтобы загрузить изображение. Если папки asset еще нет в вашем каталоге src, создайте ее.
https://github.com/Daltonic/gameshop/blob /master/src/assets/banner.png?raw=true
Со всеми этими настройками запустите приведенную ниже команду, чтобы запустить проект на вашем локальном компьютере.
yarn start
Это откроет проект в браузере по адресу **localhost:3000**
.
Заключение
На этом руководство по этой сборке завершено. Вы узнали, как создать децентрализованную платформу электронной коммерции, позволяющую размещать игровые продукты на торговой площадке.
Покупатели могут приобрести ваш игровой продукт, а при доставке деньги передаются продавцу продукта.
Это один из мощных вариантов использования для разработки реального децентрализованного приложения web3. Больше подобных сборок можно найти здесь, в моем аккаунте.
Вы также можете смотреть мои бесплатные видео на моем канале YouTube. Или забронируйте у меня частные занятия по веб3, чтобы ускорить процесс обучения веб3.< /p>
С учетом сказанного, я поймаю вас в следующий раз, хорошего дня!
Об авторе
Госпел Дарлингтон – разработчик комплексного блокчейна с 6+
летним опытом работы в индустрии разработки программного обеспечения.
Сочетая разработку программного обеспечения, написание текстов и преподавание, он демонстрирует, как создавать децентрализованные приложения в сетях блокчейнов, совместимых с EVM.
Его стеки включают JavaScript
, React
, Vue
, Angular
, Node
, React Native
, NextJs
, Solidity
и другие.
Для получения дополнительной информации о нем посетите и подпишитесь на его страницу в Twitter, Github, LinkedIn или его веб-сайт.
Оригинал