diff --git a/components/UI/avatar.tsx b/components/UI/avatar.tsx new file mode 100644 index 00000000..a0b3e4e2 --- /dev/null +++ b/components/UI/avatar.tsx @@ -0,0 +1,47 @@ +import React, { + FunctionComponent, + useContext, + useEffect, + useState, +} from "react"; +import { StarknetIdJsContext } from "../../context/StarknetIdJsProvider"; +import ProfilIcon from "./iconsComponents/icons/profilIcon"; +import theme from "../../styles/theme"; + +type AvatarProps = { + address: string; + width?: string; +}; + +const Avatar: FunctionComponent = ({ address, width = "32" }) => { + const { starknetIdNavigator } = useContext(StarknetIdJsContext); + const [starknetId, setStarknetId] = useState(""); + + useEffect(() => { + if (!address) return; + (async () => { + const domain = await starknetIdNavigator?.getStarkName(address); + if (!domain) return; + const starknetId = await starknetIdNavigator?.getStarknetId(domain); + if (!starknetId) return; + setStarknetId(starknetId.toString()); + })(); + }, [address]); + + return ( + <> + {starknetId ? ( + + ) : ( + + )} + + ); +}; + +export default Avatar; diff --git a/components/UI/changeWallet.tsx b/components/UI/changeWallet.tsx new file mode 100644 index 00000000..f8367aa8 --- /dev/null +++ b/components/UI/changeWallet.tsx @@ -0,0 +1,98 @@ +import React from "react"; +import styles from "../../styles/components/wallets.module.css"; +import { Connector, useConnectors } from "@starknet-react/core"; +import Button from "./button"; +import { FunctionComponent } from "react"; +import { Modal } from "@mui/material"; +import WalletIcons from "./iconsComponents/icons/walletIcons"; +import getDiscoveryWallets from "get-starknet-core"; +import useGetDiscoveryWallets from "../../hooks/useGetDiscoveryWallets"; + +type ChangeWalletProps = { + closeWallet: () => void; + hasWallet: boolean; +}; + +const ChangeWallet: FunctionComponent = ({ + closeWallet, + hasWallet, +}) => { + const { connect, connectors } = useConnectors(); + const downloadLinks = useGetDiscoveryWallets( + getDiscoveryWallets.getDiscoveryWallets() + ); + + function connectWallet(connector: Connector): void { + connect(connector); + closeWallet(); + } + + return ( + +
+ +

Change wallet

+ {connectors.map((connector) => { + if (connector.available()) { + return ( +
+ +
+ ); + } else { + if (connector.id === "braavos" || connector.id === "argentX") { + return ( +
+ +
+ ); + } + } + })} +
+
+ ); +}; +export default ChangeWallet; diff --git a/components/UI/iconsComponents/icons/profilIcon.tsx b/components/UI/iconsComponents/icons/profilIcon.tsx new file mode 100644 index 00000000..f2e15a05 --- /dev/null +++ b/components/UI/iconsComponents/icons/profilIcon.tsx @@ -0,0 +1,24 @@ +import React, { FunctionComponent } from "react"; + +const ProfilIcon: FunctionComponent = ({ color, width }) => { + return ( + + + + + ); +}; + +export default ProfilIcon; diff --git a/components/UI/modalWallet.tsx b/components/UI/modalWallet.tsx deleted file mode 100644 index 6ecaa74e..00000000 --- a/components/UI/modalWallet.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, { useEffect, useState } from "react"; -import styles from "../../styles/components/walletMessage.module.css"; -import { FunctionComponent } from "react"; -import { Modal } from "@mui/material"; -import { useAccount, useTransactions } from "@starknet-react/core"; -import { ContentCopy } from "@mui/icons-material"; -import CopiedIcon from "./iconsComponents/icons/copiedIcon"; -import ClickableAction from "./iconsComponents/clickableAction"; -import { CommonTransactionReceiptResponse } from "starknet"; -import CloseIcon from "./iconsComponents/icons/closeIcon"; -import ArgentIcon from "./iconsComponents/icons/argentIcon"; -import theme from "../../styles/theme"; -import LogoutIcon from "@mui/icons-material/Logout"; - -type ModalWalletProps = { - closeModal: () => void; - open: boolean; - domain: string; - disconnectByClick: () => void; - hashes: string[]; - setTxLoading: (txLoading: number) => void; -}; - -const ModalWallet: FunctionComponent = ({ - closeModal, - open, - domain, - disconnectByClick, - hashes, - setTxLoading, -}) => { - const { address, connector } = useAccount(); - const [copied, setCopied] = useState(false); - const network = - process.env.NEXT_PUBLIC_IS_TESTNET === "true" ? "testnet" : "mainnet"; - const transactions = useTransactions({ hashes, watch: true }); - // Argent web wallet is detectable only like this - const isWebWallet = (connector as any)?._wallet?.id === "argentWebWallet"; - - // TODO: Check for starknet react fix and delete that code - useEffect(() => { - const interval = setInterval(() => { - for (const tx of transactions) { - tx.refetch(); - } - }, 3_000); - return () => clearInterval(interval); - }, [transactions?.length]); - - useEffect(() => { - if (transactions) { - // Give the number of tx that are loading (I use any because there is a problem on Starknet React types) - setTxLoading( - transactions.filter((tx) => (tx?.data as any)?.status === "RECEIVED") - .length - ); - } - }, [transactions]); - - const copyToClipboard = () => { - if (!address) return; - setCopied(true); - navigator.clipboard.writeText(address); - setTimeout(() => { - setCopied(false); - }, 1500); - }; - - return ( - -
- -
-
- {connector && connector.id === "braavos" ? ( - braavos logo - ) : ( - - )} - -

Connected with  {domain} 

-
-
-
- } - title="Disconnect" - width="auto" - /> - - ) : ( - - ) - } - title="Copy Address" - width="auto" - /> - {isWebWallet && ( - - window.open( - network === "mainnet" - ? "https://web.argent.xyz" - : "https://web.hydrogen.argent47.net", - "_blank", - "noopener noreferrer" - ) - } - icon={} - title="Web wallet Dashboard" - width="auto" - /> - )} -
-
-
My transactions
-
- {transactions && transactions.length > 0 ? ( - transactions.map((tx) => { - return ( - - ); - }) - ) : ( -

No ongoing transactions

- )} -
-
-
-
- ); -}; -export default ModalWallet; diff --git a/components/UI/navbar.tsx b/components/UI/navbar.tsx index 17960ada..400241db 100644 --- a/components/UI/navbar.tsx +++ b/components/UI/navbar.tsx @@ -12,7 +12,6 @@ import { useConnectors, useAccount, useProvider, - useTransactionManager, Connector, } from "@starknet-react/core"; import Wallets from "./wallets"; @@ -20,12 +19,10 @@ import ModalMessage from "./modalMessage"; import { useDisplayName } from "../../hooks/displayName.tsx"; import { useDomainFromAddress } from "../../hooks/naming"; import { constants } from "starknet"; -import ModalWallet from "./modalWallet"; -import { CircularProgress } from "@mui/material"; -import AccountCircleIcon from "@mui/icons-material/AccountCircle"; import { useRouter } from "next/router"; import theme from "../../styles/theme"; import { FaDiscord, FaTwitter } from "react-icons/fa"; +import WalletButton from "../navbar/walletButton"; const Navbar: FunctionComponent = () => { const [nav, setNav] = useState(false); @@ -33,7 +30,6 @@ const Navbar: FunctionComponent = () => { const { address } = useAccount(); const [isConnected, setIsConnected] = useState(false); const [isWrongNetwork, setIsWrongNetwork] = useState(false); - const [txLoading, setTxLoading] = useState(0); const { available, connect, disconnect, connectors, refresh } = useConnectors(); const { provider } = useProvider(); @@ -44,7 +40,6 @@ const Navbar: FunctionComponent = () => { const network = process.env.NEXT_PUBLIC_IS_TESTNET === "true" ? "testnet" : "mainnet"; const [navbarBg, setNavbarBg] = useState(false); - const { hashes } = useTransactionManager(); const [showWallet, setShowWallet] = useState(false); const router = useRouter(); @@ -189,38 +184,12 @@ const Navbar: FunctionComponent = () => { {/* Note: I'm not sure that our testnet will be public so we don't show any link */} {/* */} -
- -
+
{
} /> - setShowWallet(false)} - disconnectByClick={disconnectByClick} - hashes={hashes} - setTxLoading={setTxLoading} - /> setHasWallet(false)} hasWallet={Boolean(hasWallet && !isWrongNetwork)} diff --git a/components/navbar/walletButton.tsx b/components/navbar/walletButton.tsx new file mode 100644 index 00000000..80169325 --- /dev/null +++ b/components/navbar/walletButton.tsx @@ -0,0 +1,182 @@ +import React, { FunctionComponent, useMemo, useState, useEffect } from "react"; +import Button from "../UI/button"; +import { useDisplayName } from "../../hooks/displayName.tsx"; +import { + useAccount, + useTransactionManager, + useTransactions, +} from "@starknet-react/core"; +import styles from "../../styles/components/navbar.module.css"; +import ProfilIcon from "../UI/iconsComponents/icons/profilIcon"; +import theme from "../../styles/theme"; +import Avatar from "../UI/avatar"; +import CopyIcon from "../UI/iconsComponents/icons/copyIcon"; +import { Wallet } from "@mui/icons-material"; +import LogoutIcon from "@mui/icons-material/Logout"; +import VerifiedIcon from "../UI/iconsComponents/icons/verifiedIcon"; +import ChangeWallet from "../UI/changeWallet"; +import ArgentIcon from "../UI/iconsComponents/icons/argentIcon"; + +type WalletButtonProps = { + setShowWallet: (showWallet: boolean) => void; + showWallet: boolean; + refreshAndShowWallet: () => void; + disconnectByClick: () => void; +}; + +const WalletButton: FunctionComponent = ({ + setShowWallet, + showWallet, + refreshAndShowWallet, + disconnectByClick, +}) => { + const { address, connector } = useAccount(); + const { hashes } = useTransactionManager(); + const transactions = useTransactions({ hashes, watch: true }); + const domainOrAddressMinified = useDisplayName(address ?? ""); + const [txLoading, setTxLoading] = useState(0); + const [copied, setCopied] = useState(false); + const [changeWallet, setChangeWallet] = useState(false); + const [hovering, setHovering] = useState(false); + const [unfocus, setUnfocus] = useState(false); + + const network = + process.env.NEXT_PUBLIC_IS_TESTNET === "true" ? "testnet" : "mainnet"; + const isWebWallet = (connector as any)?._wallet?.id === "argentWebWallet"; + + const buttonName = useMemo( + () => + address + ? txLoading + ? `${txLoading} on hold` + : domainOrAddressMinified + : "connect", + [address, domainOrAddressMinified] + ); + + useEffect(() => { + const interval = setInterval(() => { + for (const tx of transactions) { + tx.refetch(); + } + }, 3_000); + return () => clearInterval(interval); + }, [transactions?.length]); + + useEffect(() => { + if (transactions) { + // Give the number of tx that are loading (I use any because there is a problem on Starknet React types) + setTxLoading( + transactions.filter((tx) => (tx?.data as any)?.status === "RECEIVED") + .length + ); + } + }, [transactions]); + + const copyAddress = (e: React.MouseEvent) => { + e.stopPropagation(); + setCopied(true); + navigator.clipboard.writeText(address ?? ""); + setTimeout(() => { + setCopied(false); + }, 1500); + }; + + const handleDisconnect = (e: React.MouseEvent) => { + e.stopPropagation(); + disconnectByClick(); + }; + + const handleWalletChange = (e: React.MouseEvent) => { + e.stopPropagation(); + setHovering(false); + setUnfocus(true); + setChangeWallet(true); + }; + + const handleOpenWebWallet = (e: React.MouseEvent) => { + window.open( + network === "mainnet" + ? "https://web.argent.xyz" + : "https://web.hydrogen.argent47.net", + "_blank", + "noopener noreferrer" + ); + }; + + useEffect(() => { + if (!unfocus) return; + if (hovering) setUnfocus(false); + else setShowWallet(false); + }, [unfocus, hovering]); + + return ( + <> +
setUnfocus(true)} + onMouseEnter={() => setHovering(true)} + onMouseLeave={() => setHovering(false)} + > + + {isWebWallet && ( + + )} + + +
+ ) : null} + + + + setChangeWallet(false)} + hasWallet={changeWallet} + /> + + ); +}; + +export default WalletButton; diff --git a/styles/components/navbar.module.css b/styles/components/navbar.module.css index 632303b4..8cd601a1 100644 --- a/styles/components/navbar.module.css +++ b/styles/components/navbar.module.css @@ -46,3 +46,98 @@ .navbarScrolled { background-color: rgba(0, 0, 0, 1); } + +.buttonContainer { + margin: 0 1.25rem; +} + +.buttonContainer > button { + position: relative; + margin: 0; + padding: 8px 16px; + height: unset; + color: var(--background); +} + +.buttonText { + margin-right: 32px; + text-transform: capitalize; + font-weight: bold; + font-family: Sora-ExtraBold; + min-width: 124px; + /* Body/middle/bold */ + font-size: 18px; + font-weight: 700; + line-height: 24px; /* 133.333% */ + letter-spacing: 0.18px; +} + +.buttonSeparator { + position: absolute; + top: 0; + bottom: 0; + right: 64px; + align-self: stretch; + width: 1px; + background: var(--background); +} + +.buttonIcon svg { + display: block; + width: 32px; + height: 32px; + margin-left: auto; +} + +.buttonContainer[aria-label="connected"] > button { + background: var(--background600); + color: white; + min-width: 258px; +} + +.buttonContainer[aria-label="connected"] .buttonSeparator { + background: #aab1b6; +} + +.buttonContainer[aria-label="connected"] > svg { + background: #aab1b6; +} + +.buttonContainer[aria-selected="true"] > button { + border-radius: 10px 10px 0px 0px; + border: 1px solid var(--secondary500); +} + +.walletMenu { + position: absolute; + right: -1px; + left: -1px; + bottom: 0; + display: flex; + flex-direction: column; + align-items: left; + justify-content: left; + transform: translateY(100%); + padding: 12px 16px; + background-color: var(--background600); + border-radius: 0px 0px 10px 10px; + border: 1px solid var(--secondary500); +} + +.walletMenu button { + display: flex; + align-items: center; + color: var(--secondary500); +} + +.walletMenu svg { + margin-right: 12px; + color: var(--secondary500); + fill: var(--secondary500); +} + +.walletMenu button:hover svg, +.walletMenu button:hover p { + color: white; + fill: white; +} diff --git a/styles/theme.ts b/styles/theme.ts index 03331033..78ab9c41 100644 --- a/styles/theme.ts +++ b/styles/theme.ts @@ -12,6 +12,9 @@ const theme = createTheme({ main: "#f4faff", dark: "#e1dcea", }, + background: { + default: "#101012", + }, }, });