Giter VIP home page Giter VIP logo

lens-protocol-workshop's Introduction

Lens Protocol Workshop

In this workshop you'll learn how to use Next.js, TypeScript, Lens Protocol, and the Lens SDK to build out a social application.

The app we'll be building will have the following features:

  1. When the app loads, it will render a list of recommended users from the Lens API along with their profile picture and bio
  2. When we click on a user, we will navigate to a detail view where we will see all of their publications as well as more profile details
  3. The user profile view will also have the option for a user to sign in and follow another user.

By the end of this tutorial you'll have a good understanding of how to get started building on Lens with TypeScript and the Lens SDK.

Lens Dev Stack

Getting started

To get started, create a new Next.js application:

npx create-next-app lens-app

✔ Would you like to use TypeScript with this project? Yes
✔ Would you like to use ESLint with this project? Yes
✔ Would you like to use Tailwind CSS with this project?  Yes
✔ Would you like to use `src/` directory with this project? No
✔ Use App Router (recommended)? Yes
✔ Would you like to customize the default import alias? No

Next, change into the new directory and install the following dependencies:

npm install @lens-protocol/react-web@alpha @lens-protocol/wagmi@alpha ethers@legacy-v5 wagmi viem @web3modal/wagmi

Next, update the tsconfig.json and add the following to the compilerOptions configuration:

"noImplicitAny": false,

WalletConnect Setup

Go to https://cloud.walletconnect.com/app and create a project, then copy the Project ID.

Create a file named .env.local in the root directory, adding the following code:

NEXT_PUBLIC_WC_ID="<your-project-id>"

Web3Modal Provider

Create a file in the app directory named Web3ModalProvider.tsx and add the following code:

"use client"

import { createWeb3Modal, defaultWagmiConfig } from '@web3modal/wagmi/react'

import { WagmiConfig } from 'wagmi'
import { arbitrum, mainnet } from 'viem/chains'

const projectId = process.env.NEXT_PUBLIC_WC_ID || ''

const metadata = {
  name: 'Web3Modal',
  description: 'Web3Modal Example',
  url: 'https://web3modal.com',
  icons: ['https://avatars.githubusercontent.com/u/37784886']
}

const chains = [mainnet, arbitrum]
const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata })

createWeb3Modal({ wagmiConfig, projectId, chains })

export function Web3ModalProvider({ children }) {
  return <WagmiConfig config={wagmiConfig}>{children}</WagmiConfig>
}

Lens Provider

Next, create a file in the app directory named LensProvider.tsx and add the following code:

'use client'
import { LensConfig, production } from '@lens-protocol/react-web'
import { bindings as wagmiBindings } from '@lens-protocol/wagmi'
import { LensProvider as Provider } from '@lens-protocol/react-web'

const lensConfig: LensConfig = {
  bindings: wagmiBindings(),
  environment: production,
}

export function LensProvider({ children }) {
  return (
    <Provider config={lensConfig}>
      { children }
    </Provider>
  )
}

app/layout.tsx

Next, we want to configure our app to use the providers.

This is typically done at the entrypoint to the app, and only needs to be done once.

Update app/layout.tsx with the following code:

// app/layout.tsx
import './globals.css'
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import { LensProvider } from './LensProvider'
import { Web3ModalProvider } from './Web3ModalProvider'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <Web3ModalProvider>
          <LensProvider>
            {children}
          </LensProvider>
        </Web3ModalProvider>
      </body>
    </html>
  )
}

app/page.tsx

Next, let's query for profiles and render them in our app.

To do so, open app/page.tsx and add the following code:

// app/page.tsx
'use client'
import Image from 'next/image'
import { useExploreProfiles, ExploreProfilesOrderByType } from '@lens-protocol/react-web'
import Link from 'next/link'

export default function Home() {
  const { data } = useExploreProfiles({
    orderBy: ExploreProfilesOrderByType.MostFollowers
  })
  
  return (
    <div className='p-20'>
      <h1 className='text-5xl'>My Lens App</h1>
      {
        data?.map((profile, index) => (
          <Link
            href={`/profile/${profile.handle?.localName}.${profile.handle?.namespace}`}
            key={index}
          >
            <div className='my-14'>
              {
                profile.metadata?.picture?.__typename === 'ImageSet' ? (
                  <img
                    src={profile.metadata?.picture?.optimized?.uri || ''}
                    width="120"
                    height="120"
                    alt={profile.handle?.fullHandle || ''}
                  />
                ) : <div className="w-28 h-28 bg-slate-500" />
              }
              <h3 className="text-3xl my-4">{profile.handle?.localName}.{profile.handle?.namespace}</h3>
              <p className="text-xl">{profile.metadata?.bio}</p>
            </div>
          </Link>
        ))
      }
    </div>
  )
}

What's happening?

In useExploreProfiles, we are calling the Lens API to fetch a list of recommended profiles.

Once the profiles are returned, we map over them, rendering each profile with their profile details and a link to view the individual profile which we'll build out later.

Testing it out

To run the app, run the following command:

npm run dev

Profile View

In the above code, we've added a link to each profile that, when clicked, will navigate to /profile/profile.id. What we want to happen is that when a user navigates to that page, they are able to view more details about that profile.

We also want go give users a way to sign in and follow users.

This functionality does not yet exist, so let's create it.

In the app directory, create a new folder named profile.

In the profile directory create a new folder named [handle].

In the [handle] folder, create a new file named page.tsx.

In this file, add the following code:

// app/profile/[handle]/page.tsx
'use client'
import {
  useProfile, usePublications, Profile, LimitType, PublicationType
} from '@lens-protocol/react-web'

export default function Profile({ params: { handle }}) {
  const namespace = handle.split('.')[1]
  handle = handle.split('.')[0]
  let { data: profile, loading } = useProfile({
    forHandle: `${namespace}/${handle}`
  })
  if (loading) return <p className="p-14">Loading ...</p>

  return (
    <div>
      <div className="p-14">
        {
          profile?.metadata?.picture?.__typename === 'ImageSet' && (
            <img
              width="200"
              height="200"
              alt={profile.handle?.fullHandle}
              className='rounded-xl'
              src={profile.metadata.picture.optimized?.uri}
            />
          )
        }
        <h1 className="text-3xl my-3">{profile?.handle?.localName}.{profile?.handle?.namespace}</h1>
        <h3 className="text-xl mb-4">{profile?.metadata?.bio}</h3>
       { profile && <Publications profile={profile} />}
      </div>
    </div>
  )
}

function Publications({
  profile
}: {
  profile: Profile
}) {
  let { data: publications } = usePublications({
    where: {
      publicationTypes: [PublicationType.Post],
      from: [profile.id],
    },
    limit: LimitType.TwentyFive
  })

  return (
    <>
      {
        publications?.map((pub: any, index: number) => (
          <div key={index} className="py-4 bg-zinc-900 rounded mb-3 px-4">
            <p>{pub.metadata.content}</p>
            {
              pub.metadata?.asset?.image?.optimized?.uri && (
                <img
                  width="400"
                  height="400"
                  alt={profile.handle?.fullHandle}
                  className='rounded-xl mt-6 mb-2'
                  src={pub.metadata?.asset?.image?.optimized?.uri}
                />
              )
            }
          </div>
        ))
    }
    </>
  )
}

What's happening

useProfile allows you to get a user's profile details by passing in a Lens handle or profile ID

usePublications allows you to fetch a user's publications by passing in a profile

Testing it out

To run the app, run the following command:

npm run dev

Adding authentication and following a user

Next, let's add some additional functionality that will allow a user to sign and and then follow another user.

// app/profile/[handle]/page.tsx
'use client'
import {
  useProfile,
  usePublications,
  Profile,
  LimitType,
  PublicationType,
  useLogin,
  useProfiles,
  useFollow
} from '@lens-protocol/react-web'
import { useWeb3Modal } from '@web3modal/wagmi/react'
import { useAccount } from 'wagmi'

export default function Profile({ params: { handle }}) {
  const namespace = handle.split('.')[1]
  handle = handle.split('.')[0]
  let { data: profile, loading } = useProfile({
    forHandle: `${namespace}/${handle}`
  })
  const { open } = useWeb3Modal()
  const { address, isConnected } = useAccount()
  const { execute: login, data } = useLogin()
  const { execute: follow } = useFollow();
  const { data: ownedProfiles } = useProfiles({
    where: {
      ownedBy: [address || ''],
    },
  })
  
  if (!profile) return null

  if (loading) return <p className="p-14">Loading ...</p>
  return (
    <div>
      <div className="p-14">
        {
          profile?.metadata?.picture?.__typename === 'ImageSet' && (
            <img
              width="200"
              height="200"
              alt={profile.handle?.fullHandle}
              className='rounded-xl'
              src={profile.metadata.picture.optimized?.uri}
            />
          )
        }
        {
          !isConnected && (
            <button
              className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
              onClick={() => open()}>
                Connect Wallet
            </button>
          )
        }
        {
          !data && ownedProfiles && isConnected && (
            <button
              className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
              onClick={() => login({
                address: address || '',
                profileId: ownedProfiles[ownedProfiles.length - 1].id
              })}>
                Login with Lens
            </button>
          )
        }
        {
         data && profile.operations.canFollow !== 'NO' && (
            <button
            className='border border-zinc-600 rounded px-4 py-2 mt-4 mb-6'
            onClick={() => profile ? follow({ profile: profile }) : null }>
              Follow
            </button>
          )
        }
        <h1 className="text-3xl my-3">{profile?.handle?.localName}.{profile?.handle?.namespace}</h1>
        <h3 className="text-xl mb-4">{profile?.metadata?.bio}</h3>
       { profile && <Publications profile={profile} />}
      </div>
    </div>
  )
}

function Publications({
  profile
}: {
  profile: Profile
}) {
  let { data: publications } = usePublications({
    where: {
      publicationTypes: [PublicationType.Post],
      from: [profile.id],
    },
    limit: LimitType.TwentyFive
  })

  return (
    <>
      {
        publications?.map((pub: any, index: number) => (
          <div key={index} className="py-4 bg-zinc-900 rounded mb-3 px-4">
            <p>{pub.metadata.content}</p>
            {
              pub.metadata?.asset?.image?.optimized?.uri && (
                <img
                  width="400"
                  height="400"
                  alt={profile.handle?.fullHandle}
                  className='rounded-xl mt-6 mb-2'
                  src={pub.metadata?.asset?.image?.optimized?.uri}
                />
              )
            }
          </div>
        ))
    }
    </>
  )
}

When you run the app, you should now be able to sign in and follow another user.

Next Steps

Now that you've built your first basic application, it's time to explore more of the Lens API!

Consider diving into modules or learning about gasless transactions and the dispatcher.

Also consider adding the following features to your new app:

  • Searching for users
  • Creating a post

lens-protocol-workshop's People

Contributors

dabit3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

lens-protocol-workshop's Issues

Dependency Conflict: Unable to Resolve @lens-protocol/wagmi and wagmi Versions

I got an error while trying to install the dependencies. The error message indicates a dependency conflict between the @lens-protocol/wagmi package and the wagmi package.

The project requires wagmi version 0.12.13, while @lens-protocol/wagmi requires version 0.12.7.

A possible solution is to update the wagmi package to version 0.12.7, matching the requirement of @lens-protocol/wagmi

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.