unlock-protocol / hackathon-demo

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Install deps:

yarn add wagmi@0.12.0 ethers@5 @unlock-protocol/paywall @unlock-protocol/networks @unlock-protocol/contracts

Update App.js to include wagmi config

This is not Unlock specific:

Add a constant to constants.ts for the network we will use.

export const NETWORK = 5 // also called chain id
import { WagmiConfig, createClient} from 'wagmi'
import { AppProps } from 'next/app'
import '../styles/index.css'
import { ethers } from 'ethers'
import { NETWORK } from '../lib/constants'

// Create a WAGMI client
const wagmiClient = createClient({
  // Unlock provides some RPC providers but you can use your own!
  provider: new ethers.providers.JsonRpcProvider(`https://rpc.unlock-protocol.com/${NETWORK}`, NETWORK),
})

export default function MyApp({ Component, pageProps }: AppProps) {
  return <WagmiConfig client={wagmiClient}>
    <Component {...pageProps} />
  </WagmiConfig>
}

Add a TokenGate component:

This component is in fact pretty simple:

  • It wraps any component that needs to be token-gated
  • It checks the "status" of the user (has a membership or not), if the user is connected
  • If the user is not connected, it renders a subcomponent to prompt the user to connect
  • If the user is connected and they do NOT have a membership NFT, it renders a Checkout sub-component that promps them to purchase a membership!
  • If the user is connected AND they have a membership, it renders he children!
import { useAccount, useConnect, useContractRead } from "wagmi"
import {InjectedConnector} from "wagmi/connectors/injected"
import { PublicLockV13 } from "@unlock-protocol/contracts"
import { LOCK, NETWORK } from "../lib/constants"
import { ethers } from "ethers"
import { Paywall } from "@unlock-protocol/paywall"
import networks from '@unlock-protocol/networks'


export const TokenGate = ({children}) => {
  const {isConnected, address} = useAccount()

  const {data: isMember, isError, isLoading} = useContractRead({
    address: LOCK,
    abi: PublicLockV13.abi,
    functionName: 'balanceOf',
    chainId: NETWORK,
    enabled: !!address,
    args: [address],
    watch: true,
    select: (data: ethers.BigNumber) => {
      return data.gt(0)
    }
  })

  if (isLoading) {
    return <div>Loading...</div>
  }
  
  if (isError) {
    return <div>There was an error checking your membership status. Please reload the page!</div>
  }

  // User not connected
  if (!isConnected)  {
    return <Connect />
  }

  // User does not have membership
  if (!isMember) {
    return <Checkout />
  }

  // All good: user is connected and they have a membership!
  return children
}

/**
 * Connect subcomponent!
 * @returns 
 */
const Connect = () => {
  const {connect} = useConnect({
    connector: new InjectedConnector()
  })
  return <section>
    <p className="mb-4">To view this post you need to be be a member!</p>
    <button onClick={() =>  connect()} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
        Sign-In
      </button>
    </section>
}

/**
 * Checkout subcomponent!
 * @returns 
 */
const Checkout = () => {
  const {connector} = useAccount()
  const checkout = () => {
    const paywall = new Paywall(networks)
    const provider = connector!.provider
    paywall.connect(provider)
    paywall.loadCheckoutModal({
      locks: {
        [LOCK]: {
          network: NETWORK,
        }
      },
      pessimistic: true,
    })
  }

  return  (
    <section>
      <p className="mb-4">You currently don't have a membership... </p>
      <button onClick={() =>  checkout()} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
        Purchase one now!
      </button>
    </section>
  )
}

Wrap the components that renders the content

We just replace post-body.tsx with this:

import { TokenGate } from './token-gate'
import markdownStyles from './markdown-styles.module.css'

type Props = {
  content: string
}

const PostBody = ({ content }: Props) => {
  return (
    <div className="max-w-2xl mx-auto">
      <TokenGate>
        <div
          className={markdownStyles['markdown']}
          dangerouslySetInnerHTML={{ __html: content }}
        />
      </TokenGate>
    </div>
  )
}

export default PostBody

That's it!

Please jump in Unlock's Discord if you have any question!

About


Languages

Language:TypeScript 92.7%Language:JavaScript 5.5%Language:CSS 1.8%