メインコンテンツまでスキップ

lesson-3_それぞれの画面を作成しよう

それぞれのページを作成しよう

ではいよいよそれぞれのページを作成してSNS dAppを完成させましょう!

まずは最初にでてくるページを描画するindex.tsxを編集しましょう。

[index.tsx]

こちらにはウォレットを接続する前の画面のUIが記述されています。

ボタンを押すとホーム画面に遷移するという関数が動きます。

ウォレットコネクトする前の画面のUIが記述されています。

import type { NextPage } from "next";
import Head from "next/head";
import Image from "next/image";
import { useRouter } from "next/router";

const Home: NextPage = () => {
const router = useRouter();
return (
<div className="flex justify-center items-center bg-gray-200 w-screen h-screen relative">
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>

<main className="items-center h-screen w-1/3 justify-start flex bg-[#0090CD] flex-col">
<div className="z-10 h-screen w-1/3 bg-white absolute top-0 left-0"></div>
<div className="z-10 h-screen w-1/3 bg-white absolute top-0 right-0"></div>
<div className="z-0 rounded-full h-[360px] w-[360px] bg-[#87D2EC] absolute -top-24 left-[340px]"></div>
<div className="z-0 rounded-full h-[360px] w-[360px] bg-[#87D2EC] absolute top-1/4 right-[300px]"></div>
<div className="z-0 rounded-full h-[360px] w-[360px] bg-[#87D2EC] absolute -bottom-24 left-[330px]"></div>
<h1 className="z-10 text-[#0009DC] mt-24" style={{ fontSize: 57 }}>
ASTAR SNS
</h1>
<div className="flex-row flex items-center mb-11 mt-7">
<Image
className="z-10 w-24 h-24"
src="/unchain_logo.png"
alt="unchain_logo"
width={30}
height={30}
/>
<Image
className="w-10 h-7"
src="/cross_mark_2_logo-removebg.png"
alt="cross_logo"
width={30}
height={30}
/>
<Image
className="z-10 w-24 h-24"
src="/Astar_logo.png"
alt="astar_logo"
width={30}
height={30}
/>
</div>
<div className="z-10 mt-16 mb-16 text-3xl items-center flex flex-col text-[#0009DC]">
<div>SHARE YOUR</div>
<div>WONDERFUL DAILY LIFE</div>
<div>ON ASTAR</div>
</div>
<button
className="z-10 text-3xl text-white items-center flex justify-center h-20 w-72 bg-[#003AD0] hover:bg-blue-700 py-2 px-4 rounded-full"
onClick={() => {
router.push("home");
}}
>
Connect Wallet
</button>

<Image
className="rotate-[17deg] h-14 w-16 top-16 left-[530px] absolute "
src="/cross_star_6_logo-removebg.png"
alt="star_logo"
width={40}
height={40}
/>
<Image
className="rotate-[40deg] h-10 w-10 top-8 right-1/3 absolute "
src="/cross_star_6_logo-removebg.png"
alt="star_logo"
width={35}
height={30}
/>
<Image
className="rotate-[35deg] h-15 w-16 bottom-16 left-1/3 absolute "
src="/cross_star_6_logo-removebg.png"
alt="star_logo"
width={40}
height={40}
/>
<Image
className="rotate-[35deg] h-24 w-24 fill-black bottom-80 left-1/3 absolute "
src="/cross_star_2_logo-removebg.png"
alt="astar_logo"
width={100}
height={200}
/>
<Image
className="rotate-[25deg] h-24 w-24 fill-black bottom-48 right-1/3 absolute "
src="/cross_star_2_logo-removebg.png"
alt="astar_logo"
width={100}
height={200}
/>
</main>
</div>
);
};

export default Home;

では次にホーム画面,プロフィール画面,メッセージ画面を作成していきましょう!

どの画面でもuseEffectにチェーン接続のための関数や投稿取得関数を渡すことで画面を開くとコントラクトに格納してある投稿が見られるようになっています!

[home.tsx]

ホーム画面です。

useEffect関数の中にある関数群の説明を軽くするとまずブロックチェーンとの接続とアカウントとの接続が行われます。

その後チェーンとの接続が確認されたら接続されているアカウントに紐づいているプロフィールと全体の投稿リストを取得します。

そしてプロフィールを作成する関数がフロントで実行されているかをチェックして、実行されていない場合はコントラクト内でプロフィールが作成されているのかを確認します。

作られていない場合はプロフィールの作成関数が実行され、それが行われたことがフロントに保存されます。

import { ApiPromise } from "@polkadot/api";
import type { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import React, { useEffect, useState } from "react";

import { PostButton } from "../components/atoms/postButton";
import BottomNavigation from "../components/bottomNavigation";
import Post from "../components/post";
import PostModal from "../components/postModal";
import TopBar from "../components/topBar";
import { connectToContract } from "../hooks/connect";
import { balanceOf, distributeReferLikes, transfer } from "../hooks/FT";
import type { PostType } from "../hooks/postFunction";
import { getGeneralPost } from "../hooks/postFunction";
import {
checkCreatedInfo,
createProfile,
getProfileForHome,
} from "../hooks/profileFunction";

export default function home() {
const [api, setApi] = useState<ApiPromise>();

const [isCreatedProfile, setIsCreatedProfile] = useState(true);
const [isCreatedFnRun, setIsCreatedFnRun] = useState(false);
const [showNewPostModal, setShowNewPostModal] = useState(false);
const [isSetup, setIsSetup] = useState(false);
const [isDistributed, setIsDistributed] = useState(false);

const [imgUrl, setImgUrl] = useState("");
const [accountList, setAccountList] = useState<InjectedAccountWithMeta[]>([]);
const [actingAccount, setActingAccount] = useState<InjectedAccountWithMeta>();
const [generalPostList, setGeneralPostList] = useState<PostType[]>([]);
const [balance, setBalance] = useState<string>("0");

useEffect(() => {
connectToContract({
api: api,
accountList: accountList,
actingAccount: actingAccount!,
isSetup: isSetup,
setApi: setApi,
setAccountList: setAccountList,
setActingAccount: setActingAccount!,
setIsSetup: setIsSetup,
});
if (!isSetup) return;
getProfileForHome({
api: api!,
userId: actingAccount?.address!,
setImgUrl: setImgUrl,
});
balanceOf({
api: api,
actingAccount: actingAccount!,
setBalance: setBalance,
});
getGeneralPost({ api: api!, setGeneralPostList: setGeneralPostList });
if (isDistributed) return;
distributeReferLikes({
api: api,
actingAccount: actingAccount!,
});
setIsDistributed(true);
if (isCreatedFnRun) return;
checkCreatedInfo({
api: api,
userId: actingAccount?.address!,
setIsCreatedProfile: setIsCreatedProfile,
});
if (isCreatedProfile) return;
createProfile({ api: api, actingAccount: actingAccount! });
setIsCreatedFnRun(true);
});

return (
<div className="flex justify-center items-center bg-gray-200 w-screen h-screen relative">
<main className="items-center justify-center h-screen w-1/3 flex bg-white flex-col">
<PostModal
isOpen={showNewPostModal}
afterOpenFn={setShowNewPostModal}
api={api!}
actingAccount={actingAccount!}
/>
<TopBar
idList={accountList}
imgUrl={imgUrl}
setActingAccount={setActingAccount}
balance={balance}
/>
<div className="flex-1 overflow-scroll">
{generalPostList.map((post) => (
<Post
name={post.name}
time={post.createdTime}
description={post.description}
num_of_likes={post.numOfLikes}
user_img_url={post.userImgUrl}
post_img_url={post.imgUrl}
userId={post.userId}
postId={post.postId}
actingAccount={actingAccount}
api={api}
/>
))}
</div>
<div className="w-full">
<BottomNavigation api={api} />
</div>
<PostButton setShowNewPostModal={setShowNewPostModal} />
</main>
</div>
);
}

[profile.tsx]

プロフィール画面です。useEffect内で動いている関数はホーム画面のものとほぼ同じで、全体の投稿リストか特定のアカウントの投稿リストを取得するかの違いです。

import { ApiPromise } from "@polkadot/api";
import type { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import React, { useEffect, useState } from "react";

import BottomNavigation from "../components/bottomNavigation";
import Post from "../components/post";
import ProfileSettingModal from "../components/profileSettingModal";
import ProfileSubTopBar from "../components/profileSubTopBar";
import TopBar from "../components/topBar";
import { connectToContract } from "../hooks/connect";
import { balanceOf } from "../hooks/FT";
import type { PostType } from "../hooks/postFunction";
import { getIndividualPost } from "../hooks/postFunction";
import {
checkCreatedInfo,
createProfile,
getFollowerList,
getFollowingList,
getProfileForProfile,
} from "../hooks/profileFunction";

export default function profile(props: any) {
const [imgUrl, setImgUrl] = useState("");
const [isCreatedProfile, setIsCreatedProfile] = useState(true);
const [isCreatedFnRun, setIsCreatedFnRun] = useState(false);
const [name, setName] = useState("");
const [individualPostList, setIndividualPostList] = useState<PostType[]>([]);

const [showSettingModal, setShowSettingModal] = useState(false);
const [isSetup, setIsSetup] = useState(false);
const [api, setApi] = useState<ApiPromise>();
const [accountList, setAccountList] = useState<InjectedAccountWithMeta[]>([]);
const [actingAccount, setActingAccount] = useState<InjectedAccountWithMeta>();
const [followingList, setFollowingList] = useState<Array<string>>([]);
const [followerList, setFollowerList] = useState<Array<string>>([]);
const [balance, setBalance] = useState<string>("0");

useEffect(() => {
connectToContract({
api: api,
accountList: accountList,
actingAccount: actingAccount!,
isSetup: isSetup,
setApi: setApi,
setAccountList: setAccountList,
setActingAccount: setActingAccount!,
setIsSetup: setIsSetup,
});
if (!isSetup) return;
getProfileForProfile({
api: api,
userId: actingAccount?.address,
setImgUrl: setImgUrl,
setName: setName,
});
getIndividualPost({
api: api,
actingAccount: actingAccount,
setIndividualPostList: setIndividualPostList,
});
getFollowingList({
api: api,
userId: actingAccount?.address,
setFollowingList: setFollowingList,
});
getFollowerList({
api: api,
userId: actingAccount?.address,
setFollowerList: setFollowerList,
});
balanceOf({
api: api,
actingAccount: actingAccount!,
setBalance: setBalance,
});
if (isCreatedFnRun) return;
checkCreatedInfo({
api: api,
userId: actingAccount?.address!,
setIsCreatedProfile: setIsCreatedProfile,
});
if (isCreatedProfile) return;
createProfile({ api: api, actingAccount: actingAccount! });
setIsCreatedFnRun(true);
});

return (
<div className="flex justify-center items-center bg-gray-200 w-screen h-screen relative">
<main className="items-center h-screen w-1/3 flex bg-white flex-col">
<ProfileSettingModal
isOpen={showSettingModal}
afterOpenFn={setShowSettingModal}
api={api}
userId={actingAccount?.address}
setImgUrl={setImgUrl}
setName={setName}
actingAccount={actingAccount}
/>
<TopBar
idList={accountList}
imgUrl={imgUrl}
setActingAccount={setActingAccount}
balance={balance}
/>
<ProfileSubTopBar
imgUrl={imgUrl}
name={name}
followingList={followingList}
followerList={followerList}
isOpenModal={setShowSettingModal}
setActingAccount={setActingAccount}
idList={accountList}
api={api!}
actingAccount={actingAccount!}
setIsCreatedFnRun={setIsCreatedFnRun}
/>
<div className="flex-1 overflow-scroll">
{individualPostList.map((post) => (
<Post
name={post.name}
time={post.createdTime}
description={post.description}
num_of_likes={post.numOfLikes}
user_img_url={imgUrl}
post_img_url={post.imgUrl}
/>
))}
</div>
<div className="w-full">
<BottomNavigation />
</div>
</main>
</div>
);
}

[message.tsx]

メッセージ画面です。

ここでもuseEffect関数内で動く関数はホーム画面のものとほとんど変わりません。メッセージ相手の情報を取得するかの違いです。

import { ApiPromise } from "@polkadot/api";
import type { InjectedAccountWithMeta } from "@polkadot/extension-inject/types";
import React, { useEffect, useState } from "react";

import BottomNavigation from "../components/bottomNavigation";
import MessageMember from "../components/message_member";
import MessageRoom from "../components/messageRoom";
import TopBar from "../components/topBar";
import { connectToContract } from "../hooks/connect";
import { balanceOf } from "../hooks/FT";
import { getLastMessage, getMessageList } from "../hooks/messageFunction";
import {
checkCreatedInfo,
createProfile,
getProfileForMessage,
getSimpleProfileForMessage,
} from "../hooks/profileFunction";
import type { ProfileType } from "../hooks/profileFunction";

export default function message() {
// variable related to contract
const [api, setApi] = useState<ApiPromise>();
const [accountList, setAccountList] = useState<InjectedAccountWithMeta[]>([]);
const [actingAccount, setActingAccount] = useState<InjectedAccountWithMeta>();

const [imgUrl, setImgUrl] = useState("");
const [isCreatedProfile, setIsCreatedProfile] = useState(true);
const [isCreatedFnRun, setIsCreatedFnRun] = useState(false);
const [friendList, setFriendList] = useState([]);
const [messageList, setMessageList] = useState([]);
const [showMessageModal, setShowMessageModal] = useState(false);
const [userName, setUserName] = useState("");
const [userImgUrl, setUserImgUrl] = useState("");
const [myImgUrl, setMyImgUrl] = useState("");
const [messageListId, setMessageListId] = useState<string>("");
const [messageMemberList, setMessageMemberList] = useState([]);
const [myUserId, setMyUserId] = useState("");
const [isSetup, setIsSetup] = useState(false);
const [profile, setProfile] = useState<ProfileType>();
const [balance, setBalance] = useState<string>("0");

useEffect(() => {
//connect to contract
connectToContract({
api: api,
accountList: accountList,
actingAccount: actingAccount!,
isSetup: isSetup,
setApi: setApi,
setAccountList: setAccountList,
setActingAccount: setActingAccount!,
setIsSetup: setIsSetup,
});
if (!isSetup) return;

// get profile
getProfileForMessage({
api: api,
userId: actingAccount?.address,
setImgUrl: setImgUrl,
setMyImgUrl: setMyImgUrl,
setFriendList: setFriendList,
setProfile: setProfile,
});
// create message member list UI
createMessageMemberList();

balanceOf({
api: api,
actingAccount: actingAccount!,
setBalance: setBalance,
});

// check if already created profile in frontend
if (isCreatedFnRun) return;

// check if already created profile in contract
checkCreatedInfo({
api: api,
userId: actingAccount?.address,
setIsCreatedProfile: setIsCreatedProfile,
});
if (isCreatedProfile) return;
// create profile
createProfile({ api: api, actingAccount: actingAccount! });
setIsCreatedFnRun(true);
});

// create message member list UI
const createMessageMemberList = async () => {
let memberList: Array<any> = new Array();
for (var i = 0; i < friendList.length; i++) {
let friendProfile = await getSimpleProfileForMessage({
api: api,
userId: friendList[i],
});
let idList = profile?.messageListIdList;
let lastMessage: string;
let messageList = await getMessageList({
api: api,
id: idList![i],
});
if (idList !== null) {
lastMessage = await getLastMessage({ api: api, id: idList![i] });
}
let memberListFactor = (
<MessageMember
name={friendProfile?.name}
img_url={friendProfile?.imgUrl}
last_message={lastMessage}
setShowMessageModal={setShowMessageModal}
setUserName={setUserName}
setUserImgUrl={setUserImgUrl}
setMyImgUrl={setMyImgUrl}
messageListId={idList[i]}
setMessageListId={setMessageListId}
setMessageList={setMessageList}
messageList={messageList}
getMessageList={getMessageList}
setMyUserId={setMyUserId}
myUserId={profile?.userId}
api={api}
/>
);
memberList.push(memberListFactor);
}
setMessageMemberList(memberList);
};

return !showMessageModal ? (
<div className="flex justify-center items-center bg-gray-200 w-screen h-screen relative">
<main className="items-center h-screen w-1/3 flex bg-white flex-col">
<TopBar
idList={accountList}
imgUrl={imgUrl}
setActingAccount={setActingAccount}
balance={balance}
/>
<div className="flex-1 overflow-scroll w-full mt-1">
{messageMemberList}
</div>
<div className="w-full">
<BottomNavigation />
</div>
</main>
</div>
) : (
<MessageRoom
setShowMessageModal={setShowMessageModal}
userName={userName}
userImgUrl={userImgUrl}
myImgUrl={myImgUrl}
myUserId={myUserId}
api={api!}
actingAccount={actingAccount!}
messageListId={messageListId!}
messageList={messageList!}
/>
);
}

これで全ての画面の実装は完了しました!

🙋‍♂️ 質問する

わからないことがあれば、Discordの#astarでsection・Lesson名とともに質問をしてください 👋


次のセクションでは完成したフロントエンドが実際のコントラクトと接続できるかテストしましょう! 🎉