Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement network switcher in header component #271

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 70 additions & 15 deletions pkgs/frontend/app/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,14 @@ import { Link, useLocation, useNavigate, useParams } from "@remix-run/react";
import axios from "axios";
import { useActiveWalletIdentity } from "hooks/useENS";
import { useTreeInfo } from "hooks/useHats";
import { currentChain } from "hooks/useViem";
import { useActiveWallet } from "hooks/useWallet";
import { useEffect, useMemo, useState } from "react";
import type { HatsDetailSchama } from "types/hats";
import { ipfs2https } from "utils/ipfs";
import { abbreviateAddress } from "utils/wallet";
import type { Chain } from "viem";
import { useSwitchNetwork } from "./SwitchNetwork";
import CommonButton from "./common/CommonButton";
import { UserIcon } from "./icon/UserIcon";
import { WorkspaceIcon } from "./icon/WorkspaceIcon";
Expand All @@ -28,6 +31,44 @@ enum HeaderType {
WorkspaceAndUserIcons = "WorkspaceAndUserIcons",
}

// ネットワーク切り替えコンポーネントを作成
const NetworkSwitcher = ({
switchToChain,
}: {
switchToChain: (chain: Chain) => Promise<boolean>;
}) => {
return (
<MenuItem value="network" display="block">
<Text fontWeight="bold" mb={2}>
Network
</Text>
{/* チェーン切り替えボタンを表示 */}
{/* {supportedChains.map((chain) => (
<CommonButton
key={chain.id}
onClick={() => switchToChain(chain)}
w="100%"
mb={1}
variant={chain.id === currentChain.id ? "solid" : "outline"}
>
{chain.name}
</CommonButton>
))} */}

{/* 現在のチェーンを表示 */}
<CommonButton
key={currentChain.id}
onClick={() => switchToChain(currentChain)}
w="100%"
mb={1}
variant={"solid"}
>
{currentChain.name}
</CommonButton>
</MenuItem>
);
};

export const Header = () => {
const [headerType, setHeaderType] = useState<HeaderType>(
HeaderType.NonHeader,
Expand Down Expand Up @@ -76,18 +117,26 @@ export const Header = () => {
return avatar ? ipfs2https(avatar) : undefined;
}, [identity]);

const handleLogout = () => {
if (isSmartWallet) {
logout();
} else {
if (wallets.find((w) => w.connectorType === "injected")) {
alert("ウォレット拡張機能から切断してください。");
// ログアウトハンドラー
const handleLogout = async () => {
try {
if (isSmartWallet) {
await logout();
} else {
Promise.all(wallets.map((wallet) => wallet.disconnect()));
if (wallets.find((w) => w.connectorType === "injected")) {
alert("ウォレット拡張機能から切断してください。");
return;
}
await Promise.all(wallets.map((wallet) => wallet.disconnect()));
navigate("/login");
}
} catch (error) {
console.error("ログアウトに失敗しました:", error);
}
};

const { switchToChain } = useSwitchNetwork();

return headerType !== HeaderType.NonHeader ? (
<Flex justifyContent="space-between" w="100%" py={3}>
<Box display="flex" height="48px" flex="1" alignItems="center">
Expand Down Expand Up @@ -128,20 +177,26 @@ export const Header = () => {
</Text>
<Text fontSize="xs">{abbreviateAddress(identity.address)}</Text>
</MenuItem>
<NetworkSwitcher switchToChain={switchToChain} />
<MenuItem value="logout" onClick={handleLogout}>
Logout
</MenuItem>
</MenuContent>
</MenuRoot>
) : (
<CommonButton
onClick={() => {
navigate("/login");
}}
w="auto"
>
Login
</CommonButton>
<MenuRoot closeOnSelect={false}>
<MenuTrigger asChild>
<CommonButton w="auto" my="auto">
{currentChain.name}
</CommonButton>
</MenuTrigger>
<MenuContent>
<NetworkSwitcher switchToChain={switchToChain} />
<MenuItem value="logout" onClick={handleLogout}>
Logout
</MenuItem>
</MenuContent>
</MenuRoot>
)}
</Flex>
) : (
Expand Down
57 changes: 46 additions & 11 deletions pkgs/frontend/app/components/SwitchNetwork.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,57 @@
import { currentChain } from "hooks/useViem";
import { useActiveWallet } from "hooks/useWallet";
import { useWallets } from "@privy-io/react-auth";
import { currentChain, getChainById, supportedChains } from "hooks/useViem";
import { type FC, useEffect } from "react";
import type { Chain } from "viem";

export const useSwitchNetwork = () => {
const { wallets } = useWallets();

const switchToChain = async (targetChain: Chain) => {
const connectedWallet = wallets[0];
if (!connectedWallet) {
throw new Error("ウォレットが接続されていません");
}

// チェーンがサポートされているか確認
if (!supportedChains.some((chain) => chain.id === targetChain.id)) {
throw new Error("サポートされていないチェーンです");
}

const currentChainId = Number(connectedWallet.chainId.split(":")[1]);

if (currentChainId !== targetChain.id) {
try {
await connectedWallet.switchChain(targetChain.id);
return true;
} catch (error) {
console.error("チェーンの切り替えに失敗しました:", error);
throw error;
}
}
return false;
};

return { switchToChain };
};

export const SwitchNetwork: FC = () => {
const { connectedWallet } = useActiveWallet();
const { wallets } = useWallets();
const { switchToChain } = useSwitchNetwork();

useEffect(() => {
const switchChain = async () => {
if (
connectedWallet &&
Number(connectedWallet.chainId) !== currentChain.id
) {
await connectedWallet.switchChain(currentChain.id);
const initializeChain = async () => {
// ウォレットが接続されているか確認
if (!wallets.length) return;

try {
await switchToChain(currentChain);
} catch (error) {
console.error("初期チェーンの設定に失敗しました:", error);
}
};

switchChain();
}, [connectedWallet]);
initializeChain();
}, [switchToChain, wallets]); // walletsを依存配列に追加

return <></>;
};
2 changes: 1 addition & 1 deletion pkgs/frontend/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import { currentChain } from "hooks/useViem";
import { currentChain, supportedChains } from "hooks/useViem";
import { goldskyClient } from "utils/apollo";
import { Header } from "./components/Header";
import { SwitchNetwork } from "./components/SwitchNetwork";
Expand Down
40 changes: 29 additions & 11 deletions pkgs/frontend/hooks/useViem.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,36 @@
import { http, createPublicClient } from "viem";
import { http, type Chain, createPublicClient } from "viem";
import { base, mainnet, optimism, sepolia } from "viem/chains";

export const chainId = Number(import.meta.env.VITE_CHAIN_ID) || 1;

export const currentChain =
chainId === 1
? mainnet
: chainId === 11155111
? sepolia
: chainId === 10
? optimism
: chainId === 8453
? base
: sepolia;
// サポートするチェーンの定義
export const SUPPORTED_CHAINS = {
MAINNET: mainnet,
SEPOLIA: sepolia,
OPTIMISM: optimism,
BASE: base,
} as const;

// チェーンIDからチェーンを取得する関数
export const getChainById = (id: number): Chain => {
switch (id) {
case 1:
return SUPPORTED_CHAINS.MAINNET;
case 11155111:
return SUPPORTED_CHAINS.SEPOLIA;
case 10:
return SUPPORTED_CHAINS.OPTIMISM;
case 8453:
return SUPPORTED_CHAINS.BASE;
default:
return SUPPORTED_CHAINS.SEPOLIA; // デフォルトチェーン
}
};

export const currentChain = getChainById(chainId);

// サポートされているすべてのチェーンのリスト
export const supportedChains = Object.values(SUPPORTED_CHAINS);

/**
* Public client for fetching data from the blockchain
Expand Down