From 5ce4247df2e78b72cc3d02e076472e7ea6c8fbb7 Mon Sep 17 00:00:00 2001 From: Liam Arbuckle Date: Sun, 19 Feb 2023 23:07:14 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=A5=87=F0=9F=91=93=20=E2=86=9D=20Initiali?= =?UTF-8?q?sing=20Planet=20Profile/Cover=20page=20and=20added=20staking=20?= =?UTF-8?q?contract=20&=20integration,=20styling,=20just=20missing=20a=20f?= =?UTF-8?q?ew=20assets.=20https://github.com/Signal-K/polygon/issues/28=20?= =?UTF-8?q?&&=20https://github.com/Signal-K/polygon/issues/31?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 34 -------------- components/Planets/PlanetCard.tsx | 18 ++++++++ components/Stake/ApproxRewards.tsx | 42 +++++++++++++++++ components/Stake/LoadingSection.tsx | 4 ++ components/Stake/MintContainer.tsx | 32 +++++++++++++ components/Stake/OwnedGear.tsx | 62 +++++++++++++++++++++++++ components/Stake/Rewards.tsx | 49 ++++++++++++++++++++ components/Stake/Shop.tsx | 26 +++++++++++ components/Stake/ShopItem.tsx | 45 ++++++++++++++++++ pages/planets/planet.tsx | 8 ++++ pages/posts/Profile.tsx | 2 +- pages/stake/index.tsx | 49 ++++++++++++++++++++ pages/stake/play.tsx | 72 +++++++++++++++++++++++++++++ 13 files changed, 408 insertions(+), 35 deletions(-) delete mode 100644 README.md create mode 100644 components/Planets/PlanetCard.tsx create mode 100644 components/Stake/ApproxRewards.tsx create mode 100644 components/Stake/LoadingSection.tsx create mode 100644 components/Stake/MintContainer.tsx create mode 100644 components/Stake/OwnedGear.tsx create mode 100644 components/Stake/Rewards.tsx create mode 100644 components/Stake/Shop.tsx create mode 100644 components/Stake/ShopItem.tsx create mode 100644 pages/planets/planet.tsx create mode 100644 pages/stake/index.tsx create mode 100644 pages/stake/play.tsx diff --git a/README.md b/README.md deleted file mode 100644 index b12f3e33..00000000 --- a/README.md +++ /dev/null @@ -1,34 +0,0 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). - -## Getting Started - -First, run the development server: - -```bash -npm run dev -# or -yarn dev -``` - -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. - -You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file. - -[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.js`. - -The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages. - -## Learn More - -To learn more about Next.js, take a look at the following resources: - -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. - -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! - -## Deploy on Vercel - -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. diff --git a/components/Planets/PlanetCard.tsx b/components/Planets/PlanetCard.tsx new file mode 100644 index 00000000..3a34a7b0 --- /dev/null +++ b/components/Planets/PlanetCard.tsx @@ -0,0 +1,18 @@ +import React, { useEffect, useState } from "react"; +import Card from "../Card"; + +import { useSupabaseClient } from "@supabase/auth-helpers-react"; + +export function PlanetCard ({ activeTab }) { + const supabase = useSupabaseClient(); + + return ( +
+ {activeTab === 'planet' && ( +
+ Planet Name +
+ )}; +
+ ); +}; \ No newline at end of file diff --git a/components/Stake/ApproxRewards.tsx b/components/Stake/ApproxRewards.tsx new file mode 100644 index 00000000..fdc5f10f --- /dev/null +++ b/components/Stake/ApproxRewards.tsx @@ -0,0 +1,42 @@ +import React, { useState, useEffect } from "react"; + +import { useAddress } from "@thirdweb-dev/react"; +import { SmartContract } from "@thirdweb-dev/sdk"; +import { ethers } from "ethers"; +import ContractMappingResponse from "../../constants/contractMappingResponse"; + +type Props = { helperContract: SmartContract; }; + +export default function ApproxRewards ({ helperContract }: Props ) { // Calls contract to estimate the rewards owed to the authenticated player/user + const address = useAddress(); + const everyMillisecondAmount = parseInt( + (10_000_000_000_000 / 2.1).toFixed(0) // Assumes each block (on EVM) takes ~2.1 seconds to be mined. Begins when component isMounted + ); + + const [amount, setAmount] = useState(0); + const [multiplier, setMultiplier] = useState(0); + + useEffect(() => { + (async () => { + if (!address) return; + const p = ( await helperContract.call( 'playerHelper', address, )) as ContractMappingResponse; + if (p.isData) { // If a multitool owned by the player IS staked/equipped + setMultiplier(p.value.toNumber() + 1); // A better multitool (derived as tokenId of multitool contract) gives better rewards + } else { setMultiplier(0); }; + })(); + }, [address, helperContract]); + + useEffect(() => { // Update the amount in state based on everyMillisecondAmount + const interval = setInterval(() => { setAmount( amount + everyMillisecondAmount ); }, 100); // update token amount (earned from staking) + return () => clearInterval(interval); // Clear when the component unmounts + }, [amount, everyMillisecondAmount]); + + return ( +

+ Earned this session: {" "} + + {ethers.utils.formatEther((amount * multiplier).toFixed(0)) || "Error..."} + +

+ ); +} \ No newline at end of file diff --git a/components/Stake/LoadingSection.tsx b/components/Stake/LoadingSection.tsx new file mode 100644 index 00000000..520a2fff --- /dev/null +++ b/components/Stake/LoadingSection.tsx @@ -0,0 +1,4 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +export default function LoadingSection () { return
Loading...
; }; \ No newline at end of file diff --git a/components/Stake/MintContainer.tsx b/components/Stake/MintContainer.tsx new file mode 100644 index 00000000..b36eaeec --- /dev/null +++ b/components/Stake/MintContainer.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/Home.module.css'; + +import { useAddress, useClaimNFT, useEditionDrop, Web3Button } from "@thirdweb-dev/react"; +import { PLANETS_ADDRESS } from "../../constants/contractAddresses"; + +export default function MintContainer () { + const editionDrop = useEditionDrop(PLANETS_ADDRESS); + const { mutate: claim } = useClaimNFT(editionDrop); + const address = useAddress(); + + return ( +
+

Edition drop

+

Claim your planet NFT to start playing

+
+ +
+ { + claim({ + quantity: 1, + to: address!, + tokenId: 0, // Claim the first nft/planet in the collection. This mutate function will be updated, with the specific value being generated from our Flask API + }); + }} + accentColor="#f5f" + colorMode='dark' + >Claim the first planet! +
+ ); +} \ No newline at end of file diff --git a/components/Stake/OwnedGear.tsx b/components/Stake/OwnedGear.tsx new file mode 100644 index 00000000..3d01a627 --- /dev/null +++ b/components/Stake/OwnedGear.tsx @@ -0,0 +1,62 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +import { ThirdwebNftMedia, useAddress, useOwnedNFTs, Web3Button } from "@thirdweb-dev/react"; +import { EditionDrop, SmartContract } from "@thirdweb-dev/sdk"; + +import LoadingSection from "./LoadingSection"; +import { HELPER_ADDRESS } from "../../constants/contractAddresses"; + +type Props = { multitoolContract: EditionDrop, helperContract: SmartContract; }; + +export default function OwnedGear ({ multitoolContract, helperContract }: Props) { // Shows the multitools in a user's wallet and allows them to stake/equip said multitools + const address = useAddress(); + const { data: ownedMultitools, isLoading } = useOwnedNFTs( // Which nfts does the user hold from the multitools contract + multitoolContract, + address, + ); + + if (isLoading) { + return + } + + async function equip(id: string) { + if (!address) return; + const hasApproval = await multitoolContract.isApproved( // Is the contract approved by the account to be able to transfer the multitool? + address, + HELPER_ADDRESS + ); + + if (!hasApproval) { + await multitoolContract.setApprovalForAll(HELPER_ADDRESS, true); + }; + + await helperContract.call("stake", id); + + window.location.reload(); + } + + return ( + <> +
+ {ownedMultitools?.map((p) => ( +
+ +

{p.metadata.name}

+ + equip(p.metadata.id)} + > + Equip + +
+ ))} +
+ +); +} \ No newline at end of file diff --git a/components/Stake/Rewards.tsx b/components/Stake/Rewards.tsx new file mode 100644 index 00000000..22d41828 --- /dev/null +++ b/components/Stake/Rewards.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +import { ThirdwebNftMedia, useAddress, useMetadata, useContractRead, useTokenBalance, Web3Button } from "@thirdweb-dev/react"; +import { SmartContract, Token } from "@thirdweb-dev/sdk"; +import { ethers } from "ethers"; +import { HELPER_ADDRESS } from "../../constants/contractAddresses"; // Create a param/argument so that the helper contract can be changed per page like the rewards/planet (aka character) contracts + +import ApproxRewards from "./ApproxRewards"; + +type Props = { helperContract: SmartContract; rewardsContract: Token; }; + +export default function Rewards({ helperContract, rewardsContract }: Props ) { // Shows the token metadata, amount of tokens in wlalet, claimable amount (reward) + const address = useAddress(); + const { data: tokenMetadata } = useMetadata(rewardsContract); + const { data: currentBalance } = useTokenBalance(rewardsContract, address); + const { data: unclaimedAmount } = useContractRead( + helperContract, + 'calculateRewards', + address + ); + + return ( +
+

Your Minerals

+ {tokenMetadata && ( // If exists/loaded + + )} +

+ Balance: {currentBalance?.displayValue} +

+

+ Unclaimed: {" "} + {unclaimedAmount && ethers.utils.formatUnits(unclaimedAmount)} +

+ +
+ contract.call('claim')} + >Claim Rewards +
+
+ ); +} \ No newline at end of file diff --git a/components/Stake/Shop.tsx b/components/Stake/Shop.tsx new file mode 100644 index 00000000..c80530ac --- /dev/null +++ b/components/Stake/Shop.tsx @@ -0,0 +1,26 @@ +import React, { useEffect } from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +import { ThirdwebNftMedia, useNFTs } from "@thirdweb-dev/react"; +import { EditionDrop } from "@thirdweb-dev/sdk"; + +import { ShopItem } from "."; + +type Props = { multitoolContract: EditionDrop; }; + +export default function Shop ({ multitoolContract }: Props ) { // Shows all available multitools, their price, and a button to purchase them + const { data: availableMultitools } = useNFTs(multitoolContract); + return ( + <> +
+ {availableMultitools?.map((p) => ( + + ))} +
+ + ) +} \ No newline at end of file diff --git a/components/Stake/ShopItem.tsx b/components/Stake/ShopItem.tsx new file mode 100644 index 00000000..c16dec71 --- /dev/null +++ b/components/Stake/ShopItem.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +import { ThirdwebNftMedia, useActiveClaimCondition, Web3Button } from "@thirdweb-dev/react"; +import { ethers } from "ethers"; +import { NFT, EditionDrop } from "@thirdweb-dev/sdk"; + +import { MULTITOOLS_ADDRESS } from "../../constants/contractAddresses"; + +type Props = { multitoolContract: EditionDrop; item: NFT; }; + +export default function ShopItem ({ item, multitoolContract }: Props ) { + const { data: claimCondition } = useActiveClaimCondition( + multitoolContract, + item.metadata.id, + ); + + return ( +
+ +

{item.metadata.name}

+

+ Price:{" "} + + {claimCondition && ethers.utils.formatUnits(claimCondition?.price)}{" "} + Minerals + +

+ +
+ contract.erc1155.claim(item.metadata.id, 1)} + onSuccess={() => alert("Purchased!")} + onError={(error) => alert(error)} + >Buy +
+
+ ); +}; \ No newline at end of file diff --git a/pages/planets/planet.tsx b/pages/planets/planet.tsx new file mode 100644 index 00000000..4a57f068 --- /dev/null +++ b/pages/planets/planet.tsx @@ -0,0 +1,8 @@ +import React, { useState, useEffect } from "react"; +import { useRouter } from "next/router"; + +import Layout, { ProfileLayout } from "../../components/Layout"; +import Card from "../../components/Card"; + +// import { Database } from "../../utils/database.types"; // Use this for later when we are drawing from the Planets table +// type Planets = Database['public']['Tables']['planets']['Row']; \ No newline at end of file diff --git a/pages/posts/Profile.tsx b/pages/posts/Profile.tsx index e5b14bf1..870b99b6 100644 --- a/pages/posts/Profile.tsx +++ b/pages/posts/Profile.tsx @@ -1,6 +1,6 @@ import Layout, { ProfileLayout } from "../../components/Layout"; import Card from "../../components/Card"; -import {useRouter} from "next/router"; +import { useRouter } from "next/router"; import React, { useEffect, useState} from "react"; import { Database } from "../../utils/database.types"; import { useSupabaseClient, useSession } from "@supabase/auth-helpers-react"; diff --git a/pages/stake/index.tsx b/pages/stake/index.tsx new file mode 100644 index 00000000..20cca60f --- /dev/null +++ b/pages/stake/index.tsx @@ -0,0 +1,49 @@ +import type { NextPage } from "next"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; +import { useRouter } from "next/router"; + +import { ConnectWallet, useAddress, /* useEditionDrop, */ useOwnedNFTs, useContract } from "@thirdweb-dev/react"; + +import MintContainer from "../../components/Stake/MintContainer"; +import { PLANETS_ADDRESS } from "../../constants/contractAddresses"; + +const StakingHome: NextPage = () => { + const { contract: editionDrop} = useContract(PLANETS_ADDRESS, 'edition-drop'); + const address = useAddress(); + const router = useRouter(); + const { data: ownedNfts, isLoading, isError, } = useOwnedNFTs(editionDrop, address); + if (!address) { // Enable users to connect their wallet if there isn't a connected wallet + return ( +
+ +
+ ); + } + + if (isLoading) { + return
Loading
; + } + + if (!ownedNfts || isError) { + return
Error
; + } + + if (ownedNfts.length === 0) { // If the connected wallet has 0 NFTs in the planet collection + return ( +
+ +
+ ) + } + + return ( // Show this if the connected address has an NFT from the planet collection +
+ +
+ ); + }; + + export default StakingHome; \ No newline at end of file diff --git a/pages/stake/play.tsx b/pages/stake/play.tsx new file mode 100644 index 00000000..48e256f7 --- /dev/null +++ b/pages/stake/play.tsx @@ -0,0 +1,72 @@ +import React from "react"; +import styles from '../../styles/Staking-P2E/planetInteraction.module.css'; + +import { ConnectWallet, useAddress, useContract } from "@thirdweb-dev/react"; + +import { ApproxRewards, CurrentGear, GameplayAnimation, LoadingSection, OwnedGear, Rewards, Shop, ShopItem } from "../../components/Stake"; +import { HELPER_ADDRESS, PLANETS_ADDRESS, MINERALS_ADDRESS, MULTITOOLS_ADDRESS } from "../../constants/contractAddresses"; + +export default function Play() { + const address = useAddress(); // Connect to user wallet + const { contract: helperContract } = useContract(HELPER_ADDRESS); // Connect to all the contracts relevant to this page + const { contract: planetContract } = useContract(PLANETS_ADDRESS, 'edition-drop'); + const { contract: multitoolContract } = useContract(MULTITOOLS_ADDRESS, 'edition-drop'); + const { contract: rewardsContract } = useContract(MINERALS_ADDRESS, 'token'); // Could be for any type of reward/token (e.g. gas, water, minerals). In this case, it's minerals + if (!address) { // If user isn't authenticated via Thirdweb + return ( // This should only happen if an unauthenticated user navigates directly to `/play`, as the only components that point here are locked behind the component +
+ +
+ ); + }; + + return ( +
+ {helperContract && + planetContract && + rewardsContract && + multitoolContract ? ( // If all the contracts have loaded in +
+ + +
+ ) : ( // Contracts are still loading in + + )} + +
+ {multitoolContract && helperContract ? ( + <> +

Your multitools

+
+ +
+ + ) : ( + + )} + +
+ {multitoolContract && rewardsContract ? ( + <> +

Shop

+
+ +
+ + ) : ( + + )} +
+ ); +}; \ No newline at end of file