diff --git a/pkgs/frontend/app/components/Header.tsx b/pkgs/frontend/app/components/Header.tsx index 98a00907..8ae3b235 100644 --- a/pkgs/frontend/app/components/Header.tsx +++ b/pkgs/frontend/app/components/Header.tsx @@ -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"; @@ -28,6 +31,44 @@ enum HeaderType { WorkspaceAndUserIcons = "WorkspaceAndUserIcons", } +// ネットワーク切り替えコンポーネントを作成 +const NetworkSwitcher = ({ + switchToChain, +}: { + switchToChain: (chain: Chain) => Promise; +}) => { + return ( + + + Network + + {/* チェーン切り替えボタンを表示 */} + {/* {supportedChains.map((chain) => ( + switchToChain(chain)} + w="100%" + mb={1} + variant={chain.id === currentChain.id ? "solid" : "outline"} + > + {chain.name} + + ))} */} + + {/* 現在のチェーンを表示 */} + switchToChain(currentChain)} + w="100%" + mb={1} + variant={"solid"} + > + {currentChain.name} + + + ); +}; + export const Header = () => { const [headerType, setHeaderType] = useState( HeaderType.NonHeader, @@ -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 ? ( @@ -128,20 +177,26 @@ export const Header = () => { {abbreviateAddress(identity.address)} + Logout ) : ( - { - navigate("/login"); - }} - w="auto" - > - Login - + + + + {currentChain.name} + + + + + + Logout + + + )} ) : ( diff --git a/pkgs/frontend/app/components/SwitchNetwork.tsx b/pkgs/frontend/app/components/SwitchNetwork.tsx index 20db900c..427891e7 100644 --- a/pkgs/frontend/app/components/SwitchNetwork.tsx +++ b/pkgs/frontend/app/components/SwitchNetwork.tsx @@ -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 <>; }; diff --git a/pkgs/frontend/app/root.tsx b/pkgs/frontend/app/root.tsx index c10965c2..618da14e 100644 --- a/pkgs/frontend/app/root.tsx +++ b/pkgs/frontend/app/root.tsx @@ -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"; diff --git a/pkgs/frontend/hooks/useViem.ts b/pkgs/frontend/hooks/useViem.ts index 09c68901..5309404d 100644 --- a/pkgs/frontend/hooks/useViem.ts +++ b/pkgs/frontend/hooks/useViem.ts @@ -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