diff --git a/package.json b/package.json index 91e19c9..81081a0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "web-wallet", - "version": "0.1.0", + "version": "0.1.2", "private": true, "dependencies": { "@cerc-io/registry-sdk": "^0.2.5", @@ -13,6 +13,8 @@ "@ethersproject/shims": "^5.7.0", "@hookform/resolvers": "^3.3.4", "@json-rpc-tools/utils": "^1.7.6", + "@mui/icons-material": "^5.16.7", + "@mui/lab": "^5.0.0-alpha.173", "@mui/material": "^5.16.4", "@react-navigation/elements": "^1.3.30", "@react-navigation/native": "^6.1.10", diff --git a/prettier.config.js b/prettier.config.js new file mode 100644 index 0000000..ecc8a48 --- /dev/null +++ b/prettier.config.js @@ -0,0 +1,18 @@ +const config = { + arrowParens: "always", + printWidth: 80, + semi: false, + singleQuote: true, + tabWidth: 2, + trailingComma: "es5", + importOrderSeparation: true, + importOrderSortSpecifiers: true, + plugins: ["@trivago/prettier-plugin-sort-imports"], + importOrder: [ + "", + "^(pages|components|utils|icons|test|graphql)/(.*)$", + "^[./]", + ], +}; + +export default config; diff --git a/public/Logo.svg b/public/Logo.svg new file mode 100644 index 0000000..2efbd79 --- /dev/null +++ b/public/Logo.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/public/index.html b/public/index.html index 1e28cec..f863862 100644 --- a/public/index.html +++ b/public/index.html @@ -1,21 +1,24 @@ - + - - - - - - - - - - - Laconic Wallet - - - - -
-
-
-
+ } + + + + + +
+
+
- - + + diff --git a/public/manifest.json b/public/manifest.json index a88b288..f3556e5 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -21,5 +21,5 @@ "start_url": ".", "display": "standalone", "theme_color": "#000000", - "background_color": "#ffffff" + "background_color": "#0f0f0f" } diff --git a/src/App.tsx b/src/App.tsx index fb1a4ae..49881f8 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,43 +1,44 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { Button, Snackbar, Text } from 'react-native-paper'; -import { TxBody, AuthInfo } from 'cosmjs-types/cosmos/tx/v1beta1/tx'; +import React, { useCallback, useEffect, useMemo, useState } from "react"; +import { Button, Snackbar, Surface, Text } from "react-native-paper"; +import { TxBody, AuthInfo } from "cosmjs-types/cosmos/tx/v1beta1/tx"; -import { SignClientTypes } from '@walletconnect/types'; -import { useNavigation } from '@react-navigation/native'; +import { SignClientTypes } from "@walletconnect/types"; +import { useNavigation } from "@react-navigation/native"; import { createStackNavigator, StackNavigationProp, -} from '@react-navigation/stack'; -import { getSdkError } from '@walletconnect/utils'; -import { Web3WalletTypes } from '@walletconnect/web3wallet'; -import { formatJsonRpcResult } from '@json-rpc-tools/utils'; +} from "@react-navigation/stack"; +import { getSdkError } from "@walletconnect/utils"; +import { Web3WalletTypes } from "@walletconnect/web3wallet"; +import { formatJsonRpcResult } from "@json-rpc-tools/utils"; -import PairingModal from './components/PairingModal'; -import { useWalletConnect } from './context/WalletConnectContext'; -import { useAccounts } from './context/AccountsContext'; -import InvalidPath from './screens/InvalidPath'; -import SignMessage from './screens/SignMessage'; -import HomeScreen from './screens/HomeScreen'; -import SignRequest from './screens/SignRequest'; -import AddSession from './screens/AddSession'; -import WalletConnect from './screens/WalletConnect'; -import ApproveTransaction from './screens/ApproveTransaction'; -import { StackParamsList } from './types'; -import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data'; -import { getSignParamsMessage } from './utils/wallet-connect/helpers'; -import ApproveTransfer from './screens/ApproveTransfer'; -import AddNetwork from './screens/AddNetwork'; -import EditNetwork from './screens/EditNetwork'; -import { COSMOS, EIP155 } from './utils/constants'; -import { useNetworks } from './context/NetworksContext'; -import { NETWORK_METHODS } from './utils/wallet-connect/common-data'; -import { COSMOS_METHODS } from './utils/wallet-connect/COSMOSData'; +import PairingModal from "./components/PairingModal"; +import { useWalletConnect } from "./context/WalletConnectContext"; +import { useAccounts } from "./context/AccountsContext"; +import InvalidPath from "./screens/InvalidPath"; +import SignMessage from "./screens/SignMessage"; +import HomeScreen from "./screens/HomeScreen"; +import SignRequest from "./screens/SignRequest"; +import AddSession from "./screens/AddSession"; +import WalletConnect from "./screens/WalletConnect"; +import ApproveTransaction from "./screens/ApproveTransaction"; +import { StackParamsList } from "./types"; +import { EIP155_SIGNING_METHODS } from "./utils/wallet-connect/EIP155Data"; +import { getSignParamsMessage } from "./utils/wallet-connect/helpers"; +import ApproveTransfer from "./screens/ApproveTransfer"; +import AddNetwork from "./screens/AddNetwork"; +import EditNetwork from "./screens/EditNetwork"; +import { COSMOS, EIP155 } from "./utils/constants"; +import { useNetworks } from "./context/NetworksContext"; +import { NETWORK_METHODS } from "./utils/wallet-connect/common-data"; +import { COSMOS_METHODS } from "./utils/wallet-connect/COSMOSData"; +import styles from "./styles/stylesheet"; +import { Header } from "./components/Header"; const Stack = createStackNavigator(); const App = (): React.JSX.Element => { - const navigation = - useNavigation>(); + const navigation = useNavigation>(); const { web3wallet, setActiveSessions } = useWalletConnect(); const { accounts, setCurrentIndex } = useAccounts(); @@ -45,16 +46,16 @@ const App = (): React.JSX.Element => { const [modalVisible, setModalVisible] = useState(false); const [toastVisible, setToastVisible] = useState(false); const [currentProposal, setCurrentProposal] = useState< - SignClientTypes.EventArguments['session_proposal'] | undefined + SignClientTypes.EventArguments["session_proposal"] | undefined >(); const onSessionProposal = useCallback( - async (proposal: SignClientTypes.EventArguments['session_proposal']) => { + async (proposal: SignClientTypes.EventArguments["session_proposal"]) => { if (!accounts.length || !accounts.length) { const { id } = proposal; await web3wallet!.rejectSession({ id, - reason: getSdkError('UNSUPPORTED_ACCOUNTS'), + reason: getSdkError("UNSUPPORTED_ACCOUNTS"), }); return; } @@ -74,10 +75,11 @@ const App = (): React.JSX.Element => { switch (request.method) { case NETWORK_METHODS.GET_NETWORKS: const currentNetworkId = networksData.find( - networkData => networkData.networkId === selectedNetwork!.networkId, + (networkData) => + networkData.networkId === selectedNetwork!.networkId, )?.networkId; - const networkNamesData = networksData.map(networkData => { + const networkNamesData = networksData.map((networkData) => { return { id: networkData.networkId, name: networkData.networkName, @@ -98,13 +100,13 @@ const App = (): React.JSX.Element => { case NETWORK_METHODS.CHANGE_NETWORK: const networkNameData = request.params[0]; const network = networksData.find( - networkData => networkData.networkId === networkNameData.id, + (networkData) => networkData.networkId === networkNameData.id, ); setCurrentIndex(0); setSelectedNetwork(network); const response = formatJsonRpcResult(id, { - response: 'true', + response: "true", }); await web3wallet!.respondSessionRequest({ @@ -114,7 +116,7 @@ const App = (): React.JSX.Element => { break; case EIP155_SIGNING_METHODS.ETH_SEND_TRANSACTION: - navigation.navigate('ApproveTransfer', { + navigation.navigate("ApproveTransfer", { transaction: request.params[0], requestEvent, requestSessionData, @@ -122,7 +124,7 @@ const App = (): React.JSX.Element => { break; case EIP155_SIGNING_METHODS.PERSONAL_SIGN: - navigation.navigate('SignRequest', { + navigation.navigate("SignRequest", { namespace: EIP155, address: request.params[1], message: getSignParamsMessage(request.params), @@ -136,19 +138,19 @@ const App = (): React.JSX.Element => { txbody: TxBody.toJSON( TxBody.decode( Uint8Array.from( - Buffer.from(request.params.signDoc.bodyBytes, 'hex'), + Buffer.from(request.params.signDoc.bodyBytes, "hex"), ), ), ), authInfo: AuthInfo.toJSON( AuthInfo.decode( Uint8Array.from( - Buffer.from(request.params.signDoc.authInfoBytes, 'hex'), + Buffer.from(request.params.signDoc.authInfoBytes, "hex"), ), ), ), }; - navigation.navigate('SignRequest', { + navigation.navigate("SignRequest", { namespace: COSMOS, address: request.params.signerAddress, message: JSON.stringify(message, undefined, 2), @@ -158,7 +160,7 @@ const App = (): React.JSX.Element => { break; case COSMOS_METHODS.COSMOS_SIGN_AMINO: - navigation.navigate('SignRequest', { + navigation.navigate("SignRequest", { namespace: COSMOS, address: request.params.signerAddress, message: request.params.signDoc.memo, @@ -168,7 +170,7 @@ const App = (): React.JSX.Element => { break; case COSMOS_METHODS.COSMOS_SEND_TOKENS: - navigation.navigate('ApproveTransfer', { + navigation.navigate("ApproveTransfer", { transaction: request.params[0], requestEvent, requestSessionData, @@ -177,7 +179,7 @@ const App = (): React.JSX.Element => { case COSMOS_METHODS.COSMOS_SEND_TRANSACTION: const { transactionMessage, signer } = request.params; - navigation.navigate('ApproveTransaction', { + navigation.navigate("ApproveTransaction", { transactionMessage, signer, requestEvent, @@ -186,7 +188,7 @@ const App = (): React.JSX.Element => { break; default: - throw new Error('Invalid method'); + throw new Error("Invalid method"); } }, [ @@ -195,7 +197,7 @@ const App = (): React.JSX.Element => { setSelectedNetwork, setCurrentIndex, selectedNetwork, - web3wallet + web3wallet, ], ); @@ -205,18 +207,20 @@ const App = (): React.JSX.Element => { }, [setActiveSessions, web3wallet]); useEffect(() => { - web3wallet?.on('session_proposal', onSessionProposal); - web3wallet?.on('session_request', onSessionRequest); - web3wallet?.on('session_delete', onSessionDelete); + web3wallet?.on("session_proposal", onSessionProposal); + web3wallet?.on("session_request", onSessionRequest); + web3wallet?.on("session_delete", onSessionDelete); return () => { - web3wallet?.off('session_proposal', onSessionProposal); - web3wallet?.off('session_request', onSessionRequest); - web3wallet?.off('session_delete', onSessionDelete); + web3wallet?.off("session_proposal", onSessionProposal); + web3wallet?.off("session_request", onSessionRequest); + web3wallet?.off("session_delete", onSessionDelete); }; }); + const showWalletConnect = useMemo(() => accounts.length > 0, [accounts]); + return ( - <> + { component={HomeScreen} options={{ // eslint-disable-next-line react/no-unstable-nested-components - headerTitle: () => Laconic Wallet, + header: () => ( +
+ ), }} /> { component={SignMessage} options={{ // eslint-disable-next-line react/no-unstable-nested-components - headerTitle: () => Sign Message, + header: () =>
, }} /> { component={SignRequest} options={{ // eslint-disable-next-line react/no-unstable-nested-components - headerTitle: () => Sign Request, + header: () =>
, }} /> { name="AddSession" component={AddSession} options={{ - title: 'New session', + header: () =>
, }} /> { headerRight: () => ( ), @@ -282,28 +289,28 @@ const App = (): React.JSX.Element => { name="ApproveTransfer" component={ApproveTransfer} options={{ - title: 'Approve transfer', + header: () =>
, }} />
, }} />
, }} />
, }} /> @@ -317,10 +324,11 @@ const App = (): React.JSX.Element => { setToastVisible(false)} - duration={3000}> + duration={3000} + > Session approved - + ); }; diff --git a/src/components/AccountDetails.tsx b/src/components/AccountDetails.tsx index 8c16c61..bd2e737 100644 --- a/src/components/AccountDetails.tsx +++ b/src/components/AccountDetails.tsx @@ -1,9 +1,7 @@ -import React from 'react'; -import { View } from 'react-native'; -import { Text } from 'react-native-paper'; +import React from "react"; -import { Account } from '../types'; -import styles from '../styles/stylesheet'; +import { Account } from "../types"; +import { Box, Stack, Typography } from "@mui/material"; interface AccountDetailsProps { account: Account | undefined; @@ -11,20 +9,28 @@ interface AccountDetailsProps { const AccountDetails: React.FC = ({ account }) => { return ( - - - Address: - {account?.address} - - - Public Key: - {account?.pubKey} - - - HD Path: - {account?.hdPath} - - + + + + + Address: + + {account?.address} + + + + + Public Key: + + {account?.pubKey} + + + + HD Path: + + {account?.hdPath} + + ); }; diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index a1c292c..ee7eb44 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -1,22 +1,30 @@ -import React, { useEffect, useState } from 'react'; -import { TouchableOpacity, View } from 'react-native'; -import { Button, List, Text, useTheme } from 'react-native-paper'; +import React, { useEffect, useState } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { List } from "react-native-paper"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { useNavigation } from '@react-navigation/native'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from "@react-navigation/native"; +import { NativeStackNavigationProp } from "@react-navigation/native-stack"; -import { StackParamsList, Account } from '../types'; -import { addAccount } from '../utils/accounts'; -import styles from '../styles/stylesheet'; -import HDPathDialog from './HDPathDialog'; -import AccountDetails from './AccountDetails'; -import { useAccounts } from '../context/AccountsContext'; -import { useWalletConnect } from '../context/WalletConnectContext'; -import { useNetworks } from '../context/NetworksContext'; -import ConfirmDialog from './ConfirmDialog'; -import { getNamespaces } from '../utils/wallet-connect/helpers'; -import ShowPKDialog from './ShowPKDialog'; -import { setInternetCredentials } from '../utils/key-store'; +import { StackParamsList, Account } from "../types"; +import { addAccount } from "../utils/accounts"; +import HDPathDialog from "./HDPathDialog"; +import AccountDetails from "./AccountDetails"; +import { useAccounts } from "../context/AccountsContext"; +import { useWalletConnect } from "../context/WalletConnectContext"; +import { useNetworks } from "../context/NetworksContext"; +import ConfirmDialog from "./ConfirmDialog"; +import { getNamespaces } from "../utils/wallet-connect/helpers"; +import ShowPKDialog from "./ShowPKDialog"; +import { setInternetCredentials } from "../utils/key-store"; +import { + Accordion, + AccordionSummary, + Button, + Link, + Stack, +} from "@mui/material"; +import { LoadingButton } from "@mui/lab"; const Accounts = () => { const navigation = @@ -27,16 +35,14 @@ const Accounts = () => { const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } = useNetworks(); - const {web3wallet} = useWalletConnect(); + const { web3wallet } = useWalletConnect(); const [expanded, setExpanded] = useState(false); const [isAccountCreating, setIsAccountCreating] = useState(false); const [hdDialog, setHdDialog] = useState(false); - const [pathCode, setPathCode] = useState(''); + const [pathCode, setPathCode] = useState(""); const [deleteNetworkDialog, setDeleteNetworkDialog] = useState(false); - const theme = useTheme(); - const handlePress = () => setExpanded(!expanded); const hideDeleteNetworkDialog = () => setDeleteNetworkDialog(false); @@ -88,7 +94,7 @@ const Accounts = () => { }; const renderAccountItems = () => - accounts.map(account => ( + accounts.map((account) => ( { const handleRemove = async () => { const updatedNetworks = networksData.filter( - networkData => selectedNetwork!.networkId !== networkData.networkId, + (networkData) => selectedNetwork!.networkId !== networkData.networkId, ); await setInternetCredentials( - 'networks', - '_', + "networks", + "_", JSON.stringify(updatedNetworks), ); @@ -125,94 +131,74 @@ const Accounts = () => { updateAccounts={updateAccounts} pathCode={pathCode} /> - + + } + >{`Account ${currentIndex + 1}`} {renderAccountItems()} - + - - - + disabled={isAccountCreating} + > + {isAccountCreating ? "Adding" : "Add Account"} + - - + - - - { - navigation.navigate('SignMessage', { - selectedNamespace: selectedNetwork!.namespace, - selectedChainId: selectedNetwork!.chainId, - accountInfo: accounts[currentIndex], - }); - }}> - - Sign Message - - - + + { + navigation.navigate("SignMessage", { + selectedNamespace: selectedNetwork!.namespace, + selectedChainId: selectedNetwork!.chainId, + accountInfo: accounts[currentIndex], + }); + }} + > + Sign Message + - - { - navigation.navigate('AddNetwork'); - }}> - - Add Network - - - + { + navigation.navigate("AddNetwork"); + }} + > + Add Network + - - { - navigation.navigate('EditNetwork', { - selectedNetwork: selectedNetwork!, - }); - }}> - - Edit Network - - - + { + navigation.navigate("EditNetwork", { + selectedNetwork: selectedNetwork!, + }); + }} + > + Edit Network + {!selectedNetwork!.isDefault && ( - - { - setDeleteNetworkDialog(true); - }}> - - Delete Network - - - + { + setDeleteNetworkDialog(true); + }} + > + Delete Network + )} { onConfirm={handleRemove} /> - + ); diff --git a/src/components/ConfirmDialog.tsx b/src/components/ConfirmDialog.tsx index 31269cf..1d8676b 100644 --- a/src/components/ConfirmDialog.tsx +++ b/src/components/ConfirmDialog.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button, Typography } from '@mui/material'; import { ResetDialogProps } from '../types'; +import styles from "../styles/stylesheet"; const ConfirmDialog = ({ title, @@ -10,15 +11,15 @@ const ConfirmDialog = ({ }: ResetDialogProps) => { return ( - {title} - - Are you sure? + {title} + + Are you sure? - - - + ); diff --git a/src/components/Container.tsx b/src/components/Container.tsx new file mode 100644 index 0000000..76c737b --- /dev/null +++ b/src/components/Container.tsx @@ -0,0 +1,21 @@ +import { Box, BoxProps } from "@mui/material"; +import React, { PropsWithChildren } from "react"; + +export const Container: React.FC< + PropsWithChildren<{ boxProps?: BoxProps }> +> = ({ children, boxProps = {} }) => ( + + {children} + +); diff --git a/src/components/CreateWallet.tsx b/src/components/CreateWallet.tsx index 584377c..0e91e6d 100644 --- a/src/components/CreateWallet.tsx +++ b/src/components/CreateWallet.tsx @@ -1,9 +1,8 @@ -import React from 'react'; -import { View } from 'react-native'; -import { Button } from 'react-native-paper'; +import React from "react"; +import { View } from "react-native"; +import { LoadingButton } from "@mui/lab"; -import { CreateWalletProps } from '../types'; -import styles from '../styles/stylesheet'; +import { CreateWalletProps } from "../types"; const CreateWallet = ({ isWalletCreating, @@ -11,14 +10,15 @@ const CreateWallet = ({ }: CreateWalletProps) => { return ( - - + onClick={createWalletHandler} + > + {isWalletCreating ? "Creating" : "CREATE WALLET"} + ); diff --git a/src/components/Dialog.tsx b/src/components/Dialog.tsx new file mode 100644 index 0000000..9fa3da5 --- /dev/null +++ b/src/components/Dialog.tsx @@ -0,0 +1,47 @@ +import React from "react"; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Button, + Typography, +} from "@mui/material"; + +import styles from "../styles/stylesheet"; +import GridView from "./Grid"; +import { CustomDialogProps } from "../types"; + +const DialogComponent = ({ + visible, + hideDialog, + contentText, +}: CustomDialogProps) => { + const words = contentText.split(" "); + + const copyMnemonic = () => { + navigator.clipboard.writeText(contentText); + }; + + return ( + + Mnemonic + + + Your mnemonic provides full access to your wallet and funds.
+ Make sure to note it down. +
+ + Do not share your mnemonic with anyone + + +
+ + + + +
+ ); +}; + +export { DialogComponent }; diff --git a/src/components/Grid.tsx b/src/components/Grid.tsx index 764e51c..7e32aa2 100644 --- a/src/components/Grid.tsx +++ b/src/components/Grid.tsx @@ -7,7 +7,7 @@ import { GridViewProps } from '../types'; const GridView = ({ words }: GridViewProps) => { return ( - + {words.map((word, index) => ( {index + 1}. diff --git a/src/components/HDPath.tsx b/src/components/HDPath.tsx index cc38bef..185b33c 100644 --- a/src/components/HDPath.tsx +++ b/src/components/HDPath.tsx @@ -1,11 +1,12 @@ -import React, { useState } from 'react'; -import { ScrollView, View, Text } from 'react-native'; -import { Button, TextInput } from 'react-native-paper'; +import React, { useState } from "react"; +import { ScrollView, View, Text } from "react-native"; +import { TextInput } from "react-native-paper"; -import { addAccountFromHDPath } from '../utils/accounts'; -import { Account, NetworksDataState, PathState } from '../types'; -import styles from '../styles/stylesheet'; -import { useAccounts } from '../context/AccountsContext'; +import { addAccountFromHDPath } from "../utils/accounts"; +import { Account, NetworksDataState, PathState } from "../types"; +import styles from "../styles/stylesheet"; +import { useAccounts } from "../context/AccountsContext"; +import { LoadingButton } from "@mui/lab"; const HDPath = ({ pathCode, @@ -21,19 +22,19 @@ const HDPath = ({ const { setCurrentIndex } = useAccounts(); const [isAccountCreating, setIsAccountCreating] = useState(false); const [path, setPath] = useState({ - firstNumber: '', - secondNumber: '', - thirdNumber: '', + firstNumber: "", + secondNumber: "", + thirdNumber: "", }); const handleChange = (key: keyof PathState, value: string) => { - if (key === 'secondNumber' && value !== '' && !['0', '1'].includes(value)) { + if (key === "secondNumber" && value !== "" && !["0", "1"].includes(value)) { return; } setPath({ ...path, - [key]: value.replace(/[^0-9]/g, ''), + [key]: value.replace(/[^0-9]/g, ""), }); }; @@ -50,7 +51,7 @@ const HDPath = ({ hideDialog(); } } catch (error) { - console.error('Error creating account:', error); + console.error("Error creating account:", error); } finally { setIsAccountCreating(false); } @@ -63,15 +64,15 @@ const HDPath = ({ handleChange('firstNumber', text)} + onChangeText={(text) => handleChange("firstNumber", text)} value={path.firstNumber} style={styles.HDtextInput} /> - {'\'/'} + {"'/"} handleChange('secondNumber', text)} + onChangeText={(text) => handleChange("secondNumber", text)} value={path.secondNumber} style={styles.HDtextInput} /> @@ -79,19 +80,20 @@ const HDPath = ({ handleChange('thirdNumber', text)} + onChangeText={(text) => handleChange("thirdNumber", text)} value={path.thirdNumber} style={styles.HDtextInput} /> - + disabled={isAccountCreating} + > + {isAccountCreating ? "Adding" : "Add Account"} + ); diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..edb309e --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,113 @@ +import { + Button, + Divider, + Link, + Stack, + SvgIcon, + Typography, +} from "@mui/material"; +import React from "react"; +import { useNavigation } from "@react-navigation/native"; +import { Image } from "react-native"; +import { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { StackParamsList } from "../types"; +import styles from "../styles/stylesheet"; + +const WCLogo = () => { + return ( + + ); +}; + +export const Header: React.FC<{ + title: string; + showWalletConnect?: boolean; +}> = ({ title, showWalletConnect }) => { + const navigation = + useNavigation>(); + return ( + + + + + + {title} + + + + {showWalletConnect && ( + + )} + + ); +}; diff --git a/src/components/ImportWalletDialog.tsx b/src/components/ImportWalletDialog.tsx index f192eb7..e081035 100644 --- a/src/components/ImportWalletDialog.tsx +++ b/src/components/ImportWalletDialog.tsx @@ -1,17 +1,26 @@ -import React, { useEffect, useState } from 'react'; - -import { Dialog, DialogActions, DialogContent, DialogTitle, TextField, Grid, Button, Typography } from "@mui/material"; +import React, { useEffect, useState } from "react"; +import styles from "../styles/stylesheet"; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + TextField, + Grid, + Button, + Typography, +} from "@mui/material"; const ImportWalletDialog = ({ visible, hideDialog, - importWalletHandler + importWalletHandler, }: { visible: boolean; hideDialog: () => void; importWalletHandler: (recoveryPhrase: string) => Promise; }) => { - const [words, setWords] = useState(Array(12).fill('')); + const [words, setWords] = useState(Array(12).fill("")); const handleWordChange = (index: number, value: string) => { const newWords = [...words]; @@ -20,7 +29,7 @@ const ImportWalletDialog = ({ }; const handlePaste = (event: React.ClipboardEvent) => { - const pastedText = event.clipboardData.getData('Text'); + const pastedText = event.clipboardData.getData("Text"); const splitWords = pastedText.trim().split(/\s+/); if (splitWords.length === 12) { @@ -30,15 +39,17 @@ const ImportWalletDialog = ({ }; useEffect(() => { - setWords(Array(12).fill('')); - },[visible]); + setWords(Array(12).fill("")); + }, [visible]); return ( - Import your wallet from your mnemonic - - - (You can paste your entire mnemonic into the first textbox) + + Import your wallet from your mnemonic + + + + (You can paste your entire mnemonic into the first textbox) {words.map((word, index) => ( @@ -55,9 +66,16 @@ const ImportWalletDialog = ({ ))} - - - + + + ); diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx new file mode 100644 index 0000000..7bada61 --- /dev/null +++ b/src/components/Layout.tsx @@ -0,0 +1,34 @@ +import { Button, Typography } from "@mui/material"; +import React, { PropsWithChildren } from "react"; +import { Container } from "./Container"; +import { ArrowBack } from "@mui/icons-material"; +import { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { useNavigation } from "@react-navigation/native"; +import { StackParamsList } from "../types"; + +export const Layout: React.FC> = ({ + children, + title, +}) => { + const navigation = + useNavigation>(); + + return ( + + + + {title} + + {children} + + ); +}; diff --git a/src/components/MnemonicDialog.tsx b/src/components/MnemonicDialog.tsx index 1e10297..6086dc6 100644 --- a/src/components/MnemonicDialog.tsx +++ b/src/components/MnemonicDialog.tsx @@ -1,32 +1,48 @@ -import React from 'react'; -import { Dialog, DialogActions, DialogContent, DialogTitle, Button, Typography } from '@mui/material'; +import React from "react"; +import { + Dialog, + DialogActions, + DialogContent, + DialogTitle, + Button, + Typography, +} from "@mui/material"; -import styles from '../styles/stylesheet'; -import GridView from './Grid'; -import { CustomDialogProps } from '../types'; +import styles from "../styles/stylesheet"; +import GridView from "./Grid"; +import { CustomDialogProps } from "../types"; -const MnemonicDialog = ({ visible, hideDialog, contentText }: CustomDialogProps) => { - const words = contentText.split(' '); +const MnemonicDialog = ({ + visible, + hideDialog, + contentText, +}: CustomDialogProps) => { + const words = contentText.split(" "); const copyMnemonic = () => { - navigator.clipboard.writeText(contentText) + navigator.clipboard.writeText(contentText); }; return ( - Mnemonic - - - Your mnemonic provides full access to your wallet and funds. Make sure to note it down. + Mnemonic + + + Your mnemonic provides full access to your wallet and funds.
+ Make sure to note it down.
- + Do not share your mnemonic with anyone
- - - + + +
); diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index 12f7e76..703c387 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -1,10 +1,12 @@ -import React, { useState } from 'react'; -import { View } from 'react-native'; -import { List } from 'react-native-paper'; +import React, { useState } from "react"; +import { View } from "react-native"; +import { List } from "react-native-paper"; +import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; -import { NetworkDropdownProps, NetworksDataState } from '../types'; -import styles from '../styles/stylesheet'; -import { useNetworks } from '../context/NetworksContext'; +import { NetworkDropdownProps, NetworksDataState } from "../types"; +import styles from "../styles/stylesheet"; +import { useNetworks } from "../context/NetworksContext"; +import { Accordion, AccordionSummary } from "@mui/material"; const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { const { networksData, selectedNetwork, setSelectedNetwork } = useNetworks(); @@ -19,18 +21,19 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { return ( - setExpanded(!expanded)}> - {networksData.map(networkData => ( + setExpanded(!expanded)}> + }> + {selectedNetwork!.networkName} + + + {networksData.map((networkData) => ( handleNetworkPress(networkData)} /> ))} - + ); }; diff --git a/src/components/ShowPKDialog.tsx b/src/components/ShowPKDialog.tsx index b733c81..733613b 100644 --- a/src/components/ShowPKDialog.tsx +++ b/src/components/ShowPKDialog.tsx @@ -1,16 +1,15 @@ -import React, { useState } from 'react'; -import { TouchableOpacity, View } from 'react-native'; -import { Button, Typography } from '@mui/material'; -import Dialog from '@mui/material/Dialog'; -import DialogTitle from '@mui/material/DialogTitle'; -import DialogContent from '@mui/material/DialogContent'; -import DialogActions from '@mui/material/DialogActions'; +import React, { useState } from "react"; +import { TouchableOpacity, View } from "react-native"; +import { Button, Link, Typography } from "@mui/material"; +import Dialog from "@mui/material/Dialog"; +import DialogTitle from "@mui/material/DialogTitle"; +import DialogContent from "@mui/material/DialogContent"; +import DialogActions from "@mui/material/DialogActions"; -import styles from '../styles/stylesheet'; -import { getPathKey } from '../utils/misc'; -import { useNetworks } from '../context/NetworksContext'; -import { useAccounts } from '../context/AccountsContext'; -import { Text, useTheme } from 'react-native-paper'; +import styles from "../styles/stylesheet"; +import { getPathKey } from "../utils/misc"; +import { useNetworks } from "../context/NetworksContext"; +import { useAccounts } from "../context/AccountsContext"; const ShowPKDialog = () => { const { currentIndex } = useAccounts(); @@ -19,8 +18,6 @@ const ShowPKDialog = () => { const [privateKey, setPrivateKey] = useState(); const [showPKDialog, setShowPKDialog] = useState(false); - const theme = useTheme(); - const handleShowPrivateKey = async () => { const pathKey = await getPathKey( `${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`, @@ -41,12 +38,9 @@ const ShowPKDialog = () => { { setShowPKDialog(true); - }}> - - Show Private Key - + }} + > + Show Private Key
@@ -66,8 +60,8 @@ const ShowPKDialog = () => { variant="body1" style={styles.dataBoxData} sx={{ - wordWrap: 'break-word', - whiteSpace: 'initial', + wordWrap: "break-word", + whiteSpace: "initial" }} > {privateKey} @@ -76,24 +70,19 @@ const ShowPKDialog = () => { )} - - Warning: - - Never disclose this key. Anyone with your private keys can - steal any assets held in your account. + Warning: Never disclose this key. Anyone with your private keys can steal + any assets held in your account. {!privateKey ? ( <> - - + + ) : ( - + )} diff --git a/src/index.tsx b/src/index.tsx index 087f562..61a4189 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,59 +1,141 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { PaperProvider, MD3LightTheme as DefaultTheme, } from 'react-native-paper'; -import { NavigationContainer } from '@react-navigation/native'; -import { Platform } from 'react-native'; -import { Buffer } from 'buffer'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { + PaperProvider, + MD3DarkTheme as DefaultTheme, +} from "react-native-paper"; +import { NavigationContainer, DarkTheme } from "@react-navigation/native"; +import { Platform } from "react-native"; +import { Buffer } from "buffer"; -import './index.css'; -import App from './App'; -import { AccountsProvider } from './context/AccountsContext'; -import { NetworksProvider } from './context/NetworksContext'; -import reportWebVitals from './reportWebVitals'; -import { WalletConnectProvider } from './context/WalletConnectContext'; +import "./index.css"; +import App from "./App"; +import { AccountsProvider } from "./context/AccountsContext"; +import { NetworksProvider } from "./context/NetworksContext"; +import reportWebVitals from "./reportWebVitals"; +import { WalletConnectProvider } from "./context/WalletConnectContext"; +import { createTheme, ThemeProvider } from "@mui/material"; globalThis.Buffer = Buffer; const linking = { - prefixes: ['https://wallet.laconic.com'] + prefixes: ["https://wallet.laconic.com"], }; const theme = { ...DefaultTheme, - dark: false, + dark: true, }; +const navigationTheme: typeof DarkTheme = { + ...DarkTheme, + colors: { + ...DarkTheme.colors, + primary: "#0000F4", + background: "#0F0F0F", + card: "#18181A", + }, +}; + +const muiTheme = createTheme({ + components: { + MuiAccordion: { + defaultProps: { + sx: { + border: "1px solid #48474F", + borderBottomRightRadius: 3, + borderBottomLeftRadius: 3, + }, + }, + }, + MuiButton: { + defaultProps: { + color: "primary", + sx: { + fontFamily: `DM Mono, monospace`, + fontWeight: 400, + }, + }, + }, + MuiLink: { + defaultProps: { + color: "text.primary", + fontSize: "14px", + }, + }, + MuiTypography: { + defaultProps: { + color: "text.primary", + fontWeight: 400, + }, + }, + MuiPaper: { + defaultProps: { + sx: { + backgroundImage: "none", + }, + }, + }, + }, + palette: { + mode: "dark", + primary: { + main: "#0000F4", + }, + secondary: { + main: "#A2A2FF", + }, + error: { + main: "#B20710", + }, + background: { + default: "#0F0F0F", + paper: "#18181A", + }, + text: { + primary: "#FBFBFB", + }, + info: { + main: "#FBFBFB", + }, + }, +}); + const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement + document.getElementById("root") as HTMLElement, ); root.render( - - - - - - `Laconic Wallet`, - }} - > - - {Platform.OS === 'web' ? ( - - ) : null} - - - - - - - + ) : null} + + + + + + + + + +
, ); // If you want to start measuring performance in your app, pass a function diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 37fd0e9..ee0cd68 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -1,21 +1,20 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { View } from 'react-native'; -import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form'; -import { TextInput, Button, HelperText } from 'react-native-paper'; +import React, { useCallback, useEffect, useState } from "react"; +import { useForm, Controller, useWatch, FieldErrors } from "react-hook-form"; +import { TextInput, HelperText } from "react-native-paper"; -import { HDNode } from 'ethers/lib/utils'; -import { chains } from 'chain-registry'; -import { useDebouncedCallback } from 'use-debounce'; -import { z } from 'zod'; +import { HDNode } from "ethers/lib/utils"; +import { chains } from "chain-registry"; +import { useDebouncedCallback } from "use-debounce"; +import { z } from "zod"; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { useNavigation } from '@react-navigation/native'; -import { zodResolver } from '@hookform/resolvers/zod'; +import { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { useNavigation } from "@react-navigation/native"; +import { zodResolver } from "@hookform/resolvers/zod"; -import { StackParamsList } from '../types'; -import { SelectNetworkType } from '../components/SelectNetworkType'; -import { storeNetworkData } from '../utils/accounts'; -import { useNetworks } from '../context/NetworksContext'; +import { StackParamsList } from "../types"; +import { SelectNetworkType } from "../components/SelectNetworkType"; +import { storeNetworkData } from "../utils/accounts"; +import { useNetworks } from "../context/NetworksContext"; import { COSMOS, EIP155, @@ -23,14 +22,16 @@ import { EMPTY_FIELD_ERROR, INVALID_URL_ERROR, IS_NUMBER_REGEX, -} from '../utils/constants'; -import { getCosmosAccounts } from '../utils/accounts'; -import ETH_CHAINS from '../assets/ethereum-chains.json'; +} from "../utils/constants"; +import { getCosmosAccounts } from "../utils/accounts"; +import ETH_CHAINS from "../assets/ethereum-chains.json"; import { getInternetCredentials, setInternetCredentials, -} from '../utils/key-store'; -import styles from '../styles/stylesheet'; +} from "../utils/key-store"; +import { Divider, Grid } from "@mui/material"; +import { LoadingButton } from "@mui/lab"; +import { Layout } from "../components/Layout"; const ethNetworkDataSchema = z.object({ chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }), @@ -39,7 +40,7 @@ const ethNetworkDataSchema = z.object({ blockExplorerUrl: z .string() .url({ message: INVALID_URL_ERROR }) - .or(z.literal('')), + .or(z.literal("")), coinType: z .string() .nonempty({ message: EMPTY_FIELD_ERROR }) @@ -54,7 +55,7 @@ const cosmosNetworkDataSchema = z.object({ blockExplorerUrl: z .string() .url({ message: INVALID_URL_ERROR }) - .or(z.literal('')), + .or(z.literal("")), coinType: z .string() .nonempty({ message: EMPTY_FIELD_ERROR }) @@ -85,13 +86,13 @@ const AddNetwork = () => { setValue, reset, } = useForm>({ - mode: 'onChange', + mode: "onChange", resolver: zodResolver(networksFormDataSchema), }); const watchChainId = useWatch({ control, - name: 'chainId', + name: "chainId", }); const updateNetworkType = (newNetworkType: string) => { @@ -101,16 +102,16 @@ const AddNetwork = () => { const fetchChainDetails = useDebouncedCallback((chainId: string) => { if (namespace === EIP155) { const ethChainDetails = ETH_CHAINS.find( - chain => chain.chainId === Number(chainId), + (chain) => chain.chainId === Number(chainId), ); if (!ethChainDetails) { return; } - setValue('networkName', ethChainDetails.name); - setValue('rpcUrl', ethChainDetails.rpc[0]); - setValue('blockExplorerUrl', ethChainDetails.explorers?.[0].url || ''); - setValue('coinType', String(ethChainDetails.slip44 ?? '60')); - setValue('currencySymbol', ethChainDetails.nativeCurrency.symbol); + setValue("networkName", ethChainDetails.name); + setValue("rpcUrl", ethChainDetails.rpc[0]); + setValue("blockExplorerUrl", ethChainDetails.explorers?.[0].url || ""); + setValue("coinType", String(ethChainDetails.slip44 ?? "60")); + setValue("currencySymbol", ethChainDetails.nativeCurrency.symbol); return; } const cosmosChainDetails = chains.find( @@ -119,14 +120,14 @@ const AddNetwork = () => { if (!cosmosChainDetails) { return; } - setValue('networkName', cosmosChainDetails.pretty_name); - setValue('rpcUrl', cosmosChainDetails.apis?.rpc?.[0]?.address || ''); - setValue('blockExplorerUrl', cosmosChainDetails.explorers?.[0].url || ''); - setValue('addressPrefix', cosmosChainDetails.bech32_prefix); - setValue('coinType', String(cosmosChainDetails.slip44 ?? '118')); - setValue('nativeDenom', cosmosChainDetails.fees?.fee_tokens[0].denom || ''); + setValue("networkName", cosmosChainDetails.pretty_name); + setValue("rpcUrl", cosmosChainDetails.apis?.rpc?.[0]?.address || ""); + setValue("blockExplorerUrl", cosmosChainDetails.explorers?.[0].url || ""); + setValue("addressPrefix", cosmosChainDetails.bech32_prefix); + setValue("coinType", String(cosmosChainDetails.slip44 ?? "118")); + setValue("nativeDenom", cosmosChainDetails.fees?.fee_tokens[0].denom || ""); setValue( - 'gasPrice', + "gasPrice", String( cosmosChainDetails.fees?.fee_tokens[0].average_gas_price || String(process.env.DEFAULT_GAS_PRICE), @@ -142,11 +143,11 @@ const AddNetwork = () => { isDefault: false, }; - const mnemonicServer = await getInternetCredentials('mnemonicServer'); + const mnemonicServer = await getInternetCredentials("mnemonicServer"); const mnemonic = mnemonicServer; if (!mnemonic) { - throw new Error('Mnemonic not found'); + throw new Error("Mnemonic not found"); } const hdNode = HDNode.fromMnemonic(mnemonic); @@ -172,7 +173,7 @@ const AddNetwork = () => { break; default: - throw new Error('Unsupported namespace'); + throw new Error("Unsupported namespace"); } const accountInfo = `${hdPath},${node.privateKey},${node.publicKey},${address}`; @@ -183,22 +184,22 @@ const AddNetwork = () => { await Promise.all([ setInternetCredentials( `accounts/${newNetworkData.namespace}:${newNetworkData.chainId}/0`, - '_', + "_", accountInfo, ), setInternetCredentials( `addAccountCounter/${newNetworkData.namespace}:${newNetworkData.chainId}`, - '_', - '1', + "_", + "1", ), setInternetCredentials( `accountIndices/${newNetworkData.namespace}:${newNetworkData.chainId}`, - '_', - '0', + "_", + "0", ), ]); - navigation.navigate('Home'); + navigation.navigate("Home"); }, [navigation, namespace, setNetworksData], ); @@ -212,208 +213,237 @@ const AddNetwork = () => { }, [namespace, reset]); return ( - + + - ( - <> - onChange(textValue)} - /> - {errors.chainId?.message} - - )} - /> - ( - <> - onChange(textValue)} - /> - {errors.networkName?.message} - - )} - /> - ( - <> - onChange(textValue)} - /> - {errors.rpcUrl?.message} - - )} - /> - - ( - <> - onChange(textValue)} - /> - - {errors.blockExplorerUrl?.message} - - - )} - /> - ( - <> - - {errors.coinType?.message} - - )} - /> - {namespace === EIP155 ? ( - ( - <> - onChange(textValue)} - /> - - { - (errors as FieldErrors>) - .currencySymbol?.message - } - - - )} - /> - ) : ( - <> + + ( <> onChange(textValue)} + onChangeText={(textValue) => onChange(textValue)} + /> + {errors.chainId?.message} + + )} + /> + + + + ( + <> + onChange(textValue)} /> - { - ( - errors as FieldErrors< - z.infer - > - ).nativeDenom?.message - } + {errors.networkName?.message} )} /> + + + ( <> onChange(textValue)} + value={value} + onChangeText={(textValue) => onChange(textValue)} + /> + {errors.rpcUrl?.message} + + )} + /> + + + + ( + <> + onChange(textValue)} /> - { - ( - errors as FieldErrors< - z.infer - > - ).addressPrefix?.message - } + {errors.blockExplorerUrl?.message} )} /> + + ( <> - - { - ( - errors as FieldErrors< - z.infer - > - ).gasPrice?.message - } - + {errors.coinType?.message} )} /> - - )} - - + onClick={handleSubmit(submit)} + sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }} + > + {isSubmitting ? "Adding" : "Submit"} + + ); }; diff --git a/src/screens/AddSession.tsx b/src/screens/AddSession.tsx index 55fa2b3..430cccb 100644 --- a/src/screens/AddSession.tsx +++ b/src/screens/AddSession.tsx @@ -1,22 +1,24 @@ -import React, { useCallback, useState } from 'react'; -import { View } from 'react-native'; -import { Button, Text, TextInput } from 'react-native-paper'; +import React, { useCallback, useState } from "react"; +import { View } from "react-native"; +import { Text, TextInput } from "react-native-paper"; -import { useNavigation } from '@react-navigation/native'; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; +import { useNavigation } from "@react-navigation/native"; +import { NativeStackNavigationProp } from "@react-navigation/native-stack"; +import { Box, Button } from "@mui/material"; -import { web3WalletPair } from '../utils/wallet-connect/WalletConnectUtils'; -import styles from '../styles/stylesheet'; -import { StackParamsList } from '../types'; -import { useWalletConnect } from '../context/WalletConnectContext'; +import { web3WalletPair } from "../utils/wallet-connect/WalletConnectUtils"; +import styles from "../styles/stylesheet"; +import { StackParamsList } from "../types"; +import { useWalletConnect } from "../context/WalletConnectContext"; +import { Layout } from "../components/Layout"; const AddSession = () => { const navigation = useNavigation>(); - const [currentWCURI, setCurrentWCURI] = useState(''); + const [currentWCURI, setCurrentWCURI] = useState(""); - const {web3wallet} = useWalletConnect(); + const { web3wallet } = useWalletConnect(); const pair = useCallback(async () => { if (!web3wallet) { @@ -24,12 +26,12 @@ const AddSession = () => { } const pairing = await web3WalletPair(web3wallet, { uri: currentWCURI }); - navigation.navigate('WalletConnect'); + navigation.navigate("WalletConnect"); return pairing; }, [currentWCURI, navigation, web3wallet]); return ( - + Enter WalletConnect URI { style={styles.walletConnectUriText} /> - - - + - + ); }; export default AddSession; diff --git a/src/screens/EditNetwork.tsx b/src/screens/EditNetwork.tsx index c721208..e82482a 100644 --- a/src/screens/EditNetwork.tsx +++ b/src/screens/EditNetwork.tsx @@ -1,27 +1,28 @@ -import React, { useCallback } from 'react'; -import { View } from 'react-native'; -import { useForm, Controller, FieldErrors } from 'react-hook-form'; -import { TextInput, Button, HelperText, Text } from 'react-native-paper'; -import { z } from 'zod'; +import React, { useCallback } from "react"; +import { useForm, Controller, FieldErrors } from "react-hook-form"; +import { TextInput, HelperText } from "react-native-paper"; +import { z } from "zod"; -import { zodResolver } from '@hookform/resolvers/zod'; +import { zodResolver } from "@hookform/resolvers/zod"; import { NativeStackNavigationProp, NativeStackScreenProps, -} from '@react-navigation/native-stack'; -import { useNavigation } from '@react-navigation/native'; +} from "@react-navigation/native-stack"; +import { useNavigation } from "@react-navigation/native"; -import { setInternetCredentials } from '../utils/key-store'; -import { StackParamsList } from '../types'; -import styles from '../styles/stylesheet'; -import { retrieveNetworksData } from '../utils/accounts'; -import { useNetworks } from '../context/NetworksContext'; +import { setInternetCredentials } from "../utils/key-store"; +import { StackParamsList } from "../types"; +import { retrieveNetworksData } from "../utils/accounts"; +import { useNetworks } from "../context/NetworksContext"; import { COSMOS, EIP155, EMPTY_FIELD_ERROR, INVALID_URL_ERROR, -} from '../utils/constants'; +} from "../utils/constants"; +import { Divider, Grid, Typography } from "@mui/material"; +import { LoadingButton } from "@mui/lab"; +import { Layout } from "../components/Layout"; const ethNetworksFormSchema = z.object({ // Adding type field for resolving typescript error @@ -31,7 +32,7 @@ const ethNetworksFormSchema = z.object({ blockExplorerUrl: z .string() .url({ message: INVALID_URL_ERROR }) - .or(z.literal('')), + .or(z.literal("")), }); const cosmosNetworksFormDataSchema = z.object({ @@ -41,14 +42,14 @@ const cosmosNetworksFormDataSchema = z.object({ blockExplorerUrl: z .string() .url({ message: INVALID_URL_ERROR }) - .or(z.literal('')), + .or(z.literal("")), gasPrice: z .string() .nonempty({ message: EMPTY_FIELD_ERROR }) .regex(/^\d+(\.\d+)?$/), }); -type EditNetworkProps = NativeStackScreenProps; +type EditNetworkProps = NativeStackScreenProps; const EditNetwork = ({ route }: EditNetworkProps) => { const { setNetworksData } = useNetworks(); @@ -67,7 +68,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => { formState: { errors, isSubmitting }, handleSubmit, } = useForm>({ - mode: 'onChange', + mode: "onChange", resolver: zodResolver(networksFormDataSchema), }); @@ -77,121 +78,134 @@ const EditNetwork = ({ route }: EditNetworkProps) => { const { type, ...dataWithoutType } = data; const newNetworkData = { ...networkData, ...dataWithoutType }; const index = retrievedNetworksData.findIndex( - network => network.networkId === networkData.networkId, + (network) => network.networkId === networkData.networkId, ); retrievedNetworksData.splice(index, 1, newNetworkData); await setInternetCredentials( - 'networks', - '_', + "networks", + "_", JSON.stringify(retrievedNetworksData), ); setNetworksData(retrievedNetworksData); - navigation.navigate('Home'); + navigation.navigate("Home"); }, [networkData, navigation, setNetworksData], ); + const isCosmos = networkData.namespace === COSMOS; return ( - - - - Edit {networkData?.networkName} details - - - ( - <> - onChange(textValue)} - /> - {errors.networkName?.message} - - )} - /> - ( - <> - onChange(textValue)} - /> - {errors.rpcUrl?.message} - - )} - /> + + + Edit {networkData?.networkName} details + + + + + ( + <> + onChange(textValue)} + /> + + {errors.networkName?.message} + + + )} + /> + + + ( + <> + onChange(textValue)} + /> + {errors.rpcUrl?.message} + + )} + /> + - ( - <> - onChange(textValue)} + + ( + <> + onChange(textValue)} + /> + + {errors.blockExplorerUrl?.message} + + + )} + /> + + {isCosmos && ( + + ( + <> + + + { + ( + errors as FieldErrors< + z.infer + > + ).gasPrice?.message + } + + + )} /> - - {errors.blockExplorerUrl?.message} - - + )} - /> - {networkData.namespace === COSMOS && ( - ( - <> - - - { - ( - errors as FieldErrors< - z.infer - > - ).gasPrice?.message - } - - - )} - /> - )} - - + onClick={handleSubmit(submit)} + sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }} + > + {isSubmitting ? "Adding" : "Submit"} + + ); }; diff --git a/src/screens/HomeScreen.tsx b/src/screens/HomeScreen.tsx index a40f5de..ca4ce78 100644 --- a/src/screens/HomeScreen.tsx +++ b/src/screens/HomeScreen.tsx @@ -1,70 +1,41 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { View, ActivityIndicator, Image } from 'react-native'; -import { Button, Text } from 'react-native-paper'; +import React, { useCallback, useEffect, useState } from "react"; +import { View, ActivityIndicator } from "react-native"; +import { Text } from "react-native-paper"; +import { Button, Divider, Portal, Snackbar } from "@mui/material"; -import { NativeStackNavigationProp } from '@react-navigation/native-stack'; -import { useNavigation } from '@react-navigation/native'; -import { getSdkError } from '@walletconnect/utils'; -import { Portal, Snackbar } from '@mui/material'; +import { getSdkError } from "@walletconnect/utils"; -import { createWallet, resetWallet, retrieveAccounts } from '../utils/accounts'; -import { MnemonicDialog } from '../components/MnemonicDialog'; -import ImportWalletDialog from '../components/ImportWalletDialog'; -import { NetworkDropdown } from '../components/NetworkDropdown'; -import Accounts from '../components/Accounts'; -import CreateWallet from '../components/CreateWallet'; -import ConfirmDialog from '../components/ConfirmDialog'; -import styles from '../styles/stylesheet'; -import { useAccounts } from '../context/AccountsContext'; -import { useWalletConnect } from '../context/WalletConnectContext'; -import { NetworksDataState, StackParamsList } from '../types'; -import { useNetworks } from '../context/NetworksContext'; -import { IS_IMPORT_WALLET_ENABLED } from '../utils/constants'; - -const WCLogo = () => { - return ( - - ); -}; +import { createWallet, resetWallet, retrieveAccounts } from "../utils/accounts"; +import { NetworkDropdown } from "../components/NetworkDropdown"; +import Accounts from "../components/Accounts"; +import CreateWallet from "../components/CreateWallet"; +import ConfirmDialog from "../components/ConfirmDialog"; +import styles from "../styles/stylesheet"; +import { useAccounts } from "../context/AccountsContext"; +import { useWalletConnect } from "../context/WalletConnectContext"; +import { NetworksDataState } from "../types"; +import { useNetworks } from "../context/NetworksContext"; +import ImportWalletDialog from "../components/ImportWalletDialog"; +import { MnemonicDialog } from "../components/MnemonicDialog"; +import { Container } from "../components/Container"; +import { IS_IMPORT_WALLET_ENABLED } from "../utils/constants"; const HomeScreen = () => { - const { accounts, setAccounts, setCurrentIndex } = useAccounts(); + const { setAccounts, setCurrentIndex } = useAccounts(); const { networksData, selectedNetwork, setSelectedNetwork, setNetworksData } = useNetworks(); const { setActiveSessions, web3wallet } = useWalletConnect(); - const navigation = - useNavigation>(); - useEffect(() => { - if (accounts.length > 0) { - navigation.setOptions({ - // eslint-disable-next-line react/no-unstable-nested-components - headerRight: () => ( - - ), - }); - } else { - navigation.setOptions({ - headerRight: undefined, - }); - } - }, [navigation, accounts]); - const [isWalletCreated, setIsWalletCreated] = useState(false); const [isWalletCreating, setIsWalletCreating] = useState(false); const [importWalletDialog, setImportWalletDialog] = useState(false); const [mnemonicDialog, setMnemonicDialog] = useState(false); const [resetWalletDialog, setResetWalletDialog] = useState(false); const [toastVisible, setToastVisible] = useState(false); - const [invalidMnemonicError, setInvalidMnemonicError] = useState(''); + const [invalidMnemonicError, setInvalidMnemonicError] = useState(""); const [isAccountsFetched, setIsAccountsFetched] = useState(true); - const [phrase, setPhrase] = useState(''); + const [phrase, setPhrase] = useState(""); const hideMnemonicDialog = () => setMnemonicDialog(false); const hideResetDialog = () => setResetWalletDialog(false); @@ -96,7 +67,7 @@ const HomeScreen = () => { }; const importWalletHandler = async (recoveryPhrase: string) => { - try{ + try { const mnemonic = await createWallet(networksData, recoveryPhrase); if (mnemonic) { fetchAccounts(); @@ -120,10 +91,10 @@ const HomeScreen = () => { await resetWallet(); const sessions = web3wallet!.getActiveSessions(); - Object.keys(sessions).forEach(async sessionId => { + Object.keys(sessions).forEach(async (sessionId) => { await web3wallet!.disconnectSession({ topic: sessionId, - reason: getSdkError('USER_DISCONNECTED'), + reason: getSdkError("USER_DISCONNECTED"), }); }); setActiveSessions({}); @@ -135,7 +106,7 @@ const HomeScreen = () => { setCurrentIndex, setNetworksData, setSelectedNetwork, - web3wallet + web3wallet, ]); const updateNetwork = (networkData: NetworksDataState) => { @@ -149,68 +120,72 @@ const HomeScreen = () => { return ( - {!isAccountsFetched ? ( - - Loading... - - - ) : isWalletCreated && selectedNetwork ? ( - <> - - - + + {!isAccountsFetched ? ( + + Loading... + - + ) : isWalletCreated && selectedNetwork ? ( + <> + + + + + - - - ) : ( - <> - - { IS_IMPORT_WALLET_ENABLED && - - } - - )} - setImportWalletDialog(false)} - importWalletHandler={importWalletHandler} - /> - - + + ) : ( + <> + + {IS_IMPORT_WALLET_ENABLED && ( + + + + )} + + )} + setImportWalletDialog(false)} + importWalletHandler={importWalletHandler} + /> + + + setToastVisible(false)} - anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }} - ContentProps={{ style: { backgroundColor: 'red', color: 'white'} }} + anchorOrigin={{ horizontal: "center", vertical: "bottom" }} + ContentProps={{ style: { backgroundColor: "red", color: "white" } }} /> diff --git a/src/screens/SignMessage.tsx b/src/screens/SignMessage.tsx index 267103f..1377128 100644 --- a/src/screens/SignMessage.tsx +++ b/src/screens/SignMessage.tsx @@ -1,22 +1,22 @@ -import React, { useState } from 'react'; -import { View } from 'react-native'; -import { Button, Text, TextInput } from 'react-native-paper'; +import React, { useState } from "react"; +import { Text, TextInput } from "react-native-paper"; +import { Button, Divider, Stack } from "@mui/material"; -import { NativeStackScreenProps } from '@react-navigation/native-stack'; +import { NativeStackScreenProps } from "@react-navigation/native-stack"; -import { StackParamsList } from '../types'; -import styles from '../styles/stylesheet'; -import { signMessage } from '../utils/sign-message'; -import AccountDetails from '../components/AccountDetails'; +import { StackParamsList } from "../types"; +import { signMessage } from "../utils/sign-message"; +import AccountDetails from "../components/AccountDetails"; +import { Layout } from "../components/Layout"; -type SignProps = NativeStackScreenProps; +type SignProps = NativeStackScreenProps; const SignMessage = ({ route }: SignProps) => { const namespace = route.params.selectedNamespace; const chainId = route.params.selectedChainId; const account = route.params.accountInfo; - const [message, setMessage] = useState(''); + const [message, setMessage] = useState(""); const signMessageHandler = async () => { const signedMessage = await signMessage({ @@ -29,31 +29,30 @@ const SignMessage = ({ route }: SignProps) => { }; return ( - - - - - {account && `Account ${account.index + 1}`} - - - - - - + + + {account && `Account ${account.index + 1}`} + + - setMessage(text)} - value={message} - /> + + + setMessage(text)} + value={message} + /> - - - - + + ); }; diff --git a/src/screens/WalletConnect.tsx b/src/screens/WalletConnect.tsx index f581930..4728826 100644 --- a/src/screens/WalletConnect.tsx +++ b/src/screens/WalletConnect.tsx @@ -1,11 +1,12 @@ -import React, { useEffect } from 'react'; -import { Image, TouchableOpacity, View } from 'react-native'; -import { List, Text } from 'react-native-paper'; +import React, { useEffect } from "react"; +import { Image, TouchableOpacity, View } from "react-native"; +import { List, Text } from "react-native-paper"; -import { getSdkError } from '@walletconnect/utils'; +import { getSdkError } from "@walletconnect/utils"; -import { useWalletConnect } from '../context/WalletConnectContext'; -import styles from '../styles/stylesheet'; +import { useWalletConnect } from "../context/WalletConnectContext"; +import styles from "../styles/stylesheet"; +import { Layout } from "../components/Layout"; export default function WalletConnect() { const { web3wallet, activeSessions, setActiveSessions } = useWalletConnect(); @@ -13,7 +14,7 @@ export default function WalletConnect() { const disconnect = async (sessionId: string) => { await web3wallet!.disconnectSession({ topic: sessionId, - reason: getSdkError('USER_DISCONNECTED'), + reason: getSdkError("USER_DISCONNECTED"), }); const sessions = web3wallet?.getActiveSessions() || {}; setActiveSessions(sessions); @@ -26,12 +27,10 @@ export default function WalletConnect() { }, [web3wallet, setActiveSessions]); return ( - + {Object.keys(activeSessions).length > 0 ? ( <> - - Active Sessions - + {Object.entries(activeSessions).map(([sessionId, session]) => ( ( <> - {session.peer.metadata.icons[0].endsWith('.svg') ? ( + {session.peer.metadata.icons[0].endsWith(".svg") ? ( SvgURI peerMetaDataIcon @@ -60,7 +59,8 @@ export default function WalletConnect() { right={() => ( disconnect(sessionId)} - style={styles.disconnectSession}> + style={styles.disconnectSession} + > )} @@ -73,6 +73,6 @@ export default function WalletConnect() { You have no active sessions )} - + ); } diff --git a/src/styles/stylesheet.js b/src/styles/stylesheet.js index ad9b1b8..52bd51f 100644 --- a/src/styles/stylesheet.js +++ b/src/styles/stylesheet.js @@ -1,22 +1,21 @@ -import { StyleSheet } from 'react-native'; +import { StyleSheet } from "react-native"; const styles = StyleSheet.create({ createWalletContainer: { marginTop: 20, - width: 150, - alignSelf: 'center', + alignSelf: "center", marginBottom: 30, }, signLink: { - alignItems: 'flex-end', + alignItems: "flex-end", marginTop: 24, }, hyperlink: { - fontWeight: '500', - textDecorationLine: 'underline', + fontWeight: "500", + textDecorationLine: "underline", }, highlight: { - fontWeight: '700', + fontWeight: "700", }, accountContainer: { padding: 8, @@ -24,28 +23,32 @@ const styles = StyleSheet.create({ }, addAccountButton: { marginTop: 24, - alignSelf: 'center', + alignSelf: "center", }, accountComponent: { flex: 4, }, + appSurface: { + backgroundColor: "#0f0f0f", + }, appContainer: { flexGrow: 1, marginTop: 24, paddingHorizontal: 24, + backgroundColor: "#0f0f0f", }, resetContainer: { flex: 1, - justifyContent: 'center', + justifyContent: "center", }, resetButton: { - alignSelf: 'center', + alignSelf: "center", }, signButton: { marginTop: 20, marginBottom: 20, width: 150, - alignSelf: 'center', + alignSelf: "center", }, signPage: { paddingHorizontal: 24, @@ -71,33 +74,81 @@ const styles = StyleSheet.create({ borderRadius: 10, }, dialogWarning: { - color: 'red', + color: "#FFA3A8", + }, + resetDialogTitle: { + width: 500, + backgroundColor: "#18181A", + }, + resetDialogContent: { + backgroundColor: "#18181A", + }, + resetDialogActionRow: { + backgroundColor: "#18181A", + }, + button: { + color: "#fff", + margin: 10, + }, + buttonRed: { + backgroundColor: "#B20710", + }, + buttonBlue: { + backgroundColor: "#0000F4", + }, + mnemonicTitle: { + backgroundColor: "#18181A", + }, + mnemonicContainer: { + backgroundColor: "#18181A", + }, + mnemonicDialogWarning: { + color: "#FFA3A8", + marginTop: 10, + }, + mnemonicButtonRow: { + paddingRight: 40, + backgroundColor: "#18181A", + }, + mnemonicButton: { + backgroundColor: "#0000F4", + color: "white", + padding: 2, + marginBottom: 20, + }, + mnemonicGridContainer: { + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "center", + marginTop: 20, + paddingBottom: 30, + borderBottomWidth: 1, + borderBottomColor: "#29292E", }, gridContainer: { - flexDirection: 'row', - flexWrap: 'wrap', - justifyContent: 'center', + flexDirection: "row", + flexWrap: "wrap", + justifyContent: "center", }, gridItem: { - width: '25%', - margin: 8, - padding: 6, - borderWidth: 1, - borderColor: '#ccc', - borderRadius: 8, - alignItems: 'center', - justifyContent: 'flex-start', + width: "30%", + margin: 4, + padding: 4, + borderRadius: 4, + alignItems: "center", + justifyContent: "flex-start", + backgroundColor: "#29292E", }, HDcontainer: { marginTop: 24, paddingHorizontal: 8, }, HDrowContainer: { - flexDirection: 'row', - alignItems: 'center', + flexDirection: "row", + alignItems: "center", }, HDtext: { - color: 'black', + color: "#FBFBFB", fontSize: 18, margin: 4, }, @@ -107,15 +158,15 @@ const styles = StyleSheet.create({ HDbuttonContainer: { marginTop: 20, width: 200, - alignSelf: 'center', + alignSelf: "flex-start", }, spinnerContainer: { flex: 1, - justifyContent: 'center', - alignItems: 'center', + justifyContent: "center", + alignItems: "center", }, LoadingText: { - color: 'black', + color: "black", fontSize: 18, padding: 10, }, @@ -123,9 +174,9 @@ const styles = StyleSheet.create({ borderWidth: 1, borderRadius: 5, marginTop: 50, - height: 'auto', - alignItems: 'center', - justifyContent: 'center', + height: "auto", + alignItems: "center", + justifyContent: "center", padding: 10, }, requestDirectMessage: { @@ -134,51 +185,51 @@ const styles = StyleSheet.create({ marginTop: 20, marginBottom: 50, height: 500, - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", padding: 8, }, approveTransfer: { - height: '40%', + height: "40%", marginBottom: 30, }, buttonContainer: { - flexDirection: 'row', + flexDirection: "row", marginLeft: 20, marginTop: 10, marginBottom: 10, - justifyContent: 'space-evenly', + justifyContent: "space-evenly", }, badRequestContainer: { - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", padding: 20, }, invalidMessageText: { - color: 'black', + color: "black", fontSize: 16, - textAlign: 'center', + textAlign: "center", marginBottom: 20, }, container: { flex: 1, - alignItems: 'center', - justifyContent: 'center', + alignItems: "center", + justifyContent: "center", marginBottom: 10, paddingHorizontal: 20, }, modalContentContainer: { - display: 'flex', - justifyContent: 'center', - alignItems: 'center', + display: "flex", + justifyContent: "center", + alignItems: "center", borderRadius: 34, borderBottomStartRadius: 0, borderBottomEndRadius: 0, borderWidth: 1, - width: '100%', - height: '50%', - position: 'absolute', - backgroundColor: 'white', + width: "100%", + height: "50%", + position: "absolute", + backgroundColor: "#0f0f0f", bottom: 0, }, modalOuterContainer: { flex: 1 }, @@ -187,32 +238,32 @@ const styles = StyleSheet.create({ height: 50, borderRadius: 8, marginVertical: 16, - overflow: 'hidden', + overflow: "hidden", }, space: { width: 50, }, flexRow: { - display: 'flex', - flexDirection: 'row', - justifyContent: 'space-between', - alignItems: 'center', + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", marginTop: 20, paddingHorizontal: 16, marginBottom: 10, }, marginVertical8: { marginVertical: 8, - textAlign: 'center', + textAlign: "center", }, subHeading: { - textAlign: 'center', + textAlign: "center", marginBottom: 10, marginTop: 10, - fontSize: 20 + fontSize: 20, }, centerText: { - textAlign: 'center', + textAlign: "center", }, messageBody: { borderWidth: 1, @@ -225,46 +276,48 @@ const styles = StyleSheet.create({ marginTop: 20, }, dappDetails: { - display: 'flex', - alignItems: 'center', + display: "flex", + alignItems: "center", }, dataBoxContainer: { marginBottom: 10, + backgroundColor: "#29292E", + border: "none", }, dataBoxLabel: { fontSize: 18, - fontWeight: 'bold', + fontWeight: "bold", marginBottom: 3, - color: 'black', + color: "black", }, dataBox: { borderWidth: 1, - borderColor: '#ccc', + borderColor: "#ccc", padding: 10, borderRadius: 5, }, dataBoxData: { fontSize: 16, - color: 'black', + color: "white", }, transactionText: { padding: 8, fontSize: 18, - fontWeight: 'bold', - color: 'black', + fontWeight: "bold", + color: "black", }, balancePadding: { padding: 8, }, noActiveSessions: { - display: 'flex', - alignItems: 'center', + display: "flex", + alignItems: "center", marginTop: 20, marginBottom: 20, }, disconnectSession: { - display: 'flex', - justifyContent: 'center', + display: "flex", + justifyContent: "center", }, sessionItem: { paddingLeft: 12, @@ -281,7 +334,7 @@ const styles = StyleSheet.create({ margin: 0, }, selectNetworkText: { - fontWeight: 'bold', + fontWeight: "bold", marginVertical: 10, }, transactionFeesInput: { marginBottom: 10 }, @@ -292,7 +345,7 @@ const styles = StyleSheet.create({ paddingVertical: 5, }, transactionLabel: { - fontWeight: '700', + fontWeight: "700", padding: 8, }, linkContainer: { @@ -301,7 +354,7 @@ const styles = StyleSheet.create({ networksButton: { marginTop: 12, marginBottom: 20, - } + }, }); export default styles; diff --git a/yarn.lock b/yarn.lock index a05bdbc..054ff5b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2125,6 +2125,33 @@ "@ethersproject/properties" "^5.7.0" "@ethersproject/strings" "^5.7.0" +"@floating-ui/core@^1.6.0": + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== + dependencies: + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/dom@^1.0.0": + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== + dependencies: + "@floating-ui/core" "^1.6.0" + "@floating-ui/utils" "^0.2.7" + +"@floating-ui/react-dom@^2.0.8": + version "2.1.1" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.1.tgz#cca58b6b04fc92b4c39288252e285e0422291fb0" + integrity sha512-4h84MJt3CHrtG18mGsXuLCHMrug49d7DFkU0RMIyshRveBeyV2hmV/pDaF2Uxtu8kgq5r46llp5E5FQiR0K2Yg== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== + "@hapi/hoek@^9.0.0", "@hapi/hoek@^9.3.0": version "9.3.0" resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.3.0.tgz#8368869dcb735be2e7f5cb7647de78e167a251fb" @@ -2557,11 +2584,44 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@mui/base@5.0.0-beta.40": + version "5.0.0-beta.40" + resolved "https://registry.yarnpkg.com/@mui/base/-/base-5.0.0-beta.40.tgz#1f8a782f1fbf3f84a961e954c8176b187de3dae2" + integrity sha512-I/lGHztkCzvwlXpjD2+SNmvNQvB4227xBXhISPjEaJUXGImOQ9f3D2Yj/T3KasSI/h0MLWy74X0J6clhPmsRbQ== + dependencies: + "@babel/runtime" "^7.23.9" + "@floating-ui/react-dom" "^2.0.8" + "@mui/types" "^7.2.14" + "@mui/utils" "^5.15.14" + "@popperjs/core" "^2.11.8" + clsx "^2.1.0" + prop-types "^15.8.1" + "@mui/core-downloads-tracker@^5.16.4": version "5.16.4" resolved "https://registry.yarnpkg.com/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.4.tgz#a34de72acd7e81fdbcc7eeb07786205e90dda148" integrity sha512-rNdHXhclwjEZnK+//3SR43YRx0VtjdHnUFhMSGYmAMJve+KiwEja/41EYh8V3pZKqF2geKyfcFUenTfDTYUR4w== +"@mui/icons-material@^5.16.7": + version "5.16.7" + resolved "https://registry.yarnpkg.com/@mui/icons-material/-/icons-material-5.16.7.tgz#e27f901af792065efc9f3d75d74a66af7529a10a" + integrity sha512-UrGwDJCXEszbDI7yV047BYU5A28eGJ79keTCP4cc74WyncuVrnurlmIRxaHL8YK+LI1Kzq+/JM52IAkNnv4u+Q== + dependencies: + "@babel/runtime" "^7.23.9" + +"@mui/lab@^5.0.0-alpha.173": + version "5.0.0-alpha.173" + resolved "https://registry.yarnpkg.com/@mui/lab/-/lab-5.0.0-alpha.173.tgz#a0f9696d93a765b48d69a7da5aaca0affa510ae8" + integrity sha512-Gt5zopIWwxDgGy/MXcp6GueD84xFFugFai4hYiXY0zowJpTVnIrTQCQXV004Q7rejJ7aaCntX9hpPJqCrioshA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/base" "5.0.0-beta.40" + "@mui/system" "^5.16.5" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.5" + clsx "^2.1.0" + prop-types "^15.8.1" + "@mui/material@^5.16.4": version "5.16.4" resolved "https://registry.yarnpkg.com/@mui/material/-/material-5.16.4.tgz#992d630637d9d38620e4937fb11d0a97965fdabf" @@ -2589,6 +2649,15 @@ "@mui/utils" "^5.16.4" prop-types "^15.8.1" +"@mui/private-theming@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/private-theming/-/private-theming-5.16.6.tgz#547671e7ae3f86b68d1289a0b90af04dfcc1c8c9" + integrity sha512-rAk+Rh8Clg7Cd7shZhyt2HGTTE5wYKNSJ5sspf28Fqm/PZ69Er9o6KX25g03/FG2dfpg5GCwZh/xOojiTfm3hw== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/utils" "^5.16.6" + prop-types "^15.8.1" + "@mui/styled-engine@^5.16.4": version "5.16.4" resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.4.tgz#a7a8c9079c307bab91ccd65ed5dd1496ddf2a3ab" @@ -2599,6 +2668,16 @@ csstype "^3.1.3" prop-types "^15.8.1" +"@mui/styled-engine@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/styled-engine/-/styled-engine-5.16.6.tgz#60110c106dd482dfdb7e2aa94fd6490a0a3f8852" + integrity sha512-zaThmS67ZmtHSWToTiHslbI8jwrmITcN93LQaR2lKArbvS7Z3iLkwRoiikNWutx9MBs8Q6okKvbZq1RQYB3v7g== + dependencies: + "@babel/runtime" "^7.23.9" + "@emotion/cache" "^11.11.0" + csstype "^3.1.3" + prop-types "^15.8.1" + "@mui/system@^5.16.4": version "5.16.4" resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.4.tgz#c03f971ed273f0ad06c69c949c05e866ad211407" @@ -2613,11 +2692,37 @@ csstype "^3.1.3" prop-types "^15.8.1" -"@mui/types@^7.2.15": +"@mui/system@^5.16.5": + version "5.16.7" + resolved "https://registry.yarnpkg.com/@mui/system/-/system-5.16.7.tgz#4583ca5bf3b38942e02c15a1e622ba869ac51393" + integrity sha512-Jncvs/r/d/itkxh7O7opOunTqbbSSzMTHzZkNLM+FjAOg+cYAZHrPDlYe1ZGKUYORwwb2XexlWnpZp0kZ4AHuA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/private-theming" "^5.16.6" + "@mui/styled-engine" "^5.16.6" + "@mui/types" "^7.2.15" + "@mui/utils" "^5.16.6" + clsx "^2.1.0" + csstype "^3.1.3" + prop-types "^15.8.1" + +"@mui/types@^7.2.14", "@mui/types@^7.2.15": version "7.2.15" resolved "https://registry.yarnpkg.com/@mui/types/-/types-7.2.15.tgz#dadd232fe9a70be0d526630675dff3b110f30b53" integrity sha512-nbo7yPhtKJkdf9kcVOF8JZHPZTmqXjJ/tI0bdWgHg5tp9AnIN4Y7f7wm9T+0SyGYJk76+GYZ8Q5XaTYAsUHN0Q== +"@mui/utils@^5.15.14", "@mui/utils@^5.16.5", "@mui/utils@^5.16.6": + version "5.16.6" + resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.6.tgz#905875bbc58d3dcc24531c3314a6807aba22a711" + integrity sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA== + dependencies: + "@babel/runtime" "^7.23.9" + "@mui/types" "^7.2.15" + "@types/prop-types" "^15.7.12" + clsx "^2.1.1" + prop-types "^15.8.1" + react-is "^18.3.1" + "@mui/utils@^5.16.4": version "5.16.4" resolved "https://registry.yarnpkg.com/@mui/utils/-/utils-5.16.4.tgz#8e50e27a630e3d8eeb3e9d3bc31cbb0e4956f5fd" @@ -13225,16 +13330,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0": - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13337,7 +13433,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13351,13 +13447,6 @@ strip-ansi@^5.0.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14654,7 +14743,7 @@ workbox-window@6.6.1: "@types/trusted-types" "^2.0.2" workbox-core "6.6.1" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -14672,15 +14761,6 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214"