├── .eslintrc.json ├── .gitignore ├── README.md ├── components ├── AuthButton.tsx ├── Comment.tsx ├── CommentEditor.tsx ├── Comments.tsx ├── README.md └── Username.tsx ├── contracts └── Comments.sol ├── hardhat.config.js ├── hooks ├── README.md ├── useAddComment.ts ├── useComments.ts ├── useCommentsContract.ts └── useEvents.ts ├── next-env.d.ts ├── next.config.js ├── package-lock.json ├── package.json ├── pages ├── _app.tsx └── index.tsx ├── public ├── favicon.ico └── vercel.svg ├── scripts └── deploy-and-seed.js ├── test └── basic.js ├── theme.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 27 | # local env files 28 | .env.local 29 | .env.development.local 30 | .env.test.local 31 | .env.production.local 32 | 33 | # vercel 34 | .vercel 35 | 36 | # typescript 37 | *.tsbuildinfo 38 | 39 | node_modules 40 | .env 41 | coverage 42 | coverage.json 43 | typechain 44 | 45 | #Hardhat files 46 | cache 47 | artifacts 48 | 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Create a Web3 Forum with Polygon 2 | 3 | ### Tutorial Details 4 | 5 | Today we're going to build together a fullstack decentralized forum that can be used in any dapp. 6 | 7 | See the full tutorial here -> https://www.pointer.gg/tutorials/create-a-web3-forum-with-polygon/1cb8f005-08f4-48a2-9d82-cd963e16f7f1 8 | 9 | ### Overview 10 | 11 | To us at Pointer, one of the most interesting aspects of web3 is that it facilitates developers to build and share composable behaviors around databases. 12 | 13 | Anyone can dig up a smart contract on GitHub, deploy it, and have their own microservice running in seconds. 14 | 15 | Not only that, but the stable API exposed by a smart contract is a blank canvas on top of which any frontend developer in the community can build and share components. 16 | 17 | In that spirit, today we're going to build a deployable smart contract and an accompanying React component to add comment threads to your dapps. 18 | 19 | In theory this could be open sourced and published as an npm package to allow any developer to quickly add a forum to their dapp. 20 | 21 | Here's what we're going to build: 22 | 23 | https://user-images.githubusercontent.com/5760059/154543585-74136a91-bac1-4935-8790-c943b71f43df.mov 24 | 25 | Visit the tutorial to get started https://www.pointer.gg/tutorials/create-a-web3-forum-with-polygon/1cb8f005-08f4-48a2-9d82-cd963e16f7f1 26 | -------------------------------------------------------------------------------- /components/AuthButton.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Button, ButtonProps } from "@chakra-ui/react"; 3 | import { useAccount, useConnect } from "wagmi"; 4 | import toast from "react-hot-toast"; 5 | 6 | interface AuthButtonProps extends ButtonProps {} 7 | 8 | const AuthButton: React.FunctionComponent = (props) => { 9 | const [connectQuery, connect] = useConnect(); 10 | const [accountQuery] = useAccount(); 11 | 12 | React.useEffect(() => { 13 | if (connectQuery.error?.name === "ConnectorNotFoundError") { 14 | toast.error("Metamask extension required to sign in"); 15 | } 16 | }, [connectQuery.error]); 17 | 18 | // If not authenticated, require sign-in 19 | if (!accountQuery.data?.address) { 20 | return ( 21 | 29 | ); 30 | } 31 | 32 | // If authenticated, show button as usual 33 | return ; 34 | }; 35 | 36 | export default AuthButton; 37 | -------------------------------------------------------------------------------- /components/Comment.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { Text, Heading, HStack, Stack } from "@chakra-ui/react"; 3 | import TimeAgo from "react-timeago"; 4 | import Avatar from "@davatar/react"; 5 | import Username from "./Username"; 6 | import { Comment } from "../hooks/useCommentsContract"; 7 | 8 | interface CommentProps { 9 | comment: Comment; 10 | } 11 | 12 | const Comment: React.FunctionComponent = ({ comment }) => { 13 | return ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | {comment.message} 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Comment; 32 | -------------------------------------------------------------------------------- /components/CommentEditor.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react"; 2 | import { HStack, Stack, Textarea } from "@chakra-ui/react"; 3 | import { constants } from "ethers"; 4 | import Avatar from "@davatar/react"; 5 | import AuthButton from "./AuthButton"; 6 | import { useAccount } from "wagmi"; 7 | import useAddComment from "../hooks/useAddComment"; 8 | 9 | interface CommentEditorProps { 10 | topic: string; 11 | } 12 | 13 | const CommentEditor: React.FunctionComponent = ({ 14 | topic, 15 | }) => { 16 | const [message, setMessage] = React.useState(""); 17 | const mutation = useAddComment(); 18 | const [accountQuery] = useAccount(); 19 | 20 | return ( 21 | 22 | 23 | 27 |