大家好,我是帝哥(推特:@CoinmanLabs),不知不觉我们已经来到了课程的最后一周了,今天帝哥带大家一起来看看最后一周的任务。
最近有很多关于 Facebook 和 Twitter 等社交媒体巨头拥有多少控制权的讨论。
今年,Aave 团队的人们启动了一个名为Lens Protocol的项目。这是最近进入 web3 生态系统最令人兴奋的技术之一,因为它使用区块链技术将数据的权力和控制权交还给生成数据的用户。所以我想,为什么我们不一起探索呢?这将是一个合适的最后一课结合你到目前为止所学的一切🙂。
在本课中,将学习:
话不多说了,我们开始今天的课程吧。
我们今天的课程需要在VScode中去执行,首先我们需要建立一个项目,PS:如果想快速完成的直接去clone帝哥的仓库吧。
# 创建一个road-to-lens 这是注释 不要输入命令行
npx create-next-app road-to-lens
# 安装graphql
npm install @apollo/client graphql
# 运行项目验证
npm run dev
当你出现这样的结果,恭喜你已经成功完成第一步了。
我们在当前项目新建一个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.js
import { 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>
)
}
新建查询js
mkdir 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
}
}
}
}
`;
新建组件js
mkdir 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>
);
}
我们将项目运行起来出现下面的结果说明你已经离成功不远了,但是现在页面的样式太丑了,让我们来优化下。
# 安装 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;
最终我们的结果展示如下:
首先我们创建个人资料文件夹:
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}/>
}
当我们上面代码编写完毕后,可以输入下面的链接进行验证
如果到目前为止一切顺利的话,你已经完成了一半了。
将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,当你出现了下面的页面,恭喜,你正在成为一个去中心化的社交媒体开发者。
最后我们别忘了去填写表格哦。
我是懂币帝,用技术带你领略区块链魅力,第一时间获取行业最新资讯:
推特:@CoinmanLabs
微信:CoinmanLabs(社群客服微信进群)