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

lesson-2_フロントエンドを完成させよう

フロントエンドで管理者画面を設けよう

コントラクトに管理者機能をつけたので、フロントエンドでも利用できるようにしましょう。

追加するコードのロジックは今までと同じです。

それではclientに移動してください。

📁 hooksディレクトリ

コントラクトに追加した機能を利用できるように、useMessengerContract.tsの中身を編集してきましょう。 基本的にコントラクトに追加したowner機能やnumOfPendingLimitschangeNumOfPendingLimitsを使用できるように変更しています。

ReturnUseMessengerContractの返り値の型を変更します。

// useMessengerContractの返すオブジェクトの型定義です。
type ReturnUseMessengerContract = {
processing: boolean;
ownMessages: Message[];
owner: string | undefined;
numOfPendingLimits: BigNumber | undefined;
sendMessage: (props: PropsSendMessage) => void;
acceptMessage: (index: BigNumber) => void;
denyMessage: (index: BigNumber) => void;
changeNumOfPendingLimits: (limits: BigNumber) => void;
};

useMessengerContractの冒頭に状態変数を追加します。

export const useMessengerContract = ({
currentAccount,
}: PropsUseMessengerContract): ReturnUseMessengerContract => {
const [processing, setProcessing] = useState<boolean>(false);
const [messengerContract, setMessengerContract] = useState<MessengerType>();
const [ownMessages, setOwnMessages] = useState<Message[]>([]);

// 以下の2行を追加
const [owner, setOwner] = useState<string>();
const [numOfPendingLimits, setNumOfPendingLimits] = useState<BigNumber>();

// ...
};

useMessengerContract内、関数の定義と実行を追加します。

export const useMessengerContract = ({
currentAccount,
}: PropsUseMessengerContract): ReturnUseMessengerContract => {
// ...

async function denyMessage(index: BigNumber) {
// ...
}

// 以下の関数を追加
async function getOwner() {
if (!messengerContract) return;
try {
console.log("call getter of owner");
const owner = await messengerContract.owner();
setOwner(owner.toLocaleLowerCase());
} catch (error) {
console.log(error);
}
}

// 以下の関数を追加
async function getNumOfPendingLimits() {
if (!messengerContract) return;
try {
console.log("call getter of numOfPendingLimits");
const limits = await messengerContract.numOfPendingLimits();
setNumOfPendingLimits(limits);
} catch (error) {
console.log(error);
}
}

// 以下の関数を追加
async function changeNumOfPendingLimits(limits: BigNumber) {
if (!messengerContract) return;
try {
console.log("call changeNumOfPendingLimits with [%d]", limits.toNumber());
const txn = await messengerContract.changeNumOfPendingLimits(limits, {
gasLimit: 300000,
});
console.log("Processing...", txn.hash);
setProcessing(true);
await txn.wait();
console.log("Done -- ", txn.hash);
setProcessing(false);
} catch (error) {
console.log(error);
}
}

useEffect(() => {
getMessengerContract();
getOwnMessages();

// 以下2行を追加
getOwner();
getNumOfPendingLimits();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentAccount, ethereum]);

// ...
};

useMessengerContract内、イベントリスナの追加します。

export const useMessengerContract = ({
currentAccount,
}: PropsUseMessengerContract): ReturnUseMessengerContract => {
// ...

useEffect(() => {
// NewMessageのイベントリスナ
const onNewMessage = () =>
// ...
{
// ...
};

// MessageConfirmedのイベントリスナ
const onMessageConfirmed = (receiver: string, index: BigNumber) => {
// ...
};

// 以下の関数を追加
// NumOfPendingLimitsChangedのイベントリスナ
const onNumOfPendingLimitsChanged = (limitsChanged: BigNumber) => {
console.log(
"NumOfPendingLimitsChanged limits:[%d]",
limitsChanged.toNumber()
);
setNumOfPendingLimits(limitsChanged);
};

/* イベントリスナーの登録をします */
if (messengerContract) {
messengerContract.on("NewMessage", onNewMessage);
messengerContract.on("MessageConfirmed", onMessageConfirmed);

// 追加
messengerContract.on(
"NumOfPendingLimitsChanged",
onNumOfPendingLimitsChanged
);
}

/* イベントリスナーの登録を解除します */
return () => {
if (messengerContract) {
messengerContract.off("NewMessage", onNewMessage);
messengerContract.off("MessageConfirmed", onMessageConfirmed);

// 追加
messengerContract.off(
"NumOfPendingLimitsChanged",
onNumOfPendingLimitsChanged
);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [messengerContract]);

// ...
};

useMessengerContract内、返り値の変更をします。

return {
processing,
ownMessages,
owner,
numOfPendingLimits,
sendMessage,
acceptMessage,
denyMessage,
changeNumOfPendingLimits,
};

📁 componentsディレクトリ

📁 formディレクトリ

components/formディレクトリ内、 その中にChangeOwnerValueForm.tsxという名前のファイルを作成してください。

ChangeOwnerValueForm.tsx内に以下のコードを記述してください。

import { BigNumber } from "ethers";
import { useState } from "react";

import styles from "./Form.module.css";

type Props = {
processing: boolean;
currentValue: BigNumber | undefined;
changeValue: (limits: BigNumber) => void;
};

export default function ChangeOwnerValueForm({
processing,
currentValue,
changeValue,
}: Props) {
const [limits, setLimits] = useState<string>("0");

return (
<div className={styles.container}>
<div className={styles.form}>
<div className={styles.title}>
Change number of pending messages limits !
</div>
{processing ? (
<p>processing...</p>
) : (
<p>current limits: {currentValue?.toNumber()}</p>
)}

<input
type="number"
name="limits_number"
placeholder="limits"
id="input_limits"
min={0}
className={styles.number}
onChange={(e) => setLimits(e.target.value)}
/>

<div className={styles.button}>
<button
onClick={() => {
changeValue(BigNumber.from(limits));
}}
>
change{" "}
</button>
</div>
</div>
</div>
);
}

ここでは同じフォルダ内のSendMessageForm.tsxと同じようなフォームコンポーネントを作成しています。 管理者ページで使用されます。

引数で受け取るcurrentValuechangeValueは、それぞれ管理者が変更する値の、現在の値と値を変更をする関数です。 今回、管理者が変更する値はメッセージの保留数を指します。

後で実際に表示する画面を見ると、このフォームの構成する部分がわかりやすいと思います。

📁 pagesディレクトリ

pagesディレクトリ内にOwnerPage.tsxという名前のファイルを作成し、以下のコードを記述してください。

import ChangeOwnerValueForm from "../components/form/ChangeOwnerValueForm";
import Layout from "../components/layout/Layout";
import RequireWallet from "../components/layout/RequireWallet";
import { useMessengerContract } from "../hooks/useMessengerContract";
import { useWallet } from "../hooks/useWallet";

export default function OwnerPage() {
const { currentAccount, connectWallet } = useWallet();
const { processing, owner, numOfPendingLimits, changeNumOfPendingLimits } =
useMessengerContract({
currentAccount: currentAccount,
});

return (
<Layout>
<RequireWallet
currentAccount={currentAccount}
connectWallet={connectWallet}
>
{owner === currentAccount ? (
<ChangeOwnerValueForm
processing={processing}
currentValue={numOfPendingLimits}
changeValue={changeNumOfPendingLimits}
/>
) : (
<div>Unauthorized</div>
)}
</RequireWallet>
</Layout>
);
}

管理者ページを構成します。

useMessengerContractから取得したnumOfPendingLimitschangeNumOfPendingLimitsを 先ほど作成したChangeOwnerValueFormに渡しています。

また、ownerが現在接続しているアカウントと違う場合はUnauthorizedというメッセージを表示します。

最後にpagesディレクトリ内のindex.tsxを以下のように編集しましょう。

import type { NextPage } from "next";
import Link from "next/link";

import Layout from "../components/layout/Layout";
import RequireWallet from "../components/layout/RequireWallet";
import { useMessengerContract } from "../hooks/useMessengerContract";
import { useWallet } from "../hooks/useWallet";
import styles from "../styles/Home.module.css";

const Home: NextPage = () => {
const { currentAccount, connectWallet } = useWallet();
const { owner } = useMessengerContract({
currentAccount: currentAccount,
});

return (
<Layout home>
<RequireWallet
currentAccount={currentAccount}
connectWallet={connectWallet}
>
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>Welcome to Messenger 📫</h1>
<div className={styles.card}>
<Link href="/message/SendMessagePage">
<h2>send &rarr;</h2>
</Link>
<p>send messages and avax to other accounts</p>
</div>

<div className={styles.card}>
<Link href="/message/ConfirmMessagePage">
<h2>check &rarr;</h2>
</Link>
<p>Check messages from other accounts</p>
</div>

{owner === currentAccount && (
<div className={styles.card}>
<Link href="/OwnerPage">
<h2>owner &rarr;</h2>
</Link>
<p>Owner page</p>
</div>
)}
</main>
</div>
</RequireWallet>
</Layout>
);
};

export default Home;

OwnerPage.tsxと同じようにownerとユーザアカウントを照合して、 ownerの場合は管理者画面であるOwnerPageへリンクを表示します。

🖥️ web アプリを立ち上げましょう

それではターミナル上で以下のコマンドを走らせ、webアプリを立ち上げてください。

yarn client dev

ブラウザで http://localhost:3000 へアクセスします。

管理者のアカウントで接続した場合、以下のように画面が出力されるはずです。 ownerリンクが増えています。

ownerリンクをクリックし、管理者ページで値を入力し、メッセージの保留数上限を変更してみましょう。

トランザクションが完了し、current limitsが変更したら ブラウザのコンソールから、Doneではじまる行の値をコピーして、AVASCAN testnetで履歴を確認してみましょう。 💁 コンソールを表示するには、ブラウザ上で右クリック -> 検証 -> コンソールを開きます。

🌔 参考リンク

こちらに本プロジェクトの完成形のレポジトリがあります。

期待通り動かない場合は参考にしてみてください。

🙋‍♂️ 質問する

ここまでの作業で何かわからないことがある場合は、Discordの#avalancheで質問をしてください。

ヘルプをするときのフローが円滑になるので、エラーレポートには下記の3点を記載してください ✨

1. 質問が関連しているセクション番号とレッスン番号
2. 何をしようとしていたか
3. エラー文をコピー&ペースト
4. エラー画面のスクリーンショット

おめでとうございます! セクション3が終了しました! #avalancheにあなたのAVASCANのリンクを貼り付けて、コミュニティで進捗を祝いましょう 🎉 webアプリが完成したら、次のレッスンに進みましょう 🎉