├── .env.local ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── components ├── elements │ ├── Claiming.tsx │ ├── Fade.tsx │ ├── Footer.tsx │ ├── Header.tsx │ ├── Loading.tsx │ ├── NFTList.tsx │ ├── NftImage.tsx │ ├── NftImagesSlideShow.tsx │ ├── NftListItem.tsx │ ├── NoListItems.tsx │ ├── SnsLinks.tsx │ ├── SpMenu.tsx │ └── Wallet.tsx └── templates │ ├── Collection.tsx │ ├── Minting.tsx │ └── Owned.tsx ├── contexts └── NftContractProvider.tsx ├── hooks ├── useConnectWallet.ts └── useMint.ts ├── layouts └── CommonLayout.tsx ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── _document.tsx ├── api │ └── hello.ts ├── collection.tsx ├── index.tsx └── owned.tsx ├── public ├── assets │ ├── bg │ │ └── bg.svg │ ├── favicon │ │ ├── androidchrome_192x192.png │ │ ├── androidchrome_256x256.png │ │ ├── apple-touch-icon180x180.png │ │ ├── favicon_152x152.png │ │ ├── favicon_16x16.png │ │ ├── favicon_196x196.png │ │ ├── favicon_24x24.png │ │ ├── favicon_260x260-1.png │ │ ├── favicon_260x260.png │ │ ├── favicon_32x32.png │ │ ├── favicon_48x48.png │ │ └── favicon_96x96.png │ ├── icon │ │ ├── icon_black_close.svg │ │ └── icon_black_menu.svg │ └── logo │ │ ├── logo_black_discord.svg │ │ ├── logo_black_etherscan.svg │ │ ├── logo_black_instagram.svg │ │ ├── logo_black_internet.svg │ │ ├── logo_black_opensea.svg │ │ ├── logo_black_telegram.svg │ │ ├── logo_black_twitter.svg │ │ ├── logo_etherscan.svg │ │ ├── logo_internet.svg │ │ ├── logo_opensea.svg │ │ └── logo_twitter.svg ├── favicon.ico └── vercel.svg ├── styles └── globals.css ├── tsconfig.json ├── utils ├── colors.ts ├── const.ts ├── snsLinks.ts └── theme.ts └── yarn.lock /.env.local: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_CONTRACT_ADDRESS=0x739b366548117dd5BEf5b8B5573246dE841AF950 2 | NEXT_PUBLIC_CHAIN_ID=5 3 | NEXT_PUBLIC_CONTRACT_NAME=OMOCHI_Goerli 4 | 5 | NEXT_PUBLIC_TWITER_ACCOUNT= 6 | NEXT_PUBLIC_INSTAGRAM_ACCOUNT= 7 | NEXT_PUBLIC_DISCORD_URL= -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'next', 4 | 'next/core-web-vitals', 5 | 'react-app', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react/recommended', 8 | 'plugin:prettier/recommended', 9 | ], 10 | parser: '@typescript-eslint/parser', 11 | parserOptions: { 12 | ecmaVersion: 2018, 13 | sourceType: 'module', 14 | ecmaFeatures: { 15 | jsx: true, 16 | }, 17 | }, 18 | rules: { 19 | 'react/prop-types': 0, 20 | 'react/react-in-jsx-scope': 'off', 21 | 'react/jsx-no-target-blank': 'error', 22 | '@typescript-eslint/explicit-function-return-type': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | }, 25 | settings: { 26 | react: { 27 | version: 'detect', 28 | }, 29 | }, 30 | ignorePatterns: ['src/generated/*.tsx'], 31 | } 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "es5", 4 | "singleQuote": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NFTDrop(thirdweb )nextjs example 2 | 3 | This templates demonstrate the frontend connecting NFTDrop thirdweb's pre-build contract. 4 | 5 | ## Demo 6 | 7 | https://nftdrop-example.hanzochang.com 8 | 9 | スクリーンショット 2022-07-01 9 48 49 10 | 11 | Connecting Ethereum testnet(rinkeby). 12 | 13 | ## Features 14 | 15 | - mint 16 | - collection 17 | - owned collection 18 | 19 | ## Setup 20 | 21 | ### Install 22 | 23 | ``` 24 | yarn install 25 | ``` 26 | 27 | ### Run 28 | 29 | ``` 30 | yarn dev 31 | ``` 32 | 33 | Open http://localhost:3000 with your browser to see the result. 34 | 35 | ## Learn More 36 | 37 | (Japanese documentation) thirdweb の使い方 - 独自コントラクトの作成と「NFT の MINT サイト」の作り方を紹介 38 | 39 | https://hanzochang.com/articles/10 40 | -------------------------------------------------------------------------------- /components/elements/Claiming.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Spinner, Text, VStack } from '@chakra-ui/react' 2 | 3 | const Component = () => { 4 | return ( 5 | 14 | 21 | 22 | 23 | Claiming ... 24 | 25 | 26 | 27 | ) 28 | } 29 | 30 | export { Component as Claiming } 31 | -------------------------------------------------------------------------------- /components/elements/Fade.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from 'framer-motion' 2 | import React, { ReactElement } from 'react' 3 | 4 | const Component: React.FC<{ children: ReactElement }> = ({ children }) => { 5 | return ( 6 | 7 | 13 | {children} 14 | 15 | 16 | ) 17 | } 18 | 19 | export { Component as Fade } 20 | -------------------------------------------------------------------------------- /components/elements/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Flex, Text } from '@chakra-ui/react' 2 | import Link from 'next/link' 3 | import { SnsLinks } from './SnsLinks' 4 | 5 | const Component: React.FC = () => { 6 | return ( 7 | 15 | 23 | NFTDrop 24 | 25 | 26 | 27 | 28 | 29 | ) 30 | } 31 | 32 | export { Component as Footer } 33 | -------------------------------------------------------------------------------- /components/elements/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Flex, Text } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | import Link from 'next/link' 4 | import { useRouter } from 'next/router' 5 | import { SnsLinks } from './SnsLinks' 6 | import { Wallet } from './Wallet' 7 | import { NftContractContext } from '../../contexts/NftContractProvider' 8 | import { useContext } from 'react' 9 | 10 | const Component: React.FC = () => { 11 | const router = useRouter() 12 | const store = useContext(NftContractContext) 13 | 14 | return ( 15 | 24 | 32 | 33 | 34 | NFTDrop 35 | 36 | 37 | 38 | 39 | 40 | 41 | 50 | MINT 51 | 52 | 53 | 54 | 65 | COLLECTION 66 | 67 | 68 | 69 | 78 | OWNED 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | { 93 | store.setSpMenuOpened && store.setSpMenuOpened(true) 94 | }} 95 | > 96 | 101 | 102 | 103 | 104 | 105 | ) 106 | } 107 | 108 | export { Component as Header } 109 | -------------------------------------------------------------------------------- /components/elements/Loading.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Spinner, Text, VStack } from '@chakra-ui/react' 2 | 3 | const Component = () => { 4 | return ( 5 | 12 | 13 | 14 | Loading contract ... 15 | 16 | 17 | ) 18 | } 19 | 20 | export { Component as Loading } 21 | -------------------------------------------------------------------------------- /components/elements/NFTList.tsx: -------------------------------------------------------------------------------- 1 | import { useAddress, useContract, useNFTDrop } from '@thirdweb-dev/react' 2 | import { useEffect, useState } from 'react' 3 | import { NULL_ADDRESS } from '../../utils/const' 4 | 5 | const Component: React.FC = () => { 6 | const { data: nftDrop } = useContract( 7 | process.env.NEXT_PUBLIC_CONTRACT_ADDRESS, 8 | 'nft-drop' 9 | ) 10 | const [allTokens, setAllTokens] = useState>([]) 11 | const [mintPrice, setMintPrice] = useState('') 12 | const address = useAddress() 13 | 14 | useEffect(() => { 15 | nftDrop?.claimConditions.getActive().then((activeClaimCondition) => { 16 | setMintPrice(activeClaimCondition.price._hex) 17 | }) 18 | 19 | nftDrop?.getAll().then((results) => { 20 | setAllTokens(results) 21 | }) 22 | }, [nftDrop]) 23 | 24 | return ( 25 | <> 26 |
33 | {allTokens.map((token, index) => { 34 | const ownerExists: boolean = token.owner !== NULL_ADDRESS 35 | return ( 36 |
37 |

{token.metadata.name}

38 | 39 |
40 | {!ownerExists ? ( 41 | <> 42 | No Owner 43 |
44 |
45 | 46 | ) : ( 47 | 53 | {token.owner} 54 | 55 | )} 56 |
57 |
owner? : {token.owner === address ? 'true' : 'false'}
58 |
59 |
60 | ) 61 | })} 62 |
63 | 64 | ) 65 | } 66 | 67 | export { Component as NFTList } 68 | -------------------------------------------------------------------------------- /components/elements/NftImage.tsx: -------------------------------------------------------------------------------- 1 | import { AspectRatio, Flex, Spinner, Text } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | import React from 'react' 4 | 5 | export type NftImageProps = { 6 | imageUri?: string 7 | } 8 | 9 | const Component: React.FC = ({ imageUri }) => { 10 | return imageUri ? ( 11 | 17 | <> 18 | 27 | 28 | 29 | 30 | 31 | 32 | ) : ( 33 | 34 | 35 | noImage 36 | 37 | 38 | ) 39 | } 40 | 41 | export { Component as NftImage } 42 | -------------------------------------------------------------------------------- /components/elements/NftImagesSlideShow.tsx: -------------------------------------------------------------------------------- 1 | import { AnimatePresence, motion } from 'framer-motion' 2 | import { useContext, useEffect, useState } from 'react' 3 | import { NftContractContext } from '../../contexts/NftContractProvider' 4 | 5 | import { NftImage } from './NftImage' 6 | 7 | const Component: React.FC = () => { 8 | const [allTokens, setAllTokens] = useState>([]) 9 | const [rand, setRand] = useState(0) 10 | 11 | const store = useContext(NftContractContext) 12 | 13 | const token: any | undefined = 14 | allTokens && allTokens[Math.floor(rand * allTokens.length)] 15 | 16 | useEffect(() => { 17 | const interval = setInterval(() => { 18 | setRand(Math.random()) 19 | }, 3000) 20 | 21 | return () => clearInterval(interval) 22 | }, []) 23 | 24 | useEffect(() => { 25 | setAllTokens(store.allTokens) 26 | }, [store]) 27 | 28 | return token ? ( 29 | 30 | 37 | 38 | 39 | 40 | ) : ( 41 | <> 42 | ) 43 | } 44 | 45 | export { Component as NftImagesSlideShow } 46 | -------------------------------------------------------------------------------- /components/elements/NftListItem.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Text } from '@chakra-ui/react' 2 | import React from 'react' 3 | import { NULL_ADDRESS } from '../../utils/const' 4 | import { NftImage } from './NftImage' 5 | 6 | export type NftListItemProps = { 7 | // TODO Define correct nft type 8 | token: any 9 | } 10 | 11 | const Component: React.FC = ({ token }) => { 12 | return ( 13 | 14 | 15 | 16 | 17 | 24 | {token.metadata.name} 25 | 26 | 27 | ) 28 | } 29 | 30 | export { Component as NftListItem } 31 | -------------------------------------------------------------------------------- /components/elements/NoListItems.tsx: -------------------------------------------------------------------------------- 1 | import { Flex, Text, VStack } from '@chakra-ui/react' 2 | 3 | const Component = () => { 4 | return ( 5 | 12 | 13 | No Items 14 | 15 | 16 | ) 17 | } 18 | 19 | export { Component as NoListItems } 20 | -------------------------------------------------------------------------------- /components/elements/SnsLinks.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | import { 4 | discordUrl, 5 | explorerUrl, 6 | instagramUrl, 7 | openseaUrl, 8 | twitterUrl, 9 | } from '../../utils/snsLinks' 10 | 11 | const LinkIcon: React.FC<{ 12 | func: () => string 13 | icon: string 14 | }> = ({ func, icon }: { func: () => string; icon: string }) => { 15 | return ( 16 | <> 17 | {!!func() && ( 18 | 32 | 33 | 34 | )} 35 | 36 | ) 37 | } 38 | 39 | const Component: React.FC = () => { 40 | return ( 41 | 42 | 46 | 50 | 54 | 58 | 62 | 63 | ) 64 | } 65 | 66 | export { Component as SnsLinks } 67 | -------------------------------------------------------------------------------- /components/elements/SpMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Flex, Text, VStack } from '@chakra-ui/react' 2 | import Image from 'next/image' 3 | import { NextRouter, useRouter } from 'next/router' 4 | import { useContext, useEffect, useState } from 'react' 5 | import { NftContractContext } from '../../contexts/NftContractProvider' 6 | import { SnsLinks } from './SnsLinks' 7 | import { motion } from 'framer-motion' 8 | 9 | const LinkListItem: React.FC<{ 10 | path: string 11 | router: NextRouter 12 | name: string 13 | closeSpMenu: () => void 14 | }> = ({ path, router, name, closeSpMenu }) => { 15 | const onClick = () => { 16 | closeSpMenu() 17 | router.push(path) 18 | } 19 | 20 | return ( 21 | 32 | ) 33 | } 34 | 35 | const Component: React.FC = () => { 36 | const router = useRouter() 37 | const store = useContext(NftContractContext) 38 | const [isOpen, setIsOpen] = useState(false) 39 | const closeDuration = 0.2 40 | 41 | useEffect(() => { 42 | if (store.spMenuOpened == true) { 43 | setIsOpen(true) 44 | } 45 | }, [store.spMenuOpened]) 46 | 47 | const closeSpMenu = () => { 48 | setIsOpen(false) 49 | setTimeout(() => { 50 | store.setSpMenuOpened && store.setSpMenuOpened(false) 51 | }, closeDuration * 1000 + 100) 52 | } 53 | 54 | return ( 55 | 56 | 66 | 67 | 74 | 75 | 81 | 82 | 83 | 90 | 91 | 97 | 103 | 109 | 110 | 111 | 118 | 119 | 120 | 121 | 122 | 123 | ) 124 | } 125 | 126 | export { Component as SpMenu } 127 | -------------------------------------------------------------------------------- /components/elements/Wallet.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Text } from '@chakra-ui/react' 2 | import { useDisconnect } from '@thirdweb-dev/react' 3 | import { useState } from 'react' 4 | import { useConnectWallet } from '../../hooks/useConnectWallet' 5 | 6 | const truncate = (str: string, len: number) => { 7 | return str.length <= len ? str : str.substr(0, len) + '...' 8 | } 9 | 10 | const Component: React.FC = () => { 11 | const { address, connectWallet } = useConnectWallet() 12 | const disconnectWallet = useDisconnect() 13 | const [addressHovering, setAddressHovering] = useState(false) 14 | 15 | return ( 16 | <> 17 | {address ? ( 18 |
setAddressHovering(true)} 20 | onMouseLeave={() => setAddressHovering(false)} 21 | > 22 | {addressHovering ? ( 23 | 31 | ) : ( 32 | 41 | )} 42 |
43 | ) : ( 44 | 52 | )} 53 | 54 | ) 55 | } 56 | 57 | export { Component as Wallet } 58 | -------------------------------------------------------------------------------- /components/templates/Collection.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SimpleGrid } from '@chakra-ui/react' 2 | import React, { useContext, useEffect, useState } from 'react' 3 | import { NftContractContext } from '../../contexts/NftContractProvider' 4 | import { Fade } from '../elements/Fade' 5 | import { NftListItem } from '../elements/NftListItem' 6 | import { NoListItems } from '../elements/NoListItems' 7 | 8 | const Component: React.FC = () => { 9 | const [allTokens, setAllTokens] = useState>([]) 10 | const store = useContext(NftContractContext) 11 | 12 | useEffect(() => { 13 | setAllTokens(store.allTokens) 14 | }, [store]) 15 | 16 | return ( 17 | 18 | 19 | 30 | {allTokens.map((token, index) => { 31 | return ( 32 | 33 | 34 | 35 | ) 36 | })} 37 | {allTokens.length == 0 && } 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export { Component as Collection } 45 | -------------------------------------------------------------------------------- /components/templates/Minting.tsx: -------------------------------------------------------------------------------- 1 | import { Box, Button, Flex, Text, VStack } from '@chakra-ui/react' 2 | import { useContext } from 'react' 3 | import { NftContractContext } from '../../contexts/NftContractProvider' 4 | import { useConnectWallet } from '../../hooks/useConnectWallet' 5 | import { useMint } from '../../hooks/useMint' 6 | import { Fade } from '../elements/Fade' 7 | import { NftImagesSlideShow } from '../elements/NftImagesSlideShow' 8 | 9 | import { useAddress } from '@thirdweb-dev/react' 10 | 11 | const Component: React.FC = () => { 12 | const store = useContext(NftContractContext) 13 | const address = useAddress() 14 | 15 | const { mint } = useMint() 16 | const { connectWallet } = useConnectWallet() 17 | 18 | return ( 19 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
33 | {address ? ( 34 | 39 | ) : ( 40 | 43 | )} 44 | 45 | {store.claimedSupply} / {store.totalSupply} 46 | 47 | 48 | goerli testnet 49 | 50 |
51 |
52 |
53 |
54 | ) 55 | } 56 | 57 | export { Component as Minting } 58 | -------------------------------------------------------------------------------- /components/templates/Owned.tsx: -------------------------------------------------------------------------------- 1 | import { Box, SimpleGrid, Flex } from '@chakra-ui/react' 2 | import React, { useContext, useEffect, useState } from 'react' 3 | import { NftContractContext } from '../../contexts/NftContractProvider' 4 | import { Fade } from '../elements/Fade' 5 | import { NftListItem } from '../elements/NftListItem' 6 | import { NoListItems } from '../elements/NoListItems' 7 | 8 | const Component: React.FC = () => { 9 | const [allTokens, setAllTokens] = useState>([]) 10 | const store = useContext(NftContractContext) 11 | 12 | useEffect(() => { 13 | setAllTokens(store.ownedTokens) 14 | }, [store]) 15 | 16 | return ( 17 | 18 | 19 | 30 | {allTokens.map((token, index) => { 31 | return ( 32 | 33 | 34 | 35 | ) 36 | })} 37 | {allTokens.length == 0 && } 38 | 39 | 40 | 41 | ) 42 | } 43 | 44 | export { Component as Owned } 45 | -------------------------------------------------------------------------------- /contexts/NftContractProvider.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | useAddress, 3 | useClaimedNFTSupply, 4 | useContract, 5 | useMetamask, 6 | useNetwork, 7 | useNetworkMismatch, 8 | useNFTDrop, 9 | useUnclaimedNFTSupply, 10 | } from '@thirdweb-dev/react' 11 | import { 12 | createContext, 13 | Dispatch, 14 | SetStateAction, 15 | useEffect, 16 | useState, 17 | } from 'react' 18 | import { ethers } from 'ethers' 19 | 20 | type Store = { 21 | isLoading: boolean 22 | allTokens: Array 23 | isClaiming: boolean 24 | setIsClaiming?: Dispatch> 25 | spMenuOpened: boolean 26 | setSpMenuOpened?: Dispatch> 27 | ownedTokens: Array 28 | setOwnedTokens?: Dispatch> 29 | claimPrice: string 30 | totalSupply: number 31 | claimedSupply: number 32 | } 33 | 34 | export const NftContractContext = createContext({ 35 | isLoading: true, 36 | allTokens: [], 37 | isClaiming: false, 38 | spMenuOpened: false, 39 | ownedTokens: [], 40 | claimPrice: '', 41 | totalSupply: 0, 42 | claimedSupply: 0, 43 | }) 44 | 45 | type Props = { 46 | children: React.ReactNode 47 | } 48 | 49 | const Component: React.FC = ({ children }: Props) => { 50 | const { data: nftDrop } = useContract( 51 | process.env.NEXT_PUBLIC_CONTRACT_ADDRESS, 52 | 'nft-drop' 53 | ) 54 | 55 | const address = useAddress() 56 | const [allTokens, setAllTokens] = useState>([]) 57 | const [isLoading, setIsLoading] = useState(true) 58 | const [isClaiming, setIsClaiming] = useState(false) 59 | const [spMenuOpened, setSpMenuOpened] = useState(false) 60 | const [ownedTokens, setOwnedTokens] = useState>([]) 61 | const [claimPrice, setClaimPrice] = useState('') 62 | const [totalSupply, setTotalSupply] = useState(0) 63 | const [claimedSupply, setClaimedSupply] = useState(0) 64 | 65 | const { data: unclaimedNft } = useUnclaimedNFTSupply(nftDrop) 66 | const { data: claimedNft } = useClaimedNFTSupply(nftDrop) 67 | 68 | useEffect(() => { 69 | nftDrop?.getAll().then((results) => { 70 | setAllTokens(results) 71 | setIsLoading(false) 72 | }) 73 | 74 | nftDrop?.claimConditions.getActive().then((activeClaimCondition) => { 75 | setClaimPrice(ethers.utils.formatUnits(activeClaimCondition.price._hex)) 76 | }) 77 | }, [nftDrop]) 78 | 79 | useEffect(() => { 80 | if (address) { 81 | let owneds: Array = [] 82 | 83 | allTokens.map((token) => { 84 | if (token.owner == address) { 85 | owneds.push(token) 86 | } 87 | }) 88 | 89 | setOwnedTokens(owneds) 90 | } 91 | 92 | setClaimedSupply(claimedNft?.toNumber() || 0) 93 | setTotalSupply( 94 | claimedNft && unclaimedNft 95 | ? claimedNft.toNumber() + unclaimedNft.toNumber() 96 | : 0 97 | ) 98 | }, [allTokens]) 99 | 100 | useEffect(() => { 101 | // TODO transaction終了時のみにする 102 | nftDrop?.getAll().then((results) => { 103 | setAllTokens(results) 104 | setIsLoading(false) 105 | }) 106 | 107 | nftDrop?.claimConditions.getActive().then((activeClaimCondition) => { 108 | setClaimPrice(ethers.utils.formatUnits(activeClaimCondition.price._hex)) 109 | }) 110 | }, [isClaiming]) 111 | 112 | const store: Store = { 113 | isLoading, 114 | allTokens, 115 | isClaiming, 116 | setIsClaiming, 117 | spMenuOpened, 118 | setSpMenuOpened, 119 | ownedTokens, 120 | claimPrice, 121 | claimedSupply, 122 | totalSupply, 123 | } 124 | 125 | return ( 126 | 127 | {children} 128 | 129 | ) 130 | } 131 | 132 | export { Component as NftContractProvider } 133 | -------------------------------------------------------------------------------- /hooks/useConnectWallet.ts: -------------------------------------------------------------------------------- 1 | import { 2 | useAddress, 3 | useMetamask, 4 | useNetwork, 5 | useNetworkMismatch, 6 | } from '@thirdweb-dev/react' 7 | 8 | export const useConnectWallet = () => { 9 | const address = useAddress() 10 | const connectWithMetamask = useMetamask() 11 | 12 | const [, switchNetwork] = useNetwork() 13 | const isOnWrongNetwork = useNetworkMismatch() 14 | 15 | const connectWallet = () => { 16 | if (!address) { 17 | connectWithMetamask() 18 | return 19 | } 20 | 21 | if (isOnWrongNetwork) { 22 | switchNetwork && switchNetwork(Number(process.env.NEXT_PUBLIC_CHAIN_ID)) 23 | return 24 | } 25 | } 26 | 27 | return { address, connectWallet } 28 | } 29 | -------------------------------------------------------------------------------- /hooks/useMint.ts: -------------------------------------------------------------------------------- 1 | import { useContract, useNFTDrop } from '@thirdweb-dev/react' 2 | import { useContext } from 'react' 3 | 4 | import { 5 | useAddress, 6 | useMetamask, 7 | useNetwork, 8 | useNetworkMismatch, 9 | } from '@thirdweb-dev/react' 10 | 11 | import { NftContractContext } from '../contexts/NftContractProvider' 12 | 13 | export const useMint = () => { 14 | const { data: nftDrop } = useContract( 15 | process.env.NEXT_PUBLIC_CONTRACT_ADDRESS, 16 | 'nft-drop' 17 | ) 18 | const store = useContext(NftContractContext) 19 | 20 | const address = useAddress() 21 | const connectWithMetamask = useMetamask() 22 | const [, switchNetwork] = useNetwork() 23 | const isOnWrongNetwork = useNetworkMismatch() 24 | 25 | const mint = async () => { 26 | if (!address) { 27 | connectWithMetamask() 28 | return 29 | } 30 | 31 | if (isOnWrongNetwork) { 32 | switchNetwork && switchNetwork(Number(process.env.NEXT_PUBLIC_CHAIN_ID)) 33 | return 34 | } 35 | 36 | store.setIsClaiming && store.setIsClaiming(true) 37 | 38 | try { 39 | const minted = await nftDrop?.claim(1) 40 | alert(`Successfully minted NFT!`) 41 | } catch (error) { 42 | console.error(error) 43 | alert(error) 44 | } finally { 45 | store.setIsClaiming && store.setIsClaiming(false) 46 | } 47 | } 48 | 49 | return { mint } 50 | } 51 | -------------------------------------------------------------------------------- /layouts/CommonLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Box } from '@chakra-ui/react' 2 | import type { ReactElement } from 'react' 3 | import { useContext } from 'react' 4 | import { Claiming } from '../components/elements/Claiming' 5 | import { Header } from '../components/elements/Header' 6 | import { Loading } from '../components/elements/Loading' 7 | import { SpMenu } from '../components/elements/SpMenu' 8 | import { NftContractContext } from '../contexts/NftContractProvider' 9 | 10 | const Layout = ({ children }: { children: ReactElement }) => { 11 | const store = useContext(NftContractContext) 12 | 13 | return ( 14 | 15 |
16 | {store.spMenuOpened && } 17 | {store.isClaiming && } 18 | 25 | {store.isLoading ? : <>{children}} 26 | 27 | 28 | ) 29 | } 30 | 31 | export { Layout as CommonLayout } 32 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = { 7 | ...nextConfig, 8 | images: { 9 | domains: ['gateway.ipfscdn.io'], 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thirdweb-nftdrop-nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@chakra-ui/react": "^2.2.1", 13 | "@emotion/react": "^11", 14 | "@emotion/styled": "^11", 15 | "@thirdweb-dev/react": "^3.6.8", 16 | "@thirdweb-dev/sdk": "^3.6.8", 17 | "ethers": "^5.6.9", 18 | "framer-motion": "^6", 19 | "next": "12.1.6", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0" 22 | }, 23 | "devDependencies": { 24 | "@types/node": "18.0.0", 25 | "@types/react": "18.0.14", 26 | "@types/react-dom": "18.0.5", 27 | "@typescript-eslint/eslint-plugin": "^5.29.0", 28 | "@typescript-eslint/parser": "^5.29.0", 29 | "eslint": "^8.18.0", 30 | "eslint-config-next": "12.1.6", 31 | "eslint-config-prettier": "^8.5.0", 32 | "eslint-plugin-prettier": "^4.0.0", 33 | "eslint-plugin-react": "^7.30.0", 34 | "prettier": "^2.7.1", 35 | "typescript": "4.7.4" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactElement, ReactNode } from 'react' 2 | import type { NextPage } from 'next' 3 | import type { AppProps } from 'next/app' 4 | import { ChainId, ThirdwebProvider } from '@thirdweb-dev/react' 5 | import { ChakraProvider } from '@chakra-ui/react' 6 | import { theme } from '../utils/theme' 7 | import { NftContractProvider } from '../contexts/NftContractProvider' 8 | 9 | export type NextPageWithLayout = NextPage & { 10 | getLayout?: (page: ReactElement) => ReactNode 11 | } 12 | 13 | type AppPropsWithLayout = AppProps & { 14 | Component: NextPageWithLayout 15 | } 16 | 17 | // const chainId = ChainId.Goerli 18 | const activeChainId: number = parseInt(`${process.env.NEXT_PUBLIC_CHAIN_ID}`) 19 | 20 | function MyApp({ Component, pageProps }: AppPropsWithLayout) { 21 | const getLayout = Component.getLayout ?? ((page) => page) 22 | 23 | return ( 24 | 25 | 26 | 27 | {getLayout()} 28 | 29 | 30 | 31 | ) 32 | } 33 | 34 | export default MyApp 35 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | // pages/_document.js 2 | 3 | import { ColorModeScript } from '@chakra-ui/react' 4 | import NextDocument, { Html, Head, Main, NextScript } from 'next/document' 5 | import { theme } from '../utils/theme' 6 | 7 | export default class Document extends NextDocument { 8 | render() { 9 | return ( 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: '' }) 13 | } 14 | -------------------------------------------------------------------------------- /pages/collection.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import { Collection } from '../components/templates/Collection' 4 | import type { NextPageWithLayout } from './_app' 5 | import type { ReactElement } from 'react' 6 | import { CommonLayout } from '../layouts/CommonLayout' 7 | import { Fade } from '../components/elements/Fade' 8 | 9 | const Home: NextPageWithLayout = () => { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | Home.getLayout = function getLayout(page: ReactElement) { 23 | return {page} 24 | } 25 | 26 | export default Home 27 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import { Minting } from '../components/templates/Minting' 4 | import { CommonLayout } from '../layouts/CommonLayout' 5 | import type { ReactElement } from 'react' 6 | import type { NextPageWithLayout } from './_app' 7 | import { Fade } from '../components/elements/Fade' 8 | 9 | const Home: NextPageWithLayout = () => { 10 | return ( 11 | <> 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ) 20 | } 21 | 22 | Home.getLayout = function getLayout(page: ReactElement) { 23 | return {page} 24 | } 25 | 26 | export default Home 27 | -------------------------------------------------------------------------------- /pages/owned.tsx: -------------------------------------------------------------------------------- 1 | import type { NextPage } from 'next' 2 | import Head from 'next/head' 3 | import { Collection } from '../components/templates/Collection' 4 | import type { NextPageWithLayout } from './_app' 5 | import type { ReactElement } from 'react' 6 | import { CommonLayout } from '../layouts/CommonLayout' 7 | import { Fade } from '../components/elements/Fade' 8 | import { Owned } from '../components/templates/Owned' 9 | 10 | const Home: NextPageWithLayout = () => { 11 | return ( 12 | <> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | ) 21 | } 22 | 23 | Home.getLayout = function getLayout(page: ReactElement) { 24 | return {page} 25 | } 26 | 27 | export default Home 28 | -------------------------------------------------------------------------------- /public/assets/favicon/androidchrome_192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/androidchrome_192x192.png -------------------------------------------------------------------------------- /public/assets/favicon/androidchrome_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/androidchrome_256x256.png -------------------------------------------------------------------------------- /public/assets/favicon/apple-touch-icon180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/apple-touch-icon180x180.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_152x152.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_16x16.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_196x196.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_196x196.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_24x24.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_260x260-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_260x260-1.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_260x260.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_260x260.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_32x32.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_48x48.png -------------------------------------------------------------------------------- /public/assets/favicon/favicon_96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/assets/favicon/favicon_96x96.png -------------------------------------------------------------------------------- /public/assets/icon/icon_black_close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/assets/icon/icon_black_menu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_discord.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_etherscan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_internet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_opensea.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_telegram.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_black_twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_etherscan.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /public/assets/logo/logo_internet.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/assets/logo/logo_opensea.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/assets/logo/logo_twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hanzochang/thirdweb-nftdrop-nextjs/66dae096567ee38f86081da128919dfd20ef1ded/public/favicon.ico -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | padding: 0; 4 | margin: 0; 5 | font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, 6 | Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; 7 | } 8 | 9 | a { 10 | color: inherit; 11 | text-decoration: none; 12 | } 13 | 14 | * { 15 | box-sizing: border-box; 16 | } 17 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /utils/colors.ts: -------------------------------------------------------------------------------- 1 | export const colors = {} 2 | -------------------------------------------------------------------------------- /utils/const.ts: -------------------------------------------------------------------------------- 1 | export const NULL_ADDRESS = '0x0000000000000000000000000000000000000000' 2 | -------------------------------------------------------------------------------- /utils/snsLinks.ts: -------------------------------------------------------------------------------- 1 | export const explorerUrl = () => { 2 | const address = process.env.NEXT_PUBLIC_CONTRACT_ADDRESS 3 | 4 | switch (process.env.NEXT_PUBLIC_CHAIN_ID) { 5 | case '1': 6 | // Mainnet 7 | return `https://etherscan.io/token/${address}` 8 | case '4': 9 | // Rinkeby 10 | return `https://rinkeby.etherscan.io/token/${address}` 11 | case '5': 12 | // Goerli 13 | return `https://goerli.etherscan.io/token/${address}` 14 | case '137': 15 | // Polygon 16 | return `https://polygonscan.com/token/${address}` 17 | case '80001': 18 | // Munbai 19 | return `https://mumbai.polygonscan.com/token/${address}` 20 | case '1666600000': 21 | // Harmony 22 | return `https://explorer.harmony.one/address/${address}` 23 | default: 24 | return '' 25 | } 26 | } 27 | 28 | export const openseaUrl = () => { 29 | const name = process.env.NEXT_PUBLIC_CONTRACT_NAME 30 | 31 | switch (process.env.NEXT_PUBLIC_CHAIN_ID) { 32 | case '1': 33 | // Mainnet 34 | return `https://opensea.io/collection/${name}` 35 | case '4': 36 | // Rinkeby 37 | return `https://testnets.opensea.io/collection/${name}` 38 | default: 39 | return `` 40 | } 41 | } 42 | 43 | export const twitterUrl = () => { 44 | return `https://twitter.com/${process.env.NEXT_PUBLIC_TWITER_ACCOUNT}` 45 | } 46 | 47 | export const instagramUrl = () => { 48 | return `https://www.instagram.com/${process.env.NEXT_PUBLIC_INSTAGRAM_ACCOUNT}` 49 | } 50 | 51 | export const discordUrl = () => { 52 | return `${process.env.NEXT_PUBLIC_DISCORD_URL}` 53 | } 54 | 55 | export const snsLinks = { 56 | explorerUrl, 57 | openseaUrl, 58 | twitterUrl, 59 | instagramUrl, 60 | discordUrl, 61 | } 62 | -------------------------------------------------------------------------------- /utils/theme.ts: -------------------------------------------------------------------------------- 1 | import { extendTheme } from '@chakra-ui/react' 2 | import { colors } from '../utils/colors' 3 | 4 | export const theme = extendTheme({ 5 | textStyles: { 6 | logo: { 7 | fontSize: { base: '18px', md: '24px' }, 8 | fontWeight: 'bold', 9 | lineHeight: '100%', 10 | letterSpacing: '', 11 | }, 12 | }, 13 | colors, 14 | }) 15 | --------------------------------------------------------------------------------