diff --git a/frontend/src/App.js b/frontend/src/App.js index 2327d4b4..f1f8dc68 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,6 +1,7 @@ import React, { useEffect, useState } from 'react'; import twitterLogo from './assets/twitter-logo.svg'; import './App.css'; +import LoadingIndicator from './Components/LoadingIndicator'; // Smart contract component imports import SelectCharacter from './Components/SelectCharacter'; @@ -81,6 +82,8 @@ const App = () => { const [currentAccount, setCurrentAccount] = useState(null); const [characterNFT, setCharacterNFT] = useState(null); + const [isLoading, setIsLoading] = useState(false); + // Actions const checkIfWalletIsConnected = async () => { try { @@ -107,8 +110,41 @@ const App = () => { } }; + // Render -> if user is connected AND does not have a character NFT, show SelectCharacter component + useEffect(() => { + const fetchNFTMetadata = async () => { + console.log('Checking for Character NFT on address:', currentAccount); + + const provider = new ethers.providers.Web3Provider(window.ethereum); // component that talks to the ethereum nodes + const signer = provider.getSigner(); + const gameContract = new ethers.Contract( // Contract object -> creates connection to the contract + CONTRACT_ADDRESS, + GameContent.abi, + signer + ); + + const txn = await gameContract.checkIfUserHasNFT(); + if (txn.name) { + console.log('User has character NFT'); + setCharacterNFT(transformCharacterData(txn)); + } else { + console.log('User does not have character NFT'); + } + }; + + // Only run this if the user is connected + if (currentAccount) { + console.log('Checking for Character NFT on address:', currentAccount); + fetchNFTMetadata(); + } + }, [currentAccount]); // Fire this useEffect only when currentAccount changes + // Rendering dynamic user content to the frontend const renderContent = () => { + console.log(currentAccount, " - ", characterNFT); + if (currentAccount && isLoading) { + return ; + } // If user isn't signed in if (!currentAccount) { return ( @@ -154,20 +190,6 @@ const App = () => { } }; - useEffect(() => { - checkIfWalletIsConnected(); - - const checkNetwork = async () => { - try { - if (window.ethereum.networkVersion !== '4') { - alert('Please switch to the Rinkeby test network'); - } - } catch (error) { - console.log(error); - } - } - }, []); - // Render -> if user is connected AND does not have a character NFT, show SelectCharacter component useEffect(() => { const fetchNFTMetadata = async () => { @@ -197,6 +219,20 @@ const App = () => { } }, [currentAccount]); // Fire this useEffect only when currentAccount changes + useEffect(() => { + checkIfWalletIsConnected(); + + const checkNetwork = async () => { + try { + if (window.ethereum.networkVersion !== '4') { + alert('Please switch to the Rinkeby test network'); + } + } catch (error) { + console.log(error); + } + } + }, []); + return (
diff --git a/frontend/src/Components/SelectCharacter/index.js b/frontend/src/Components/SelectCharacter/index.js index ab8cfed9..74166cd1 100644 --- a/frontend/src/Components/SelectCharacter/index.js +++ b/frontend/src/Components/SelectCharacter/index.js @@ -1,27 +1,33 @@ import React, { useEffect, useState } from 'react'; -import './SelectCharacter.css' +import './SelectCharacter.css'; import { ethers } from 'ethers'; import { CONTRACT_ADDRESS, transformCharacterData } from '../../constants'; -import GameContent from '../../utils/GameContent.json'; +import gameContent from '../../utils/GameContent.json' +// import LoadingIndicator from './Components/LoadingIndicator'; +import './SelectCharacter.css'; const SelectCharacter = ({ setCharacterNFT }) => { const [characters, setCharacters] = useState([]); const [gameContract, setGameContract] = useState(null); + const [mintingCharacter, setMintingCharacter] = useState(false); - const mintCharacterNFTAction = (characterId) => async () => { - try { - if (gameContract) { - console.log('Minting character in progress...'); - const mintTxn = await gameContract.mintCharacterNFT(characterId); - await mintTxn.wait(); - console.log('mintTxn:', mintTxn); - } - } catch (error) { - console.warn('MintCharacterAction Error:', error); - } - } + // Render Methods + const renderCharacters = () => + characters.map((character, index) => ( +
+
+

{character.name}

+
+ {character.name} + +
+ )); - // Display mintable characters + // UseEffect useEffect(() => { const { ethereum } = window; @@ -30,94 +36,103 @@ const SelectCharacter = ({ setCharacterNFT }) => { const signer = provider.getSigner(); const gameContract = new ethers.Contract( CONTRACT_ADDRESS, - GameContent.abi, + gameContent.abi, signer ); - // Set the gameContract in state setGameContract(gameContract); } else { - console.log("Ethereum object not found"); + console.log('Ethereum object not found'); } + }, []); - // Fetch all characters + // Actions + const mintCharacterNFTAction = (characterId) => async () => { + try { + if (gameContract) { + setMintingCharacter(true); + console.log('Minting character in progress...'); + const mintTxn = await gameContract.mintCharacterNFT(characterId); + await mintTxn.wait(); + console.log('mintTxn:', mintTxn); + // hides loading indicator when done with Action + setMintingCharacter(false); + } + } catch (error) { + console.warn('MintCharacterAction Error:', error); + setMintingCharacter(false); + } + + }; + useEffect(() => { const getCharacters = async () => { try { console.log('Getting contract characters to mint'); - // Call contract function to get all characters const charactersTxn = await gameContract.getAllDefaultCharacters(); console.log('charactersTxn:', charactersTxn); - - // Transform the data of all the characters const characters = charactersTxn.map((characterData) => transformCharacterData(characterData) ); - // Set all mint-able characters in state setCharacters(characters); } catch (error) { - console.error("Something went wrong fetching characters:", error); + console.error('Something went wrong fetching characters:', error); } }; - - const onCharacterMint = async (sender, tokenId, characterIndex) => { // Called whenever a new character NFT is minted + const onCharacterMint = async (sender, tokenId, characterIndex) => { console.log( `CharacterNFTMinted - sender: ${sender} tokenId: ${tokenId.toNumber()} characterIndex: ${characterIndex.toNumber()}` ); + alert(`Your NFT is all done -- see it here: https://testnets.opensea.io/assets/${gameContract}/${tokenId.toNumber()}`); - // Once the character NFT is minted, we can fetch the metadata from the contract and set it in state to move onto the Arena (game scene) if (gameContract) { const characterNFT = await gameContract.checkIfUserHasNFT(); - console.log('Character NFT: ', characterNFT); + console.log('CharacterNFT: ', characterNFT); setCharacterNFT(transformCharacterData(characterNFT)); } }; - // Get the characters if (gameContract) { getCharacters(); - - // Set up NFT Minted listener gameContract.on('CharacterNFTMinted', onCharacterMint); + // alert(`Your NFT is all done -- see it here: https://testnets.opensea.io/assets/${gameContract}/${tokenId.toNumber()}`); } - return () => { - // When the component unmounts, clean up this listener if (gameContract) { gameContract.off('CharacterNFTMinted', onCharacterMint); } }; }, [gameContract]); - // Render MethodsComp - const renderCharacters = () => { - characters.map((character, index) => ( -
-
-

{characters.name}

-
- {characters.name} - -
- )); - } - return (
-

Mint Your Hero. Choose wisely.

- {/* Only show this when there are characters in state */} +

Mint Your Hero. Save El Barrio.

{characters.length > 0 && (
{renderCharacters()}
)} + {mintingCharacter}
+ ); }; -export default SelectCharacter; \ No newline at end of file + + +export default SelectCharacter; + +/* + {* Only show our loading state if mintingCharacter is true *} + {mintingCharacter && ( +
+ +

Minting In Progress...

+ Minting loading indicator +
+ )} + */ \ No newline at end of file diff --git a/frontend/src/constants.js b/frontend/src/constants.js index 5b4436a5..69f94c5a 100644 --- a/frontend/src/constants.js +++ b/frontend/src/constants.js @@ -1,4 +1,4 @@ -const CONTRACT_ADDRESS = "0x8aD3C1b653E5543dfb72bd9c249D943968e0fA77"; +const CONTRACT_ADDRESS = "0x1Ffe78935e014927E5064F0656b072fD5e2b1b8e"; const transformCharacterData = (characterData) => { return { diff --git a/hardhat.config.js b/hardhat.config.js index 63df0087..a9d0e729 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -20,8 +20,10 @@ module.exports = { solidity: '0.8.1', networks: { rinkeby: { - url: process.env.ALCHEMY, // Alchemy Rinkeby URL - accounts: [process.env.PRIVATE], // This is the private key of the account (rinkeby) + url: "https://eth-rinkeby.alchemyapi.io/v2/gQUHfEyfP_v73J37inUmWzPnYN0nOLr_", + accounts: ["a7a2f59191ce8477761f043f9da63a40ced24f803fd6a423930585e981ef5554"] + /*url: process.env.ALCHEMY, // Alchemy Rinkeby URL + accounts: [process.env.PRIVATE], // This is the private key of the account (rinkeby)*/ }, }, -}; +}; \ No newline at end of file diff --git a/scripts/deploy.js b/scripts/deploy.js index 288dce1a..14391f42 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -4,13 +4,13 @@ const main = async () => { const gameContractFactory = await hre.ethers.getContractFactory("GameContent"); // Compiles the contract const gameContract = await gameContractFactory.deploy( ["Scrooby", "Gizmo", "Elsie"], // Names - ["https://i.imgur.com/pKd5Sdk.png", // Images - "https://i.imgur.com/xVu4vFL.png", - "https://i.imgur.com/WMB6g9u.png"], + ["https://www.pinclipart.com/picdir/middle/85-850316_r2-d2-icon-r2d2-icon-clipart.png", // Images + "https://static.thenounproject.com/png/318368-200.png", + "https://www.clipartmax.com/png/middle/31-312830_bb8-vector-google-search-star-wars-bb8-birthday-card.png"], [100, 200, 300], // HP Values [100, 50, 25], // Attack damage values "Scroobus", // Boss name - "https://i.imgur.com/AksR0tt.png", // Boss image + "https://w7.pngwing.com/pngs/759/987/png-transparent-php-computer-icons-web-development-logo-icon-text-trademark-logo.png", // Boss image 10000, // Boss HP 500 // Boss attack damage ); // Hardhat creates a local Eth network just for this contract with refresh for every deploy