diff --git a/apps/sample-react-app/src/Components/ConnectWalletButton.tsx b/apps/sample-react-app/src/Components/SwitchWalletButton.tsx similarity index 58% rename from apps/sample-react-app/src/Components/ConnectWalletButton.tsx rename to apps/sample-react-app/src/Components/SwitchWalletButton.tsx index 589abab8..5a2c6319 100644 --- a/apps/sample-react-app/src/Components/ConnectWalletButton.tsx +++ b/apps/sample-react-app/src/Components/SwitchWalletButton.tsx @@ -1,17 +1,15 @@ import type { HTMLChakraProps } from '@chakra-ui/react'; -import { Button, Icon, useDisclosure } from '@chakra-ui/react'; -import { WalletIcon } from '@heroicons/react/24/solid'; +import { useDisclosure } from '@chakra-ui/react'; import React from 'react'; -import { useWallet } from '@vechain/react-wallet-kit'; +import { ConnectWalletButton, useWallet } from '@vechain/react-wallet-kit'; import { AccountDetailModal } from './AccountDetailModal'; import { AddressButton } from './AddressButton'; -import { ConnectWalletModal } from './ConnectWalletModal'; -interface ConnectWalletButtonProps { +interface SwitchWalletButtonProps { buttonProps?: HTMLChakraProps<'button'>; } -export const ConnectWalletButton: React.FC = ({ +export const SwitchWalletButton: React.FC = ({ buttonProps, }): React.ReactElement => { const { isOpen, onOpen, onClose } = useDisclosure(); @@ -38,16 +36,5 @@ export const ConnectWalletButton: React.FC = ({ ); - return ( - <> - - - - ); + return ; }; diff --git a/apps/sample-react-app/src/Components/index.ts b/apps/sample-react-app/src/Components/index.ts index 2c393926..be36fbcf 100644 --- a/apps/sample-react-app/src/Components/index.ts +++ b/apps/sample-react-app/src/Components/index.ts @@ -2,6 +2,6 @@ export * from './AccountDetailBody'; export * from './AccountDetailModal'; export * from './AddressButton'; export * from './AddressIcon'; -export * from './ConnectWalletButton'; +export * from './SwitchWalletButton'; export * from './ConnectWalletModal'; export * from './WalletSourceRadio'; diff --git a/apps/sample-react-app/src/Components/layout/NavBar.tsx b/apps/sample-react-app/src/Components/layout/NavBar.tsx index 8076e345..1790917b 100644 --- a/apps/sample-react-app/src/Components/layout/NavBar.tsx +++ b/apps/sample-react-app/src/Components/layout/NavBar.tsx @@ -20,7 +20,7 @@ import { Bars3Icon } from '@heroicons/react/24/solid'; import { useWallet } from '@vechain/react-wallet-kit'; import { VechainLogo } from '../../Logos'; import { AccountDetailBody } from '../AccountDetailBody'; -import { ConnectWalletButton } from '../ConnectWalletButton'; +import { SwitchWalletButton } from '../SwitchWalletButton'; export const NavBar = (): JSX.Element => { const bg = useColorModeValue('gray.50', 'gray.900'); @@ -101,7 +101,7 @@ const MobileNavBarDrawer = ({ source={accountState.source} /> ) : ( - + )} @@ -114,7 +114,7 @@ const MobileNavBarDrawer = ({ const NavBarWalletConnect = (): JSX.Element => { return ( - + ); }; diff --git a/apps/sample-react-app/src/Screens/components/Welcome.tsx b/apps/sample-react-app/src/Screens/components/Welcome.tsx index bfd1e88c..78746791 100644 --- a/apps/sample-react-app/src/Screens/components/Welcome.tsx +++ b/apps/sample-react-app/src/Screens/components/Welcome.tsx @@ -1,7 +1,7 @@ import { Button, Heading, HStack, Link, Text, VStack } from '@chakra-ui/react'; import type { JSX } from 'react'; import { StyledCard } from '../../Components/shared'; -import { ConnectWalletButton } from '../../Components'; +import { SwitchWalletButton } from '../../Components'; export const Welcome = (): JSX.Element => { return ( @@ -18,7 +18,7 @@ export const Welcome = (): JSX.Element => { w="full" wrap="wrap" > - + void; +} + +export const ConnectWalletModal: React.FC = ({ + isOpen, + onClose, +}) => { + const header = ( + + + Connect Wallet + + ); + + return ( + } + header={header} + isOpen={isOpen} + onClose={onClose} + /> + ); +}; + +interface ConnectedWalletBodyProps { + onClose: () => void; +} + +const ConnectedWalletBody: React.FC = ({ + onClose, +}) => { + const toast = useToast(); + const { setAccount } = useWallet(); + const { vendor } = useConnex(); + + const [connectionLoading, setConnectionLoading] = useState(false); + const [connectionError, setConnectionError] = useState(''); + + const connectToWalletHandler = + useCallback(async (): Promise => { + const message: Connex.Vendor.CertMessage = { + purpose: 'identification', + payload: { + type: 'text', + content: 'Sign a certificate to prove your identity', + }, + }; + + if (!vendor) throw new Error('Vendor not available'); + + const certResponse = await vendor.sign('cert', message).request(); + + const cert: Certificate = { + purpose: message.purpose, + payload: message.payload, + domain: certResponse.annex.domain, + timestamp: certResponse.annex.timestamp, + signer: certResponse.annex.signer, + signature: certResponse.signature, + }; + + Certificate.verify(cert); + + return cert; + }, [vendor]); + + const onSuccessfullConnection = useCallback( + (cert: Certificate): void => { + setAccount(cert.signer); + onClose(); + toast({ + title: 'Wallet connected.', + description: `You've succesfully connected with wallet ${cert.signer}`, + status: 'success', + position: 'bottom-left', + duration: 5000, + isClosable: true, + }); + }, + [toast, setAccount, onClose], + ); + + const connectHandler = useCallback(async () => { + try { + setConnectionError(''); + setConnectionLoading(true); + + const cert = await connectToWalletHandler(); + + onSuccessfullConnection(cert); + } catch (e) { + if (e instanceof Error) { + setConnectionError(e.message); + } else { + setConnectionError('Failed to connect to wallet'); + } + } finally { + setConnectionLoading(false); + } + }, [ + onSuccessfullConnection, + setConnectionError, + setConnectionLoading, + connectToWalletHandler, + ]); + + const connect = useCallback(() => { + connectHandler().catch((e) => { + throw e; + }); + }, [connectHandler]); + + return ( + <> + + + Wallet + + + + + {connectionLoading ? ( + + + Waiting for wallet approval... + + ) : null} + {connectionError ? ( + + + {connectionError} + + ) : null} + + + + + ); +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/Components/Dialog.tsx b/packages/react-wallet-kit/src/ConnectWalletButton/Components/Dialog.tsx new file mode 100644 index 00000000..e1fb5abf --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/Components/Dialog.tsx @@ -0,0 +1,49 @@ +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { + Modal, + ModalBody, + ModalCloseButton, + ModalContent, + ModalFooter, + ModalHeader, + ModalOverlay, +} from '@chakra-ui/react'; +import React from 'react'; + +interface DialogProps { + isOpen: boolean; + onClose: () => void; + header?: React.ReactNode; + headerStyle?: HTMLChakraProps<'header'>; + body?: React.ReactNode; + footer?: React.ReactNode; + showCloseButton?: boolean; + closeButtonStyle?: HTMLChakraProps<'button'>; +} + +export const Dialog: React.FC = ({ + isOpen, + onClose, + header, + headerStyle = {}, + body, + footer, + showCloseButton = true, + closeButtonStyle = {}, +}) => { + return ( + + + + {header ? ( + {header} + ) : null} + {showCloseButton ? ( + + ) : null} + {body ? {body} : null} + {footer ? {footer} : null} + + + ); +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/Components/RadioCard.tsx b/packages/react-wallet-kit/src/ConnectWalletButton/Components/RadioCard.tsx new file mode 100644 index 00000000..b0012f46 --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/Components/RadioCard.tsx @@ -0,0 +1,66 @@ +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Box, Button, Flex, HStack } from '@chakra-ui/react'; +import React from 'react'; + +interface RadioCardProps extends HTMLChakraProps<'button'> { + children: React.ReactNode; + selected: boolean; + onClick: () => void; +} + +export const RadioCard: React.FC = ({ + children, + selected, + onClick, + ...props +}) => { + return ( + + ); +}; + +interface RadioCircleProps extends HTMLChakraProps<'div'> { + filled?: boolean; +} + +const RadioCircle: React.FC = ({ + filled = false, + ...props +}) => { + return ( + + + + ); +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/Components/WalletSourceRadio.tsx b/packages/react-wallet-kit/src/ConnectWalletButton/Components/WalletSourceRadio.tsx new file mode 100644 index 00000000..b163e375 --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/Components/WalletSourceRadio.tsx @@ -0,0 +1,111 @@ +import { + Box, + Flex, + HStack, + Icon, + Image, + Text, + Tooltip, + VStack, +} from '@chakra-ui/react'; +import { ExclamationTriangleIcon } from '@heroicons/react/24/solid'; +import React, { useCallback } from 'react'; +import type { WalletSource } from '@vechain/wallet-kit'; +import { useWallet } from '../../ConnexProvider'; +import { WalletSources } from '../Constants'; +import { RadioCard } from './RadioCard'; + +export const WalletSourceRadio: React.FC = () => { + const { availableWallets, wallets, setSource, accountState } = useWallet(); + + const handleSourceClick = useCallback( + (isDisabled: boolean, source: WalletSource) => () => { + if (!isDisabled) { + setSource(source); + } + }, + [setSource], + ); + + return ( + + {wallets.map((source: WalletSource) => { + const isDisabled = !availableWallets.includes(source); + const isSelected = source === accountState.source; + + return ( + + ); + })} + + ); +}; + +interface WalletSourceButtonProps { + source: WalletSource; + isSelected: boolean; + isDisabled: boolean; + onClick: () => void; +} + +const WalletSourceButton: React.FC = ({ + source, + isSelected, + isDisabled, + onClick, +}) => { + const sourceInfo = WalletSources[source]; + return ( + + + {`${sourceInfo.name}-logo`} + {sourceInfo.name} + + {isDisabled ? ( + + + + ) : null} + + ); +}; + +interface SourceNotDetectedIconProps { + source: WalletSource; +} + +const SourceNotDetectedIcon: React.FC = ({ + source, +}) => { + const sourceInfo = WalletSources[source]; + + return ( + + + + + + ); +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/ConnectWalletButton.tsx b/packages/react-wallet-kit/src/ConnectWalletButton/ConnectWalletButton.tsx new file mode 100644 index 00000000..3f96366e --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/ConnectWalletButton.tsx @@ -0,0 +1,27 @@ +import type { HTMLChakraProps } from '@chakra-ui/react'; +import { Button, Icon, useDisclosure } from '@chakra-ui/react'; +import { WalletIcon } from '@heroicons/react/24/solid'; +import React from 'react'; +import { ConnectWalletModal } from './Components/ConnectWalletModal'; + +interface ConnectWalletButtonProps { + buttonProps?: HTMLChakraProps<'button'>; +} + +export const ConnectWalletButton: React.FC = ({ + buttonProps, +}): React.ReactElement => { + const { isOpen, onOpen, onClose } = useDisclosure(); + return ( + <> + + + + ); +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/Constants/Constants.ts b/packages/react-wallet-kit/src/ConnectWalletButton/Constants/Constants.ts new file mode 100644 index 00000000..8bb8651c --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/Constants/Constants.ts @@ -0,0 +1,27 @@ +import { WalletSource } from '@vechain/wallet-kit'; + +interface SourceInfo { + name: string; + logo: string; +} + +const baseLogoUrl = `${process.env.PUBLIC_URL}/images/logo`; + +export const WalletSources: Record = { + [WalletSource.WalletConnect]: { + name: 'Wallet Connect', + logo: `${baseLogoUrl}/wallet-connect-logo.png`, + }, + [WalletSource.VeWorldExtension]: { + name: 'VeWorld Extension', + logo: `${baseLogoUrl}/veworld_black.png`, + }, + [WalletSource.Sync]: { + name: 'Sync', + logo: `${baseLogoUrl}/sync.png`, + }, + [WalletSource.Sync2]: { + name: 'Sync 2', + logo: `${baseLogoUrl}/sync2.png`, + }, +}; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/Constants/index.ts b/packages/react-wallet-kit/src/ConnectWalletButton/Constants/index.ts new file mode 100644 index 00000000..a719bffa --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/Constants/index.ts @@ -0,0 +1 @@ +export * from './Constants'; diff --git a/packages/react-wallet-kit/src/ConnectWalletButton/index.ts b/packages/react-wallet-kit/src/ConnectWalletButton/index.ts new file mode 100644 index 00000000..f0725a9c --- /dev/null +++ b/packages/react-wallet-kit/src/ConnectWalletButton/index.ts @@ -0,0 +1 @@ +export * from './ConnectWalletButton'; diff --git a/packages/react-wallet-kit/src/index.tsx b/packages/react-wallet-kit/src/index.tsx index a9e4b854..efd9ac66 100644 --- a/packages/react-wallet-kit/src/index.tsx +++ b/packages/react-wallet-kit/src/index.tsx @@ -3,3 +3,4 @@ import * as React from 'react'; export * from './ConnexProvider'; export * from './types'; +export * from './ConnectWalletButton';