# Alchemy第十周教程

By [xiaoge](https://paragraph.com/@xiaoge) · 2022-11-12

---

准备开始
----

> ### 需要注意：每次修改或者覆盖代码需要按ctrl+s保存代码）

进入

[https://replit.com/](https://replit.com/)

登入你的账号看下图设置

![](https://storage.googleapis.com/papyrus_images/2ed7867ae0ab55b1e9be03af69329259761a58bb1c6b83645f636c604eff3b51.png)

### 创建成功以后在shell里输入如下代码，其中week10为你的项目名字，可以随意取

    npx create-next-app road-to-lens
    

![](https://storage.googleapis.com/papyrus_images/e7ddcfa7ceb2537e8a2193cadd556f02bbefa8fc2db0144e77e3b58b65fcae21.png)

### 输入成功以后，提示 'Ok to processed'，输入y，等待下载项目所需的安装包并创建项目。

![](https://storage.googleapis.com/papyrus_images/757754a77bdca7ae73366b61f3d9fd1cb2f2de29bdbd9c129dd77b03c3f0e00e.png)

> 提示这个就可以下一步了

![](https://storage.googleapis.com/papyrus_images/8963149311708941c474da192fc2d7f1fb3e45420aafdc87346f5d2ebe27b65c.png)

### 然后我们在Shell里输入下方代码切换到项目所在路径下。

    cd road-to-lens 
    

![](https://storage.googleapis.com/papyrus_images/5c7c4811ecc848f2466a616829809cc65e99ca9183768f0684b5374588904ec3.png)

### 在shell输入下方代码,安装graphql（看好路径）

    npm install @apollo/client graphql
    

![](https://storage.googleapis.com/papyrus_images/70ec1a04a6da45448c55f5333d93db58c2e6dc1ad99a74a5efca98134df573c8.png)

有下方提示后即安装成功

![](https://storage.googleapis.com/papyrus_images/50a611a1a45b07ed3dfe88a6cbde8aff39f8ae26efcb40e3b70729e3a10591d6.png)

### 在shell输入下方代码运行项目验证

    npm run dev
    

![](https://storage.googleapis.com/papyrus_images/5357a778a57093ff78b09590d58b1f8acb354f8f711fbf090bef9f3e05f0d88d.png)

有下图右上角的显示后就是验证成功接下来下一步

![](https://storage.googleapis.com/papyrus_images/faa23f50825004e4ce53ae7ccd2bfbfbbed23865de29efbdbb6c79429778f033.png)

### 在 index.js 页面上使用 Lens 推荐的配置文件尝试 Apollo GraphQL

新建apollo-client.js

我们在当前项目新建一个apollo-client.js

![](https://storage.googleapis.com/papyrus_images/9d0522f847c70c5f401a509bb304955227cba59eafc1b151e0fe3184969b04f9.png)

创建完文件后点击打开

![](https://storage.googleapis.com/papyrus_images/ab186ffa52fd27b1af711c4603be5f96ed1d54cb740cff2b80916827e8489a47.png)

然后输入下方代码

    // ./apollo-client.js
    import { ApolloClient, InMemoryCache } from "@apollo/client";
    
    const client = new ApolloClient({
        uri: "https://api.lens.dev",
        cache: new InMemoryCache(),
    });
    
    export default client;
    

修改我们的/pages/\_app.js

先找到文件打开

![](https://storage.googleapis.com/papyrus_images/1a5f48636b275cb834b65022534a78d31d3e9bd7337071a62c1bacbeb22a27c9.png)

然后输入下方代码：

    // pages/_app.js
    import '../styles/globals.css'
    import { ApolloProvider } from "@apollo/client";
    import client from "../apollo-client";
    
    function MyApp({ Component, pageProps }) {
      return (
        <ApolloProvider client={client}>
          <Component {...pageProps} />
        </ApolloProvider>
      );
    }
    
    export default MyApp
    

### 修改/pages/index.js

先找到文件打开

![](https://storage.googleapis.com/papyrus_images/c474bffd3b74c881cdb82c8829d40a4944cf7f9bf7b1a0dd9d2f5ddf690456fe.png)

输入下方代码：

    import { useQuery, gql } from "@apollo/client";
    import recommendedProfilesQuery from '../queries/recommendedProfilesQuery.js';
    import Profile from '../components/Profile.js';
    
    export default function Home() {
      const {loading, error, data} = useQuery(recommendedProfilesQuery);
     
      if (loading) return 'Loading..';
      if (error) return `Error! ${error.message}`;
    
      return (
        <div>
          {data.recommendedProfiles?.map((profile, index) => {
            console.log(`Profile ${index}:`, profile);
            return <Profile key={profile.id} profile={profile} displayFullProfile={false} />;
          })}
        </div>
     )
    }
    

### 新建查询js

    mkdir queries
    touch queries/recommendedProfilesQuery.js
    

先点加号新建一个Shell.

![](https://storage.googleapis.com/papyrus_images/349e3de940843d6f487c56ed1c9941b6f8ef809cfa0ce3bda6f44a13145446a1.png)

![](https://storage.googleapis.com/papyrus_images/1acdb7bb672762a926eeae655d0e5e69d938f279f5f38bb930028758bb18c942.png)

先定位目录输入下方代码：

    cd road-to-lens 
    

![](https://storage.googleapis.com/papyrus_images/1b7e9dce5925b33f7601c3bdd9a408f2c7788b4e8dfa5ba142e8d44fdb133933.png)

然后输入下方命令一共两条,会创建一个新的文件夹和文件（如果使用VScode第二条 touch代码需要在CMD使用）

    mkdir queries
    

    touch queries/recommendedProfilesQuery.js
    

![](https://storage.googleapis.com/papyrus_images/94fba46f24a61e56626f975a6b7aab1c2127eca6a0d3140db1ca22f050193add.png)

将下面的代码写入recommendedProfilesQuery.js

    // queries/recommendedProfilesQuery.js
    
    import {gql} from '@apollo/client';
    
    export default gql`
      query RecommendedProfiles {
        recommendedProfiles {
              id
            name
            bio
            attributes {
              displayType
              traitType
              key
              value
            }
              followNftAddress
            metadata
            isDefault
            picture {
              ... on NftImage {
               contractAddress
               tokenId
               uri
               verified
              }
              ... on MediaSet {
                original {
                  url
                  mimeType
                }
              }
              __typename
            }
            handle
            coverPicture {
              ... on NftImage {
                contractAddress
                tokenId
                uri
                verified
              }
              ... on MediaSet {
                original {
                  url
                  mimeType
                }
              }
              __typename
            }
            ownedBy
            dispatcher {
              address
              canUseRelay
            }
            stats {
              totalFollowers
              totalFollowing
              totalPosts
              totalComments
              totalMirrors
              totalPublications
              totalCollects
            }
            followModule {
              ... on FeeFollowModuleSettings {
                type
                amount {
                  asset {
                    symbol
                    name
                    decimals
                    address
                  }
                  value
                }
                recipient
              }
              ... on ProfileFollowModuleSettings {
              type
              }
              ... on RevertFollowModuleSettings {
              type
              }
            }
        }
      }
    `;
    

### 新建组件js

和上一步一样（不需要新建Shell）直接输入下方代码（如果使用VScode第二条 touch代码需要在CMD使用）

    mkdir components
    

    touch components/Profile.js
    

同样创建了一个文件夹和一个文件

![](https://storage.googleapis.com/papyrus_images/0dde5b3819bd90d947a0de84c4a693a6695684c9e5cadb572d8b882141339b0c.png)

将下面代码写入Profile.js

    // components/Profile.js
    import Link from "next/link";
    export default function Profile(props) {
      const profile = props.profile;
    
      // When displayFullProfile is true, we show more info.
      const displayFullProfile = props.displayFullProfile;
      return (
        <div className="p-8">
          <Link href={`/profile/${profile.id}`}>
            <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">
              <div className="md:flex">
                <div className="md:shrink-0">
                  {profile.picture ? (
                    <img
                      src={
                       profile.picture.original
                         ? profile.picture.original.url
                         : profile.picture.uri
                      }
                      className="h-48 w-full object-cover md:h-full md:w-48"
                    />
                  ) : (
                    <div
                      style={{
                        backgrondColor: "gray",
                      }}
                      className="h-48 w-full object-cover md:h-full md:w-48"
                    />
                  )}
                </div>
                <div className="p-8">
                  <div className="uppercase tracking-wide text-sm text-indigo-500 font-semibold">
                    {profile.handle}
                    {displayFullProfile && 
                      " (" + profile.name + ")"}
                  </div>
                  <div className="block mt-1 text-sm leading-tight font-medium text-black hover:underline">
                    {profile.bio}
                  </div>
                  <div className="mt-2 text-sm text-slate-900">{profile.ownedBy}</div>
                  <p className="mt-2 text-xs text-slate-500">
                    following: {profile.stats.totalFollowing} followers:{" "}
                    {profile.stats.totalFollowers}
                  </p>
                </div>
               </div>
              </div>
            </Link>
          </div>
        );
    }
    

### 再次运行下方代码验证项目出现选择直接按回车

    npm run dev
    

![](https://storage.googleapis.com/papyrus_images/204998ab1df9cd9b225c173950a44dc7ef2b6e49712300683017d17ee13fe0bb.png)

验证成功后右上角会出现一张图片随机的

![](https://storage.googleapis.com/papyrus_images/802edb1e55ede976c9fc0411697361052db074b264dc3116e08d13a544e6cd0b.png)

> 我们将项目运行起来出现下面的结果说明你已经离成功不远了，但是现在页面的样式太丑了，让我们来优化下。

优化页面样式
------

### 输入下方命令记得先创建一个新的Shell窗口，然后定位路径

路径代码：

    cd road-to-lens 
    

安装 Tailwind

    npm install -D tailwindcss postcss autoprefixer
    

当你执行完下面这个命令，系统会给你生成一个tailwind.config.js

    npx tailwindcss init -p
    

出现选择项直接回车

![](https://storage.googleapis.com/papyrus_images/3ce71bcf2e73b7961a1d63b59d820161a329f7fde3d3ebf1fd333fdd4cc78052.png)

出现下方提示后输入第二个代码，同样出现选项直接回车

![](https://storage.googleapis.com/papyrus_images/cc534333b9e6f978151231fc220e456ad146c96dc6e06c633fbe121c3caeafb7.png)

会生成一个文件

![](https://storage.googleapis.com/papyrus_images/97db151c87861d40a7b6d3d13dddeb2ee4e5c8a9134421a3f7187baaecc11786.png)

在生成的tailwind.config.js中写入以下代码：

    // tailwind.config.js
    
    module.exports = {
      content: [
        "./pages/**/*.{js,ts,jsx,tsx}",
        "./components/**/*.{js,ts,jsx,tsx}",
      ],
      theme: {
        extend: {},
      },
      plugins: [],
    }
    

### 接着我们找到styles/globals.css文件

![](https://storage.googleapis.com/papyrus_images/49bfe53d915e09b8cce468609fb806bd3d4b238bb11c8c08cfc9084d09ba5d1e.png)

用以下内容替换文件内容。

    html,
    body {
    padding: 0;
    margin: 0;
    font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
    Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
    }
    
    a {
      color: inherit;
      text-decoration: none;
    }
    
    * {
      box-sizing: border-box;
    }
    
    @media (prefers-color-scheme: dark) {
      html {
        color-scheme: dark;
      }
      body {
        color: white;
    background: black;
    }
    }
    
    /* ./styles/globals.css */
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    

再次验证一下大概会出现这种界面

验证代码：

    npm run dev
    

![](https://storage.googleapis.com/papyrus_images/6072c3c96bc853753809bdca50adc97cd056b20043ea833df85cdcbaa6e2d148.png)

### 然后我们再pages下面新建一个profile文件夹，然后在下面新建一个\[id\].js文件，这个页面用来显示用户的详情页

![](https://storage.googleapis.com/papyrus_images/cd3f98a91e7b376048936688791675f7992a0b06e98fcba1980d0dd3ad7c5307.png)

然后打开文件输入以下代码：

    import { useQuery, useMutation } from "@apollo/client";
    import { useRouter } from "next/router";
    import fetchProfileQuery from "../../queries/fetchProfileQuery.js";
    import Profile from "../../components/Profile.js";
    import Post from "../../components/Post.js";
    
    export default function ProfilePage() {
      const router = useRouter();
      const { id } = router.query;
    
      console.log("fetching profile for", id);
      const { loading, error, data } = useQuery(fetchProfileQuery, {
        variables: {
          request: { profileId: id },
          publicationsRequest: {
            profileId: id,
            publicationTypes: ["POST"],
          },
        },
      });
    
      if (loading) return "Loading..";
      if (error) return `Error! ${error.message}`;
    
      return (
        <div className="flex flex-col p-8 items-center">
          <Profile profile={data.profile} displayFullProfile={true} />
          {data.publications.items.map((post, idx) => {
            return <Post key={idx} post={post}/>;
          })}
        </div>
      );
    }
    

### 然后我们在queries文件夹下面新增文件fetchProfileQuery.js，这个用来获取用户详情数据

![](https://storage.googleapis.com/papyrus_images/fbc498e2e89354b98bcd80ea68bb2dc297d2fbcdb28af8378445322444b0d64e.png)

打开文件后输入以下代码：

    import { gql } from "@apollo/client";
    
    export default gql`
      query (
        $request: SingleProfileQueryRequest!
        $publicationsRequest: PublicationsQueryRequest!
      ) {
        publications( request: $publicationsRequest) {
          items {
            __typename
            ... on Post {
              ...PostFields
            }
            ... on Comment {
              ...CommentFields
            }
            ... on Mirror {
              ...MirrorFields
            }
          }
          pageInfo {
            prev
            next
            totalCount
          }
        }
        profile(request: $request) {
          id
          name
          bio
          attributes {
            displayType
            traitType
            key
            value
          }
          followNftAddress
          metadata
          isDefault
          picture {
            ... on NftImage {
              contractAddress
              tokenId
              uri
              verified
            }
            ... on MediaSet {
              original {
                url
                mimeType
              }
            }
            __typename
          }
          handle
          coverPicture {
            ... on NftImage {
              contractAddress
              tokenId
              uri
              verified
            }
            ... on MediaSet {
              original {
                url
                mimeType
              }
            }
            __typename
          }
          ownedBy
          dispatcher {
            address
            canUseRelay
          }
          stats {
            totalFollowers
            totalFollowing
            totalPosts
            totalComments
            totalMirrors
            totalPublications
            totalCollects
          }
          followModule {
            ... on FeeFollowModuleSettings {
              type
              amount {
                asset {
                  symbol
                  name
                  decimals
                  address
                }
                value
              }
              recipient
            }
            ... on ProfileFollowModuleSettings {
              type
            }
            ... on RevertFollowModuleSettings {
              type
            }
          }
        }
      }
    
      fragment MediaFields on Media {
        url
        mimeType
      }
    
      fragment ProfileFields on Profile {
        id
        name
        bio
        attributes {
          displayType
          traitType
          key
          value
        }
        isFollowedByMe
        isFollowing(who: null)
        followNftAddress
        metadata
        isDefault
        handle
        picture {
          ... on NftImage {
            contractAddress
            tokenId
            uri
            verified
          }
          ... on MediaSet {
            original {
              ...MediaFields
            }
          }
        }
        coverPicture {
          ... on NftImage {
            contractAddress
            tokenId
            uri
            verified
          }
          ... on MediaSet {
            original {
              ...MediaFields
            }
          }
        }
        ownedBy
        dispatcher {
          address
        }
        stats {
          totalFollowers
          totalFollowing
          totalPosts
          totalComments
          totalMirrors
          totalPublications
          totalCollects
        }
        followModule {
          ... on FeeFollowModuleSettings {
            type
            amount {
              asset {
                name
                symbol
                decimals
                address
              }
              value
            }
            recipient
          }
          ... on ProfileFollowModuleSettings {
            type
          }
          ... on RevertFollowModuleSettings {
            type
          }
        }
      }
    
      fragment PublicationStatsFields on PublicationStats {
        totalAmountOfMirrors
        totalAmountOfCollects
        totalAmountOfComments
      }
    
      fragment MetadataOutputFields on MetadataOutput {
        name
        description
        content
        media {
          original {
            ...MediaFields
          }
        }
        attributes {
          displayType
          traitType
          value
        }
      }
    
      fragment Erc20Fields on Erc20 {
        name
        symbol
        decimals
        address
      }
    
      fragment CollectModuleFields on CollectModule {
        __typename
        ... on FreeCollectModuleSettings {
          type
          followerOnly
          contractAddress
        }
        ... on FeeCollectModuleSettings {
          type
          amount {
            asset {
              ...Erc20Fields
            }
            value
          }
          recipient
          referralFee
        }
        ... on LimitedFeeCollectModuleSettings {
          type
          collectLimit
          amount {
            asset {
              ...Erc20Fields
            }
            value
          }
          recipient
          referralFee
        }
        ... on LimitedTimedFeeCollectModuleSettings {
          type
          collectLimit
          amount {
            asset {
              ...Erc20Fields
            }
            value
          }
          recipient
          referralFee
          endTimestamp
        }
        ... on RevertCollectModuleSettings {
          type
        }
        ... on TimedFeeCollectModuleSettings {
          type
          amount {
            asset {
              ...Erc20Fields
            }
            value
          }
          recipient
          referralFee
          endTimestamp
        }
      }
    
      fragment PostFields on Post {
        id
        profile {
          ...ProfileFields
        }
        stats {
          ...PublicationStatsFields
        }
        metadata {
          ...MetadataOutputFields
        }
        createdAt
        collectModule {
          ...CollectModuleFields
        }
        referenceModule {
          ... on FollowOnlyReferenceModuleSettings {
            type
          }
        }
        appId
        hidden
        mirrors(by: null)
        hasCollectedByMe
      }
    
      fragment MirrorBaseFields on Mirror {
        id
        profile {
          ...ProfileFields
        }
        stats {
          ...PublicationStatsFields
        }
        metadata {
          ...MetadataOutputFields
        }
        createdAt
        collectModule {
          ...CollectModuleFields
        }
        referenceModule {
          ... on FollowOnlyReferenceModuleSettings {
            type
          }
        }
        appId
        hidden
        hasCollectedByMe
      }
    
      fragment MirrorFields on Mirror {
        ...MirrorBaseFields
        mirrorOf {
          ... on Post {
            ...PostFields
          }
          ... on Comment {
            ...CommentFields
          }
        }
      }
    
      fragment CommentBaseFields on Comment {
        id
        profile {
          ...ProfileFields
        }
        stats {
          ...PublicationStatsFields
        }
        metadata {
          ...MetadataOutputFields
        }
        createdAt
        collectModule {
          ...CollectModuleFields
        }
        referenceModule {
          ... on FollowOnlyReferenceModuleSettings {
            type
          }
        }
        appId
        hidden
        mirrors(by: null)
        hasCollectedByMe
      }
    
      fragment CommentFields on Comment {
        ...CommentBaseFields
        mainPost {
          ... on Post {
            ...PostFields
          }
          ... on Mirror {
            ...MirrorBaseFields
            mirrorOf {
              ... on Post {
                ...PostFields
              }
              ... on Comment {
                ...CommentMirrorOfFields
              }
            }
          }
        }
      }
    
      fragment CommentMirrorOfFields on Comment {
        ...CommentBaseFields
        mainPost {
          ... on Post {
            ...PostFields
          }
          ... on Mirror {
            ...MirrorBaseFields
          }
        }
      }
    `;
    

### 然后我们在components下面新建一个Post.js

![](https://storage.googleapis.com/papyrus_images/0cc4fa94ad596380dcf941296b75faa821c21de0ff596245bc38137c0f339ac1.png)

打开文件后输入下方代码

    // components/Post.js
    
    export default function Post(props) {
      const post = props.post; 
             
      return (    
        <div className="p-8">
           <div className="max-w-md mx-auto bg-white rounded-xl shadow-md overflow-hidden md:max-w-2xl">    
              <div className="md:flex">   
                 <div className="p-8"> 
                    <p className="mt-2 text-xs text-slate-500 whitespace-pre-line">    
                      {post.metadata.content}    
                    </p>
                  </div>
                </div>
              </div>
            </div>
         );
    }
        
    

验证任务是否完成
--------

在浏览器打开下方框选的链接

![](https://storage.googleapis.com/papyrus_images/d07f425d9a2259bb165c20d8e36822c682189d5e7ea42c31e3693864048f948e.png)

打开后如下显示.（图片加载不出来是正常的）

![](https://storage.googleapis.com/papyrus_images/5e0f25080a2a03b597cfccb75f6b7e25bbd4855e5bf461a7987fa392abbe90b8.png)

然后随便点一个人的资料进入. 类似下方显示就可以了

![](https://storage.googleapis.com/papyrus_images/bf55c0a6ad857179ef65a5a3920b69057edc7476431518aad60a470372085f71.png)

发布
--

![](https://storage.googleapis.com/papyrus_images/65b8f79cf5cbff4a175b5739004409675be95d1ab18fd1bfd9416f0d8914bc79.png)

![](https://storage.googleapis.com/papyrus_images/a1edcf11982adba709ff6f0fd42b139958c3e38d8aa02e710874de72c48c9c9d.png)

![](https://storage.googleapis.com/papyrus_images/7123c53ee400f77f44bdb38206ff58ae70e2c343b401b4ec5108df836c2ebeb3.png)

![](https://storage.googleapis.com/papyrus_images/154c641330b4187a42c997bcc1866583653eb472aeb23e124b76184ce5dbf8ff.png)

![](https://storage.googleapis.com/papyrus_images/1bd85256d51c80234ee8f8b35ab5c3015aaf8a2734d09cea9924dead80bd2046.png)

![](https://storage.googleapis.com/papyrus_images/03dc1c5296ade43434b198132f9fd6af3393aac91c1267e79aedba0d71a93b89.png)

---

*Originally published on [xiaoge](https://paragraph.com/@xiaoge/alchemy-10)*
