Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve switch chain UX, add L2 networks & Zerion, refactor code #82

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@ import AutoHideAlert, {alertRef} from "./AutoHideAlert.js";
import MintModal, {modalRef} from "./MintModal.js";
import {ThemeProvider} from "@mui/material";
import {theme} from "../styles/theme.js";
import { Web3ContextProvider } from "./Web3Context";

export const App = () => {
return <ThemeProvider theme={theme}>
<div>
<AutoHideAlert ref={alertRef} />
<MintModal ref={modalRef} />
</div>
<Web3ContextProvider>
<div>
<AutoHideAlert ref={alertRef} />
<MintModal ref={modalRef} />
</div>
</Web3ContextProvider>
</ThemeProvider>
}
84 changes: 84 additions & 0 deletions src/components/ConfirmTxStep.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import React, { useContext } from 'react';
import { Box, Button, CircularProgress, IconButton, Typography } from '@mui/material';
import { isMetaMaskDesktop } from "../utils";
import { getConfigChainID } from "../web3";
import { NETWORKS } from "../constants";
import { Web3Context } from "./Web3Context";
import { ChevronLeft } from "@mui/icons-material";

export const ConfirmTxStep = ({ txHash, quantity, setIsLoading }) => {
const [{ wallet, chainID }, setState] = useContext(Web3Context)

const renderConfirmText = () => {
if (wallet) {
console.log("current chainID", chainID)
if (chainID !== getConfigChainID()) {
const networkName = NETWORKS[getConfigChainID()]?.name
return {
title: `Switch to ${networkName ?? "correct"} network`,
subtitle: <>
Open your wallet and switch the network to <b>{networkName ?? "correct one"}</b> to proceed
</>
}
}
return {
title: "Confirm the transaction in your wallet",
subtitle: <>
Wait up to 2-3 sec until the transaction appears in your wallet
<br/><br/>
{isMetaMaskDesktop() && "If you don't see the Confirm button, scroll down ⬇️"}
</>
}
}
return {
title: "Connect your wallet",
subtitle: <>
Connect your wallet to confirm the transaction
</>
}
}

const { title, subtitle } = renderConfirmText()

return <Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: 300,
height: 300,
}}>
{!txHash && <IconButton
sx={{
position: "absolute",
top: 16,
left: 16,
"& .MuiSvgIcon-root": {
fontSize: "1.75rem",
},
color: (theme) => theme.palette.grey[500],
}}
onClick={() => setIsLoading(false)}>
<ChevronLeft />
</IconButton>}
{txHash ? <CircularProgress /> : <span style={{
fontSize: 60,
lineHeight: 1,
margin: 0
}}>
👀
</span>}
<Typography
sx={{ mt: 3, textAlign: "center" }}
variant="h4">
{txHash ? `Minting ${quantity} NFT...` : title}
</Typography>
{!txHash && <Typography sx={{
mt: 3,
pl: 3,
pr: 3,
color: "#757575",
textAlign: "center"
}} variant="subtitle2">{subtitle}</Typography>}
</Box>
}
80 changes: 30 additions & 50 deletions src/components/MintModal.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useImperativeHandle, useState } from "react";
import { Box, CircularProgress, Dialog, DialogContent, DialogTitle, IconButton, Typography } from "@mui/material";
import React, { useContext, useImperativeHandle, useState } from "react";
import { Box, Dialog, DialogContent, DialogTitle, IconButton, Typography } from "@mui/material";
import CloseIcon from '@mui/icons-material/Close';
import { QuantityModalStep } from './QuantityModalStep';
import { isMobile } from "../utils";
import { ConfirmTxStep } from "./ConfirmTxStep";
import { Web3Context } from "./Web3Context";

const DialogTitleWithClose = ({ children, onClose }) => {
return <DialogTitle>
Expand Down Expand Up @@ -32,9 +33,14 @@ export const MintModal = (props, ref) => {
const [isLoading, setIsLoading] = useState(false)
const [step, setStep] = useState(1)
const [quantity, setQuantity] = useState(1)
// TODO: migrate to hooks and global state / Context
// this is a hack
const [state, setState] = useContext(Web3Context)
const { wallet, chainID } = state

const handleClose = () => {
setIsOpen(false);
setIsOpen(false)
setIsLoading(false)
}

useImperativeHandle(ref, () => ({
Expand All @@ -46,53 +52,27 @@ export const MintModal = (props, ref) => {
<Dialog
open={isOpen}
onClose={handleClose}>
{isLoading &&
<Box sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
width: 300,
height: 300,
}}>
{txHash ? <CircularProgress /> : <span style={{
fontSize: 60,
lineHeight: 1,
margin: 0
}}>
👀
</span>}
<Typography
sx={{ mt: 3, textAlign: "center" }}
variant="h4">
{txHash
? `Minting ${quantity} NFT...`
: 'Confirm the transaction in your wallet'}
</Typography>
{!txHash && <Typography sx={{
mt: 3,
pl: 3,
pr: 3,
color: "#757575",
textAlign: "center"
}} variant="subtitle2">
Wait up to 2-3 sec until the transaction appears in your wallet
<br/><br/>
{!isMobile() && "If you don't see the Confirm button, scroll down ⬇️"}</Typography>}
</Box>
}
{isLoading && <ConfirmTxStep
wallet={wallet}
chainID={chainID}
txHash={txHash}
quantity={quantity}
setIsLoading={setIsLoading}
/>}
{!isLoading && <>
<DialogTitleWithClose onClose={handleClose}>
<Typography variant="h1">Mint now</Typography>
</DialogTitleWithClose>
<DialogContent className="mintModal-content">
{step === 1 && <QuantityModalStep
setTxHash={setTxHash}
setQuantity={setQuantity}
setStep={setStep}
setIsLoading={setIsLoading}
/>}
</DialogContent>
<DialogTitleWithClose onClose={handleClose}>
<Typography variant="h1">Mint now</Typography>
</DialogTitleWithClose>
<DialogContent className="mintModal-content">
{step === 1 && <QuantityModalStep
setTxHash={setTxHash}
setQuantity={setQuantity}
setStep={setStep}
setIsLoading={setIsLoading}
state={state}
setState={setState}
/>}
</DialogContent>
</>}
</Dialog>
)
Expand Down
14 changes: 9 additions & 5 deletions src/components/QuantityModalStep.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { parseTxError, roundToDecimal } from '../utils';
import { Attribution } from './Attribution';
import { isEthereumContract } from "../contract";

export const QuantityModalStep = ({ setQuantity, setIsLoading, setTxHash, setStep }) => {
export const QuantityModalStep = ({ setQuantity, setIsLoading, setTxHash, state, setState }) => {
const [quantityValue, setQuantityValue] = useState(1)
const [maxTokens, setMaxTokens] = useState(undefined)
const [mintPrice, setMintPrice] = useState(undefined)
Expand Down Expand Up @@ -49,22 +49,26 @@ export const QuantityModalStep = ({ setQuantity, setIsLoading, setTxHash, setSte

const onSuccess = async () => {
setIsLoading(true)
const { tx } = await mint(quantityValue)
const { tx } = await mint(quantityValue, {
onConnectSuccess: (wallet) => setState({ ...state, wallet }),
setState: (wallet, chainID) => setState({ wallet, chainID })
})
if (tx === undefined) {
setIsLoading(false)
}
tx?.on("transactionHash", (hash) => {
tx?.once("transactionHash", (hash) => {
setTxHash(hash)
})?.on("confirmation", async () => {
})?.once("confirmation", async () => {
setIsLoading(false)
setTxHash(undefined)
showAlert(`Successfully minted ${quantityValue} NFTs${window.DEFAULTS?.redirectURL ? ". You will be redirected in less than a second" : ""}`, "success")
// TODO: show success state in the modal
if (window.DEFAULTS?.redirectURL) {
setTimeout(() => {
window.location.href = window.DEFAULTS?.redirectURL
}, 800)
}
})?.on("error", (e) => {
})?.once("error", (e) => {
setIsLoading(false)
const { code, message } = parseTxError(e);
if (code !== 4001) {
Expand Down
33 changes: 33 additions & 0 deletions src/components/Web3Context.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createContext, useEffect, useState } from "react";
import { currentAddress, getWalletAddressOrConnect, provider, web3 } from "../wallet";
import { useWeb3 } from "../hooks/useWeb3";

const defaultContext = {
wallet: undefined,
chainID: undefined,
}

export const Web3Context = createContext([defaultContext, () => defaultContext]);

// TODO: remove this dirty hooks hack when migrate to RainbowKit + ethers or similar
export const Web3ContextProvider = (props) => {
const [state, setState] = useState(defaultContext)
const [web3, provider] = useWeb3([state.wallet, state.chainID])

useEffect(() => {
if (!provider)
return

provider.on("chainChanged", (chainId) => {
console.log("chainChanged", chainId)
setState({
...state,
chainID: Number(String(provider.chainId).replace("0x", ""))
})
})
}, [provider])

return <Web3Context.Provider value={[state, setState]}>
{props.children}
</Web3Context.Provider>
}
72 changes: 72 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,54 @@ export const NETWORKS = {
testnetID: 5,
blockExplorerURL: "https://goerli.etherscan.io"
},
10: {
name: "Optimism",
chain: "ethereum",
rpcURL: "https://mainnet.optimism.io/",
currency: {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
testnetID: 420,
blockExplorerURL: "https://optimistic.etherscan.io"
},
420: {
name: "Optimism Goerli",
chain: "ethereum",
rpcURL: "https://goerli.optimism.io/",
currency: {
"name": "Goerli Ether",
"symbol": "ETH",
"decimals": 18
},
testnetID: 420,
blockExplorerURL: "https://goerli-optimism.etherscan.io"
},
42161: {
name: "Arbitrum One",
chain: "ethereum",
currency: {
"name": "Ether",
"symbol": "ETH",
"decimals": 18
},
testnetID: 421613,
rpcURL: "https://arb1.arbitrum.io/rpc/",
blockExplorerURL: "https://arbiscan.io",
},
421613: {
"name": "Arbitrum Görli",
"chain": "ethereum",
currency: {
"name": "Goerli Ether",
"symbol": "ETH",
"decimals": 18
},
testnetID: 421613,
rpcURL: "https://goerli-rollup.arbitrum.io/rpc/",
blockExplorerURL: "https://goerli.arbiscan.io"
},
137: {
name: "Polygon",
chain: "polygon",
Expand Down Expand Up @@ -83,6 +131,30 @@ export const NETWORKS = {
testnetID: 97,
blockExplorerURL: "https://testnet.bscscan.com",
},
43114: {
name: "Avalanche",
chain: "AVAX",
rpcURL: "https://api.avax.network/ext/bc/C/rpc/",
currency: {
"name": "Avalanche",
"symbol": "AVAX",
"decimals": 18
},
testnetID: 43113,
blockExplorerURL: "https://snowtrace.io"
},
43113: {
name: "Avalanche Fuji",
chain: "AVAX",
rpcURL: "https://api.avax-test.network/ext/bc/C/rpc/",
currency: {
"name": "Avalanche",
"symbol": "AVAX",
"decimals": 18
},
testnetID: 43113,
blockExplorerURL: "https://cchain.explorer.avax-test.network"
},
25: {
name: "Cronos Blockchain",
chain: "cronos",
Expand Down
Loading