介绍
一个开源solidity合集仓库
https://github.com/qdwds/smart-contracts
以太坊ERC721全栈开发开NFT合集从入门到项目实战项目
https://learnblockchain.cn/course/31
https://edu.51cto.com/course/33566.html
一起学习吧
可合成的NFT
这一小节是实现NFT的页面展示和功能交互
项目目录
├── README.md
├── abi
│ └── ComposeNFT.json
├── components
│ ├── Botton
│ │ └── index.tsx
│ ├── Header
│ │ └── index.tsx
│ └── NFT
│ └── index.tsx
├── deploy.sh
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── pages
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api
│ │ └── hello.ts
│ └── index.tsx
├── pnpm-lock.yaml
├── public
│ ├── favicon.ico
│ ├── next.svg
│ └── vercel.svg
├── src
│ └── rainbowkit.ts
├── styles
│ └── globals.css
└── tsconfig.json
Index.tsx
当前文件为主页面,功能主要实现了NFT页面展示和交互
import React, { useState } from 'react'
import { Stack, Box, Snackbar } from '@mui/material';
import NFT from "../components/NFT";
import { Breed, Mint } from "../components/Botton";
import { useAccount, useContractRead } from 'wagmi';
import compose from "../abi/ComposeNFT.json";
const compsoeContract: any = {
address: compose.address,
abi: compose.abi,
}
export default function Index() {
const [compose, setCompose] = useState<string[]>([])
const [donkey, setDonkey] = useState<any[]>([]);
const { address } = useAccount();
const [open, setOpen] = useState(false);
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
const { refetch } = useContractRead({
...compsoeContract,
functionName: 'getDonkeys',
args: [address],
onSuccess(data: any) {
if(data.length > 0)
setDonkey(data.map((d: any) => ({
...d,
check: false
})));
}
})
const handleAddCompose = (data: any) => {
console.log(data);
if (compose.length >= 2) return setOpen(true);;
const newDonkey = donkey.map((d: any) => {
if (d.tokenId == data.tokenId) {
return {
...d,
check: true
}
} else {
return { ...d }
}
})
setDonkey(newDonkey);
setCompose([...compose, data.tokenId]);
}
const handleRemoveCompose = (data: any) => {
const newDonkey = donkey.map((d: any) => {
if (d.tokenId == data.tokenId) {
return {
...d,
check: false
}
} else {
return { ...d }
}
})
setDonkey(newDonkey);
setCompose(compose.filter((c: string) => data.tokenId != c));
}
return (
<>
<Snackbar open={open} anchorOrigin={{ vertical:"top", horizontal:"center" }} autoHideDuration={3000} onClose={handleClose} message="最多只能选择两个NFT进行合成!"/>
<Stack direction="row" spacing={2} sx={{ justifyContent: 'center', marginTop: 2 }}>
<Breed refetch={refetch} compose={compose} setCompose={setCompose}></Breed>
<Mint refetch={refetch}></Mint>
</Stack>
<Box sx={{ padding: 4 }}>
{
donkey.map((itme: any) => {
return <NFT
key={itme.tokenId}
data={itme}
handleAddCompose={handleAddCompose}
handleRemoveCompose={handleRemoveCompose}
></NFT>
})
}
</Box>
</>
)
}
NFT展示组件
这个组件主要功能是展示当前用户的NFT。
import { Box, Button, Card, CardActionArea, CardActions, CardContent, CardMedia, Typography } from '@mui/material'
import React from 'react'
const resetAddress = (owner: string) => `${owner.slice(0, 4)}...${owner.slice(38)}`;
export default function NFT({ data, handleAddCompose, handleRemoveCompose }: any) {
return (
<>
{
<Card sx={{ maxWidth: 260, float: "left", marginLeft: 2, marginBottom: 2 }} key={data.tokenId}>
<CardActionArea>
<CardMedia
component="img"
height="240"
image={data.uri}
alt="green iguana"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{String(data.tokenId)} #
</Typography>
<Typography variant="body2" color="text.secondary" component="div">
<Box>
<div>等级: {data.level}</div>
<div>dadId: {String(data.dadId)}</div>
<div>mumId: {String(data.mumId)}</div>
<div>拥有者:{resetAddress(data.owner)}</div>
</Box>
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
{
data.check ?
<Button size="small" color="error" onClick={() => handleRemoveCompose(data)}>移除合成</Button> :
<Button size="small" color="primary" onClick={() => handleAddCompose(data)}>添加合成</Button>
}
</CardActions>
</Card>
}
</>
)
}
mint和合成按钮组件
import { useAccount, useContractWrite, usePrepareContractWrite } from 'wagmi';
import compose from "../../abi/ComposeNFT.json"
import { Alert, Button, Snackbar } from '@mui/material';
import { LoadingButton } from '@mui/lab';
import { useState } from 'react';
const compsoeContract: any = {
address: compose.address,
abi: compose.abi,
}
export const Breed = ({ refetch, compose, setCompose }: any) => {
const [msg, setMsg] = useState("");
const [open, setOpen] = useState(false);
const handleClose = (event: React.SyntheticEvent | Event, reason?: string) => {
if (reason === 'clickaway') {
return;
}
setOpen(false);
};
const { config }: any = usePrepareContractWrite({
...compsoeContract,
functionName: 'breed',
args:compose,
onError(error:any){
const message = error?.reason || error.data?.message || error.message;
if(message == "missing argument: passed to contract") return;
setOpen(true);
setMsg(message);
}
})
const { isLoading, writeAsync } = useContractWrite({
...config,
async onSuccess(data:any){
await data.wait();
refetch();
setCompose([]);
},
})
return (
<>
<Snackbar open={open} anchorOrigin={{ vertical:"top", horizontal:"center" }} autoHideDuration={3000} onClose={handleClose}>
<Alert onClose={handleClose} severity="error" sx={{ width: '100%' }}>{msg}</Alert>
</Snackbar>
<LoadingButton
loading={isLoading}
variant="contained"
color="success"
disabled={!writeAsync}
onClick={()=>writeAsync?.()}
>合成</LoadingButton>
</>
)
}
export const Mint = ({ refetch }: any) => {
const { address } = useAccount();
const { isLoading, writeAsync } = useContractWrite({
mode: 'recklesslyUnprepared',
...compsoeContract,
functionName: 'safeMint',
args: [address],
async onSuccess(data){
await data.wait();
refetch();
}
})
return (
<LoadingButton
loading={isLoading}
variant="outlined"
color="error"
disabled={!writeAsync}
onClick={()=>writeAsync?.()}
>mint</LoadingButton>
)
}