# Alchemy第十周教程- 使用 Lens 协议创建去中心化 Twitter **Published by:** [Alice's Web3](https://paragraph.com/@horseverse/) **Published on:** 2022-09-25 **URL:** https://paragraph.com/@horseverse/alchemy-lens-twitter ## Content 在本课中,将学习:如何使用 Apollo GraphQL 客户端设置 Next.js 应用程序如何使用 Lens 协议 API 获取存储在 Polygon 区块链上的个人资料、帖子和其他数据MintKudos API 简介——以便您可以将您的 PoK 代币集成到您的 dapp 中!Lit 协议简介——如果您想加密某些帖子以仅显示给各个社区成员如何使用 Repl.it 部署你的去中心化社交媒体应用程序前端网站扩展此项目的多个挑战选项!话不多说了,我们开始今天的课程吧。1.设置依赖安装Apollo我们今天的课程需要在VScode中去执行,首先我们需要建立一个项目# 创建一个road-to-lens 这是注释 不要输入命令行 npx create-next-app road-to-lens # 安装graphql npm install @apollo/client graphql # 运行项目验证 npm run dev 当你出现这样的结果,恭喜你已经成功完成第一步了。2.在 index.js 页面上使用 Lens 推荐的配置文件尝试 Apollo GraphQL2.1新建apollo-client.js我们在当前项目新建一个apollo-client.js输入下面的内容:// ./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:// 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.jsimport { useQuery } 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> ) } 新建查询jsmkdir queries touch queries/recommendedProfilesQuery.js 将下面的代码写入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 } } } } `; 新建组件jsmkdir components touch components/Profile.js 将下面代码写入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 && " (" + 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> ); } 我们将项目运行起来出现下面的结果说明你已经离成功不远了,但是现在页面的样式太丑了,让我们来优化下。2.2优化页面样式# 安装 Tailwind npm install -D tailwindcss postcss autoprefixer # 当你执行完下面这个命令,系统会给你生成一个tailwind.config.js npx tailwindcss init -p 在生成的tailwind.config.js中写入一下内容:// tailwind.config.js module.exports = { content: [ "./pages/**/*.{js,ts,jsx,tsx}", "./components/**/*.{js,ts,jsx,tsx}", ], theme: { extend: {}, }, plugins: [], } 同时将我们的globals.css文件末尾加入下面的内容:/* ./styles/globals.css */ @tailwind base; @tailwind components; @tailwind utilities; 最终我们的结果展示如下:3. 创建个人资料页面首先我们创建个人资料文件夹:mkdir pages/profile road-to-lens % touch pages/profile/\[id\].js touch queries/fetchProfileQuery.js 在fetchProfileQuery.js中写入下面的代码:// queries/fetchProfileQuery.js import { gql } from '@apollo/client'; export default gql` query($request: SingleProfileQueryRequest!) { 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 } } } } `; 在id.js文件中写入下面的代码:// pages/profile/[id].js import { useQuery } from "@apollo/client"; import { useRouter } from "next/router"; import fetchProfileQuery from "../../queries/fetchProfileQuery.js"; import Profile from "../../components/Profile.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 } }, }); if (loading) return "Loading.."; if (error) return `Error! ${error.message}`; console.log("on profile page data: ", data); return <Profile profile={data.profile} displayFullProfile={true}/> } 当我们上面代码编写完毕后,可以输入下面的链接进行验证http://localhost:3000/profile/0x9752http://localhost:3000/profile/0x25c4如果到目前为止一切顺利的话,你已经完成了一半了。4.在个人资料页面上加载用户帖子将fetchProfileQuery.js覆盖为下面的: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 新建一个Post.js并且写入一下代码:// 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> ); } 将[id].js覆盖为下面的: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> ); } 验证结果 http://localhost:3000/profile/0x28a2,恭喜,你正在成为一个去中心化的社交媒体开发者。 ## Publication Information - [Alice's Web3](https://paragraph.com/@horseverse/): Publication homepage - [All Posts](https://paragraph.com/@horseverse/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@horseverse): Subscribe to updates