diff --git a/pkgs/frontend/app/components/Header.tsx b/pkgs/frontend/app/components/Header.tsx index b8d6fc2..c1b99a5 100644 --- a/pkgs/frontend/app/components/Header.tsx +++ b/pkgs/frontend/app/components/Header.tsx @@ -2,9 +2,13 @@ import { useState, useEffect, useMemo } from "react"; import { Box, Flex, Text } from "@chakra-ui/react"; import { WorkspaceIcon } from "./icon/WorkspaceIcon"; import { UserIcon } from "./icon/UserIcon"; -import { useLocation } from "@remix-run/react"; +import { useLocation, useNavigate } from "@remix-run/react"; import { useActiveWalletIdentity } from "hooks/useENS"; import { ipfs2https } from "utils/ipfs"; +import { MenuContent, MenuItem, MenuRoot, MenuTrigger } from "./ui/menu"; +import { useActiveWallet } from "hooks/useWallet"; +import { usePrivy, useWallets } from "@privy-io/react-auth"; +import CommonButton from "./common/CommonButton"; const NO_HEADER_PATHS: string[] = ["/login", "/signup"]; // 適宜ヘッダーが不要なページのパスを追加 const WORKSPACES_PATHS: string[] = ["/workspaces"]; // 適宜ワークスペースが未選択な状態のページのパスを追加 @@ -28,6 +32,7 @@ export const Header = () => { HeaderType.NonHeader ); + const navigate = useNavigate(); const { pathname } = useLocation(); // ToDo: ページのパスや hooks で柔軟にロジックを実装する(切り替えてテストできます) @@ -63,6 +68,9 @@ export const Header = () => { workspaceName, ]); + const { isSmartWallet } = useActiveWallet(); + const { logout } = usePrivy(); + const { wallets } = useWallets(); const { identity } = useActiveWalletIdentity(); const userImageUrl = useMemo(() => { @@ -70,6 +78,18 @@ export const Header = () => { return avatar ? ipfs2https(avatar) : undefined; }, [identity]); + const handleLogout = () => { + if (isSmartWallet) { + logout(); + } else { + if (wallets.find((w) => w.connectorType === "injected")) { + alert("ウォレット拡張機能から切断してください。"); + } else { + Promise.all(wallets.map((wallet) => wallet.disconnect())); + } + } + }; + return headerType !== HeaderType.NonHeader ? ( @@ -90,7 +110,29 @@ export const Header = () => { )} - + {identity ? ( + + + + + + + Logout + + + + ) : ( + { + navigate("/login"); + }} + w="auto" + > + Login + + )} ) : ( <> diff --git a/pkgs/frontend/app/components/ui/menu.tsx b/pkgs/frontend/app/components/ui/menu.tsx new file mode 100644 index 0000000..763005b --- /dev/null +++ b/pkgs/frontend/app/components/ui/menu.tsx @@ -0,0 +1,110 @@ +"use client" + +import { AbsoluteCenter, Menu as ChakraMenu, Portal } from "@chakra-ui/react" +import * as React from "react" +import { LuCheck, LuChevronRight } from "react-icons/lu" + +interface MenuContentProps extends ChakraMenu.ContentProps { + portalled?: boolean + portalRef?: React.RefObject +} + +export const MenuContent = React.forwardRef( + function MenuContent(props, ref) { + const { portalled = true, portalRef, ...rest } = props + return ( + + + + + + ) + }, +) + +export const MenuArrow = React.forwardRef< + HTMLDivElement, + ChakraMenu.ArrowProps +>(function MenuArrow(props, ref) { + return ( + + + + ) +}) + +export const MenuCheckboxItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.CheckboxItemProps +>(function MenuCheckboxItem(props, ref) { + return ( + + + {props.children} + + ) +}) + +export const MenuRadioItem = React.forwardRef< + HTMLDivElement, + ChakraMenu.RadioItemProps +>(function MenuRadioItem(props, ref) { + const { children, ...rest } = props + return ( + + + + + + + {children} + + ) +}) + +export const MenuItemGroup = React.forwardRef< + HTMLDivElement, + ChakraMenu.ItemGroupProps +>(function MenuItemGroup(props, ref) { + const { title, children, ...rest } = props + return ( + + {title && ( + + {title} + + )} + {children} + + ) +}) + +export interface MenuTriggerItemProps extends ChakraMenu.ItemProps { + startIcon?: React.ReactNode +} + +export const MenuTriggerItem = React.forwardRef< + HTMLDivElement, + MenuTriggerItemProps +>(function MenuTriggerItem(props, ref) { + const { startIcon, children, ...rest } = props + return ( + + {startIcon} + {children} + + + ) +}) + +export const MenuRadioItemGroup = ChakraMenu.RadioItemGroup +export const MenuContextTrigger = ChakraMenu.ContextTrigger +export const MenuRoot = ChakraMenu.Root +export const MenuSeparator = ChakraMenu.Separator + +export const MenuItem = ChakraMenu.Item +export const MenuItemText = ChakraMenu.ItemText +export const MenuItemCommand = ChakraMenu.ItemCommand +export const MenuTrigger = ChakraMenu.Trigger diff --git a/pkgs/frontend/hooks/useWallet.ts b/pkgs/frontend/hooks/useWallet.ts index 65bd735..a2bc046 100644 --- a/pkgs/frontend/hooks/useWallet.ts +++ b/pkgs/frontend/hooks/useWallet.ts @@ -1,4 +1,4 @@ -import { useWallets } from "@privy-io/react-auth"; +import { usePrivy, useWallets } from "@privy-io/react-auth"; import { createSmartAccountClient, SmartAccountClient } from "permissionless"; import { toSimpleSmartAccount } from "permissionless/accounts"; import { createPimlicoClient } from "permissionless/clients/pimlico"; @@ -36,6 +36,8 @@ export const useSmartAccountClient = () => { * @returns */ const create = async () => { + setClient(undefined); + console.log(wallets); const embeddedWallet = wallets.find( (wallet) => wallet.connectorType === "embedded" ); @@ -78,6 +80,7 @@ export const useAccountClient = () => { useEffect(() => { const create = async () => { + setClient(undefined); if (!wallets[0]) return; const wallet = wallets[0]; diff --git a/pkgs/frontend/package.json b/pkgs/frontend/package.json index 137bb70..454836d 100644 --- a/pkgs/frontend/package.json +++ b/pkgs/frontend/package.json @@ -14,7 +14,7 @@ "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json}\"" }, "dependencies": { - "@chakra-ui/react": "^3.2.0", + "@chakra-ui/react": "^3.2.3", "@emotion/cache": "^11.13.5", "@emotion/react": "^11.13.5", "@emotion/server": "^11.11.0", @@ -33,7 +33,7 @@ "permissionless": "^0.2.20", "react": "^18.3.1", "react-dom": "^18.3.1", - "react-icons": "^5.3.0", + "react-icons": "^5.4.0", "viem": "^2.21.51" }, "devDependencies": { diff --git a/yarn.lock b/yarn.lock index 9a59181..3addbee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1308,10 +1308,10 @@ dependencies: "@chainsafe/is-ip" "^2.0.1" -"@chakra-ui/react@^3.2.0": - version "3.2.2" - resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-3.2.2.tgz#cd846cdfaf9002d7339f15e1932ed2a886c7374b" - integrity sha512-9GAESg6rSJ4NCfPAv2s5mOYnrFU+5x3xxR9Ab1phsZ2junR7po55PxB/Zdr1VNb1NX+6oIGMAR5s74oN5sYlfQ== +"@chakra-ui/react@^3.2.3": + version "3.2.3" + resolved "https://registry.yarnpkg.com/@chakra-ui/react/-/react-3.2.3.tgz#61615fbee75e9aedb6bb136d15de5ce99184f9da" + integrity sha512-KfhKkcnHPqMwrX5eZ1xVeewOy6L4+iL2684tnP7re7erferfEBeqAAkGZpzWUcjb+IMwClYFygXk0gQrsVdtaQ== dependencies: "@ark-ui/react" "4.4.4" "@emotion/is-prop-valid" "1.3.1" @@ -15524,10 +15524,10 @@ react-helmet-async@^1.3.0: react-fast-compare "^3.2.0" shallowequal "^1.1.0" -react-icons@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.3.0.tgz#ccad07a30aebd40a89f8cfa7d82e466019203f1c" - integrity sha512-DnUk8aFbTyQPSkCfF8dbX6kQjXA9DktMeJqfjrg6cK9vwQVMxmcA3BfP4QoiztVmEHtwlTgLFsPuH2NskKT6eg== +react-icons@^5.4.0: + version "5.4.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.4.0.tgz#443000f6e5123ee1b21ea8c0a716f6e7797f7416" + integrity sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ== react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0: version "16.13.1"