Merge pull request 'Style with Laconic colors and branding' (#15) from style/laconic-colors into main

Reviewed-on: #15
This commit is contained in:
zramsay 2024-08-11 21:05:11 +00:00
commit e5c5d13c77
29 changed files with 1504 additions and 985 deletions

View File

@ -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",

18
prettier.config.js Normal file
View File

@ -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: [
"<THIRD_PARTY_MODULES>",
"^(pages|components|utils|icons|test|graphql)/(.*)$",
"^[./]",
],
};
export default config;

10
public/Logo.svg Normal file
View File

@ -0,0 +1,10 @@
<svg width="115" height="20" viewBox="0 0 115 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z" fill="#FBFBFB"/>
<path d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z" fill="#FBFBFB"/>
<path d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z" fill="#FBFBFB"/>
<path d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z" fill="#FBFBFB"/>
<path d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z" fill="#FBFBFB"/>
<path d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z" fill="#FBFBFB"/>
<path d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z" fill="#FBFBFB"/>
<path d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z" fill="#FBFBFB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -1,15 +1,18 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Laconic Wallet Web App"
/>
<meta name="description" content="Laconic Wallet Web App" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300;1,400;1,500&display=swap"
rel="stylesheet" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
@ -26,10 +29,15 @@
-->
<title>Laconic Wallet</title>
<style>
#app {
background-color: #0f0f0f;
}
body {
margin: 0;
padding: 0;
height: 100%;
background-color: #0f0f0f;
}
.loader-wrapper {
@ -61,8 +69,9 @@
}
}
</style>
</head>
<body>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
<div class="loader-wrapper">
@ -79,5 +88,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</body>
</html>

View File

@ -21,5 +21,5 @@
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
"background_color": "#0f0f0f"
}

View File

@ -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<StackParamsList>();
const App = (): React.JSX.Element => {
const navigation =
useNavigation<StackNavigationProp<StackParamsList>>();
const navigation = useNavigation<StackNavigationProp<StackParamsList>>();
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 (
<>
<Surface style={styles.appSurface}>
<Stack.Navigator
screenOptions={{
headerBackTitleVisible: true,
@ -227,7 +231,9 @@ const App = (): React.JSX.Element => {
component={HomeScreen}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Laconic Wallet</Text>,
header: () => (
<Header title="Wallet" showWalletConnect={showWalletConnect} />
),
}}
/>
<Stack.Screen
@ -235,7 +241,7 @@ const App = (): React.JSX.Element => {
component={SignMessage}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Message</Text>,
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@ -243,7 +249,7 @@ const App = (): React.JSX.Element => {
component={SignRequest}
options={{
// eslint-disable-next-line react/no-unstable-nested-components
headerTitle: () => <Text variant="titleLarge">Sign Request</Text>,
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@ -258,7 +264,7 @@ const App = (): React.JSX.Element => {
name="AddSession"
component={AddSession}
options={{
title: 'New session',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
@ -271,8 +277,9 @@ const App = (): React.JSX.Element => {
headerRight: () => (
<Button
onPress={() => {
navigation.navigate('AddSession');
}}>
navigation.navigate("AddSession");
}}
>
{<Text>PAIR</Text>}
</Button>
),
@ -282,28 +289,28 @@ const App = (): React.JSX.Element => {
name="ApproveTransfer"
component={ApproveTransfer}
options={{
title: 'Approve transfer',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="AddNetwork"
component={AddNetwork}
options={{
title: 'Add Network',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="EditNetwork"
component={EditNetwork}
options={{
title: 'Edit Network',
header: () => <Header title="Wallet" />,
}}
/>
<Stack.Screen
name="ApproveTransaction"
component={ApproveTransaction}
options={{
title: 'Approve Transaction',
header: () => <Header title="Wallet" />,
}}
/>
</Stack.Navigator>
@ -317,10 +324,11 @@ const App = (): React.JSX.Element => {
<Snackbar
visible={toastVisible}
onDismiss={() => setToastVisible(false)}
duration={3000}>
duration={3000}
>
Session approved
</Snackbar>
</>
</Surface>
);
};

View File

@ -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<AccountDetailsProps> = ({ account }) => {
return (
<View style={styles.accountContainer}>
<Text variant="bodyLarge" selectable={true}>
<Text style={styles.highlight}>Address: </Text>
{account?.address}
</Text>
<Text variant="bodyLarge" selectable={true}>
<Text style={styles.highlight}>Public Key: </Text>
{account?.pubKey}
</Text>
<Text variant="bodyLarge">
<Text style={styles.highlight}>HD Path: </Text>
{account?.hdPath}
</Text>
</View>
<Box sx={{ marginY: 4 }}>
<Stack spacing={1}>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
Address:
</Typography>
<Typography color="text.primary">{account?.address}</Typography>
</Stack>
</Stack>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
Public Key:
</Typography>
<Typography color="text.primary">{account?.pubKey}</Typography>
</Stack>
<Stack flexDirection="row">
<Typography color="secondary" sx={{ mr: 1 }}>
HD Path:
</Typography>
<Typography color="text.primary">{account?.hdPath}</Typography>
</Stack>
</Box>
);
};

View File

@ -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<boolean>(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) => (
<List.Item
key={account.index}
title={`Account ${account.index + 1}`}
@ -101,12 +107,12 @@ const Accounts = () => {
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}
/>
<List.Accordion
title={`Account ${currentIndex + 1}`}
expanded={expanded}
onPress={handlePress}>
<Accordion expanded={expanded} onClick={handlePress}>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
>{`Account ${currentIndex + 1}`}</AccordionSummary>
{renderAccountItems()}
</List.Accordion>
</Accordion>
<View style={styles.addAccountButton}>
<Button
mode="contained"
onPress={addAccountHandler}
<Stack direction="row" spacing={3} sx={{ mt: 4 }}>
<LoadingButton
variant="contained"
onClick={addAccountHandler}
loading={isAccountCreating}
disabled={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}
</Button>
</View>
disabled={isAccountCreating}
>
{isAccountCreating ? "Adding" : "Add Account"}
</LoadingButton>
<View style={styles.addAccountButton}>
<Button
mode="contained"
onPress={() => {
variant="contained"
onClick={() => {
setHdDialog(true);
setPathCode(`m/44'/${selectedNetwork!.coinType}'/`);
}}>
}}
>
Add Account from HD path
</Button>
</View>
</Stack>
<AccountDetails account={accounts[currentIndex]} />
<View style={styles.linkContainer}>
<View style={styles.signLink}>
<Stack direction="row" spacing={4}>
<TouchableOpacity
onPress={() => {
navigation.navigate('SignMessage', {
navigation.navigate("SignMessage", {
selectedNamespace: selectedNetwork!.namespace,
selectedChainId: selectedNetwork!.chainId,
accountInfo: accounts[currentIndex],
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Sign Message
</Text>
}}
>
<Link>Sign Message</Link>
</TouchableOpacity>
</View>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('AddNetwork');
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Add Network
</Text>
navigation.navigate("AddNetwork");
}}
>
<Link>Add Network</Link>
</TouchableOpacity>
</View>
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
navigation.navigate('EditNetwork', {
navigation.navigate("EditNetwork", {
selectedNetwork: selectedNetwork!,
});
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Edit Network
</Text>
}}
>
<Link>Edit Network</Link>
</TouchableOpacity>
</View>
{!selectedNetwork!.isDefault && (
<View style={styles.signLink}>
<TouchableOpacity
onPress={() => {
setDeleteNetworkDialog(true);
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Delete Network
</Text>
}}
>
<Link>Delete Network</Link>
</TouchableOpacity>
</View>
)}
<ConfirmDialog
title="Delete Network"
@ -221,7 +207,7 @@ const Accounts = () => {
onConfirm={handleRemove}
/>
<ShowPKDialog />
</View>
</Stack>
</View>
</View>
);

View File

@ -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 (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<Typography variant="body1">Are you sure?</Typography>
<DialogTitle style={{...styles.resetDialogTitle}} >{title}</DialogTitle>
<DialogContent style={{...styles.resetDialogContent}}>
<Typography>Are you sure?</Typography>
</DialogContent>
<DialogActions>
<Button color="error" onClick={onConfirm}>
<DialogActions style={{...styles.resetDialogActionRow}}>
<Button style={{...styles.buttonRed, ...styles.button}} color="error" onClick={onConfirm}>
Yes
</Button>
<Button onClick={hideDialog}>No</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideDialog}>No</Button>
</DialogActions>
</Dialog>
);

View File

@ -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 = {} }) => (
<Box
{...boxProps}
sx={{
width: "100%",
maxWidth: "752px",
marginX: "auto",
backgroundColor: "background.paper",
padding: 3,
borderRadius: 2,
...boxProps.sx,
}}
>
{children}
</Box>
);

View File

@ -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 (
<View>
<View style={styles.createWalletContainer}>
<Button
mode="contained"
<View>
<LoadingButton
variant="contained"
loading={isWalletCreating}
disabled={isWalletCreating}
onPress={createWalletHandler}>
{isWalletCreating ? 'Creating' : 'Create Wallet'}
</Button>
onClick={createWalletHandler}
>
{isWalletCreating ? "Creating" : "CREATE WALLET"}
</LoadingButton>
</View>
</View>
);

47
src/components/Dialog.tsx Normal file
View File

@ -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 (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle style={{...styles.mnemonicTitle}}>Mnemonic</DialogTitle>
<DialogContent style={{...styles.mnemonicContainer}}>
<Typography component="div">
Your mnemonic provides full access to your wallet and funds.<br/>
Make sure to note it down.
</Typography>
<Typography component="div" style={{ ...styles.mnemonicDialogWarning }}>
Do not share your mnemonic with anyone
</Typography>
<GridView words={words} />
</DialogContent>
<DialogActions style={{...styles.mnemonicButtonRow}}>
<Button style={{...styles.mnemonicButton}} onClick={copyMnemonic}>Copy</Button>
<Button style={{...styles.mnemonicButton}} onClick={hideDialog}>Done</Button>
</DialogActions>
</Dialog>
);
};
export { DialogComponent };

View File

@ -7,7 +7,7 @@ import { GridViewProps } from '../types';
const GridView = ({ words }: GridViewProps) => {
return (
<View style={styles.gridContainer}>
<View style={styles.mnemonicGridContainer}>
{words.map((word, index) => (
<View key={index} style={styles.gridItem}>
<Text>{index + 1}. </Text>

View File

@ -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<PathState>({
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 = ({
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('firstNumber', text)}
onChangeText={(text) => handleChange("firstNumber", text)}
value={path.firstNumber}
style={styles.HDtextInput}
/>
<Text style={styles.HDtext}>{'\'/'}</Text>
<Text style={styles.HDtext}>{"'/"}</Text>
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('secondNumber', text)}
onChangeText={(text) => handleChange("secondNumber", text)}
value={path.secondNumber}
style={styles.HDtextInput}
/>
@ -79,19 +80,20 @@ const HDPath = ({
<TextInput
keyboardType="numeric"
mode="outlined"
onChangeText={text => handleChange('thirdNumber', text)}
onChangeText={(text) => handleChange("thirdNumber", text)}
value={path.thirdNumber}
style={styles.HDtextInput}
/>
</View>
<View style={styles.HDbuttonContainer}>
<Button
mode="contained"
onPress={createFromHDPathHandler}
<LoadingButton
variant="contained"
onClick={createFromHDPathHandler}
loading={isAccountCreating}
disabled={isAccountCreating}>
{isAccountCreating ? 'Adding' : 'Add Account'}
</Button>
disabled={isAccountCreating}
>
{isAccountCreating ? "Adding" : "Add Account"}
</LoadingButton>
</View>
</ScrollView>
);

113
src/components/Header.tsx Normal file
View File

@ -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 (
<Image
style={styles.walletConnectLogo}
source={require("../assets/WalletConnect-Icon-Blueberry.png")}
/>
);
};
export const Header: React.FC<{
title: string;
showWalletConnect?: boolean;
}> = ({ title, showWalletConnect }) => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
return (
<Stack
direction="row"
sx={{
backgroundColor: "background.paper",
pl: 2,
alignItems: "center",
py: 1,
justifyContent: "space-between",
}}
spacing={1}
>
<Stack direction="row">
<Button
component={Link}
onClick={() => {
navigation.navigate("Home");
}}
>
<SvgIcon sx={{ height: 20, width: 100 }}>
<svg
width="115"
height="20"
viewBox="0 0 115 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M3.37388 10.5194C5.70149 8.19185 7.14225 4.97748 7.1416 1.42853C7.14246 0.94681 7.11586 0.470456 7.063 0L-0.000488281 0.000643078L-0.000273922 13.5723C-0.000917354 15.2174 0.62632 16.863 1.88091 18.1175C3.1356 19.3721 4.78235 20.0001 6.42772 19.9993L6.42729 19.9997L19.9995 20L19.999 12.9355C19.5296 12.8838 19.0532 12.857 18.5704 12.8569C15.0224 12.8574 11.8079 14.298 9.48026 16.6255C7.78654 18.2768 5.07093 18.2771 3.39812 16.6043C1.72638 14.9325 1.72562 12.2161 3.37388 10.5194ZM18.5344 1.46863C16.5837 -0.481929 13.4146 -0.48268 11.4633 1.46863C9.512 3.41984 9.51276 6.58895 11.4633 8.53941C13.415 10.491 16.5831 10.4907 18.5344 8.53941C20.4857 6.5882 20.4861 3.42016 18.5344 1.46863Z"
fill="#FBFBFB"
/>
<path
d="M31.4741 18.5838H39.2552V16.3302H34.075V1.41351H31.4741V18.5838Z"
fill="#FBFBFB"
/>
<path
d="M49.8108 1.41351H45.4976L40.9893 18.5838H43.6769L44.8039 14.2913H50.3744L51.5014 18.5838H54.3191L49.8108 1.41351ZM45.3458 12.145L47.6 3.2593H47.6866L49.8541 12.145H45.3458Z"
fill="#FBFBFB"
/>
<path
d="M62.9292 8.06885H65.9636C65.9636 3.17534 64.3813 1.07196 60.6967 1.07196C56.8169 1.07196 55.1479 3.73341 55.1479 9.97909C55.1479 16.2462 56.8169 18.9291 60.6967 18.9291C64.3813 18.9291 65.9636 16.8901 65.9853 12.1468H62.9508C62.9292 15.8599 62.474 16.7828 60.6967 16.7828C58.6593 16.7828 58.1607 15.4307 58.1824 9.97909C58.1824 4.54896 58.6809 3.19678 60.6967 3.21823C62.474 3.21823 62.9292 4.18413 62.9292 8.06885Z"
fill="#FBFBFB"
/>
<path
d="M73.7781 1.07209C77.7229 1.09364 79.4135 3.77643 79.4135 10.0007C79.4135 16.2249 77.7229 18.9078 73.7781 18.9292C69.8117 18.9507 68.1211 16.2678 68.1211 10.0007C68.1211 3.73354 69.8117 1.05064 73.7781 1.07209ZM71.1555 10.0007C71.1555 15.4308 71.6757 16.783 73.7781 16.783C75.8589 16.783 76.3791 15.4308 76.3791 10.0007C76.3791 4.54909 75.8589 3.19691 73.7781 3.21847C71.6757 3.23992 71.1555 4.59209 71.1555 10.0007Z"
fill="#FBFBFB"
/>
<path
d="M85.0819 18.5624L82.481 18.5838V1.41351H87.0544L91.3243 15.4073H91.3676V1.41351H93.968V18.5838H89.677L85.1254 3.51689H85.0819V18.5624Z"
fill="#FBFBFB"
/>
<path
d="M100.468 1.41351H97.8677V18.5838H100.468V1.41351Z"
fill="#FBFBFB"
/>
<path
d="M111.139 8.06885H114.174C114.174 3.17534 112.591 1.07196 108.906 1.07196C105.028 1.07196 103.358 3.73341 103.358 9.97909C103.358 16.2462 105.028 18.9291 108.906 18.9291C112.591 18.9291 114.174 16.8901 114.195 12.1468H111.161C111.139 15.8599 110.684 16.7828 108.906 16.7828C106.869 16.7828 106.371 15.4307 106.393 9.97909C106.393 4.54896 106.891 3.19678 108.906 3.21823C110.684 3.21823 111.139 4.18413 111.139 8.06885Z"
fill="#FBFBFB"
/>
</svg>
</SvgIcon>
</Button>
<Divider
flexItem
orientation="vertical"
color="#FBFBFB"
sx={{ height: "20px", width: "1px", alignSelf: "center" }}
/>
<Typography fontSize="1.25rem" sx={{ paddingLeft: 1 }}>
{title}
</Typography>
</Stack>
{showWalletConnect && (
<Button onClick={() => navigation.navigate("WalletConnect")}>
{<WCLogo />}
</Button>
)}
</Stack>
);
};

View File

@ -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<void>;
}) => {
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<HTMLDivElement>) => {
const pastedText = event.clipboardData.getData('Text');
const pastedText = event.clipboardData.getData("Text");
const splitWords = pastedText.trim().split(/\s+/);
if (splitWords.length === 12) {
@ -30,14 +39,16 @@ const ImportWalletDialog = ({
};
useEffect(() => {
setWords(Array(12).fill(''));
},[visible]);
setWords(Array(12).fill(""));
}, [visible]);
return (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle>Import your wallet from your mnemonic</DialogTitle>
<DialogContent>
<Typography>
<DialogTitle style={styles.mnemonicTitle}>
Import your wallet from your mnemonic
</DialogTitle>
<DialogContent style={styles.mnemonicContainer}>
<Typography component="div">
(You can paste your entire mnemonic into the first textbox)
</Typography>
<Grid container spacing={2}>
@ -55,9 +66,16 @@ const ImportWalletDialog = ({
))}
</Grid>
</DialogContent>
<DialogActions>
<Button onClick={() => importWalletHandler(words.join(' '))}>Import Wallet</Button>
<Button onClick={hideDialog}>Cancel</Button>
<DialogActions style={styles.mnemonicButtonRow}>
<Button
onClick={() => importWalletHandler(words.join(" "))}
style={styles.mnemonicButton}
>
Import Wallet
</Button>
<Button onClick={hideDialog} style={styles.mnemonicButton}>
Cancel
</Button>
</DialogActions>
</Dialog>
);

34
src/components/Layout.tsx Normal file
View File

@ -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<PropsWithChildren<{ title: string }>> = ({
children,
title,
}) => {
const navigation =
useNavigation<NativeStackNavigationProp<StackParamsList>>();
return (
<Container
boxProps={{ sx: { backgroundColor: "inherit", padding: 0, mt: 3 } }}
>
<Button
startIcon={<ArrowBack />}
color="info"
sx={{ mb: 4 }}
onClick={() => navigation.navigate("Home")}
>
Home
</Button>
<Typography variant="h4" sx={{ mb: 4 }}>
{title}
</Typography>
<Container>{children}</Container>
</Container>
);
};

View File

@ -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 (
<Dialog open={visible} onClose={hideDialog}>
<DialogTitle>Mnemonic</DialogTitle>
<DialogContent>
<Typography variant="h6" component="div" style={{ color: 'rgba(0, 0, 0, 0.87)' }}>
Your mnemonic provides full access to your wallet and funds. Make sure to note it down.
<DialogTitle style={{ ...styles.mnemonicTitle }}>Mnemonic</DialogTitle>
<DialogContent style={{ ...styles.mnemonicContainer }}>
<Typography component="div">
Your mnemonic provides full access to your wallet and funds. <br />
Make sure to note it down.
</Typography>
<Typography variant="h6" component="div" style={{ ...styles.dialogWarning }}>
<Typography component="div" style={styles.mnemonicDialogWarning}>
Do not share your mnemonic with anyone
</Typography>
<GridView words={words} />
</DialogContent>
<DialogActions>
<Button onClick={copyMnemonic}>Copy</Button>
<Button onClick={hideDialog}>Cancel</Button>
<DialogActions style={{ ...styles.mnemonicButtonRow }}>
<Button style={{ ...styles.mnemonicButton }} onClick={copyMnemonic}>
Copy
</Button>
<Button style={{ ...styles.mnemonicButton }} onClick={hideDialog}>
Done
</Button>
</DialogActions>
</Dialog>
);

View File

@ -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 (
<View style={styles.networkDropdown}>
<List.Accordion
title={selectedNetwork!.networkName}
expanded={expanded}
onPress={() => setExpanded(!expanded)}>
{networksData.map(networkData => (
<Accordion expanded={expanded} onClick={() => setExpanded(!expanded)}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
{selectedNetwork!.networkName}
</AccordionSummary>
{networksData.map((networkData) => (
<List.Item
key={networkData.networkId}
title={networkData.networkName}
onPress={() => handleNetworkPress(networkData)}
/>
))}
</List.Accordion>
</Accordion>
</View>
);
};

View File

@ -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<string>();
const [showPKDialog, setShowPKDialog] = useState<boolean>(false);
const theme = useTheme();
const handleShowPrivateKey = async () => {
const pathKey = await getPathKey(
`${selectedNetwork!.namespace}:${selectedNetwork!.chainId}`,
@ -41,12 +38,9 @@ const ShowPKDialog = () => {
<TouchableOpacity
onPress={() => {
setShowPKDialog(true);
}}>
<Text
variant="titleSmall"
style={[styles.hyperlink, { color: theme.colors.primary }]}>
Show Private Key
</Text>
}}
>
<Link>Show Private Key</Link>
</TouchableOpacity>
</View>
<View>
@ -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 = () => {
)}
<View>
<Typography variant="body1" style={styles.dialogWarning}>
<Typography component="span">
Warning:
</Typography>
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.
</Typography>
</View>
</DialogContent>
<DialogActions>
{!privateKey ? (
<>
<Button onClick={handleShowPrivateKey} color="error">
Yes
</Button>
<Button onClick={hideShowPKDialog}>No</Button>
<Button style={{...styles.buttonRed, ...styles.button}} onClick={handleShowPrivateKey}>Yes</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>No</Button>
</>
) : (
<Button onClick={hideShowPKDialog}>Ok</Button>
<Button style={{...styles.buttonBlue, ...styles.button}} onClick={hideShowPKDialog}>Ok</Button>
)}
</DialogActions>
</Dialog>

View File

@ -1,32 +1,111 @@
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(
<div id="app">
<PaperProvider theme={theme}>
<NetworksProvider>
<AccountsProvider>
@ -34,26 +113,29 @@ root.render(
<NavigationContainer
linking={linking}
documentTitle={{
formatter: () =>
`Laconic Wallet`,
formatter: () => `Laconic | Wallet`,
}}
theme={navigationTheme}
>
<React.Fragment>
{Platform.OS === 'web' ? (
{Platform.OS === "web" ? (
<style type="text/css">{`
@font-face {
font-family: 'MaterialCommunityIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf')}) format('truetype');
src: url(${require("react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf")}) format('truetype');
}
`}</style>
) : null}
<ThemeProvider theme={muiTheme}>
<App />
</ThemeProvider>
</React.Fragment>
</NavigationContainer>
</WalletConnectProvider>
</AccountsProvider>
</NetworksProvider>
</PaperProvider>
</div>,
);
// If you want to start measuring performance in your app, pass a function

View File

@ -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<z.infer<typeof networksFormDataSchema>>({
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,9 +213,12 @@ const AddNetwork = () => {
}, [namespace, reset]);
return (
<View style={styles.appContainer}>
<Layout title="Add Network">
<SelectNetworkType updateNetworkType={updateNetworkType} />
<Divider flexItem sx={{ my: 4 }} />
<Grid container spacing={2} sx={{ px: 1 }}>
<Grid item xs={6}>
<Controller
control={control}
name="chainId"
@ -226,12 +230,15 @@ const AddNetwork = () => {
value={value}
label="Chain ID"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.chainId?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
defaultValue=""
@ -243,12 +250,17 @@ const AddNetwork = () => {
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
<HelperText type="error">
{errors.networkName?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="rpcUrl"
@ -260,13 +272,15 @@ const AddNetwork = () => {
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
defaultValue=""
@ -278,7 +292,7 @@ const AddNetwork = () => {
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
@ -286,6 +300,8 @@ const AddNetwork = () => {
</>
)}
/>
</Grid>
<Grid item xs={namespace === EIP155 ? 12 : 6}>
<Controller
control={control}
name="coinType"
@ -303,7 +319,9 @@ const AddNetwork = () => {
</>
)}
/>
</Grid>
{namespace === EIP155 ? (
<Grid item xs={12}>
<Controller
control={control}
name="currencySymbol"
@ -315,19 +333,24 @@ const AddNetwork = () => {
value={value}
label="Currency Symbol"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>)
.currencySymbol?.message
(
errors as FieldErrors<
z.infer<typeof ethNetworkDataSchema>
>
).currencySymbol?.message
}
</HelperText>
</>
)}
/>
</Grid>
) : (
<>
<Grid item xs={6}>
<Controller
control={control}
name="nativeDenom"
@ -339,7 +362,7 @@ const AddNetwork = () => {
value={value}
label="Native Denom"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
@ -353,6 +376,8 @@ const AddNetwork = () => {
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="addressPrefix"
@ -364,7 +389,7 @@ const AddNetwork = () => {
value={value}
label="Address Prefix"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{
@ -378,6 +403,8 @@ const AddNetwork = () => {
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
@ -403,17 +430,20 @@ const AddNetwork = () => {
</>
)}
/>
</Grid>
</>
)}
<Button
mode="contained"
</Grid>
<LoadingButton
variant="contained"
loading={isSubmitting}
disabled={isSubmitting}
style={styles.networksButton}
onPress={handleSubmit(submit)}>
{isSubmitting ? 'Adding' : 'Submit'}
</Button>
</View>
onClick={handleSubmit(submit)}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
>
{isSubmitting ? "Adding" : "Submit"}
</LoadingButton>
</Layout>
);
};

View File

@ -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<NativeStackNavigationProp<StackParamsList>>();
const [currentWCURI, setCurrentWCURI] = useState<string>('');
const [currentWCURI, setCurrentWCURI] = useState<string>("");
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 (
<View style={styles.appContainer}>
<Layout title="Add Session">
<View style={styles.inputContainer}>
<Text variant="titleMedium">Enter WalletConnect URI</Text>
<TextInput
@ -41,13 +43,13 @@ const AddSession = () => {
style={styles.walletConnectUriText}
/>
<View style={styles.signButton}>
<Button mode="contained" onPress={pair}>
<Box sx={{ mt: 2 }}>
<Button variant="contained" onClick={pair}>
Pair Session
</Button>
</Box>
</View>
</View>
</View>
</Layout>
);
};
export default AddSession;

View File

@ -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<StackParamsList, 'EditNetwork'>;
type EditNetworkProps = NativeStackScreenProps<StackParamsList, "EditNetwork">;
const EditNetwork = ({ route }: EditNetworkProps) => {
const { setNetworksData } = useNetworks();
@ -67,7 +68,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
formState: { errors, isSubmitting },
handleSubmit,
} = useForm<z.infer<typeof networksFormDataSchema>>({
mode: 'onChange',
mode: "onChange",
resolver: zodResolver(networksFormDataSchema),
});
@ -77,31 +78,33 @@ 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 (
<View style={styles.appContainer}>
<View>
<Text style={styles.subHeading}>
<Layout title="Edit Network">
<Typography fontSize="1.5rem" fontWeight={500}>
Edit {networkData?.networkName} details
</Text>
</View>
</Typography>
<Divider flexItem sx={{ my: 4 }} />
<Grid container spacing={2}>
<Grid item xs={6}>
<Controller
control={control}
defaultValue={networkData.networkName}
@ -113,12 +116,16 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
label="Network Name"
value={value}
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.networkName?.message}</HelperText>
<HelperText type="error">
{errors.networkName?.message}
</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={6}>
<Controller
control={control}
name="rpcUrl"
@ -130,13 +137,15 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
label="New RPC URL"
onBlur={onBlur}
value={value}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
</>
)}
/>
</Grid>
<Grid item xs={isCosmos ? 6 : 12}>
<Controller
control={control}
defaultValue={networkData.blockExplorerUrl}
@ -148,7 +157,7 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
value={value}
label="Block Explorer URL (Optional)"
onBlur={onBlur}
onChangeText={textValue => onChange(textValue)}
onChangeText={(textValue) => onChange(textValue)}
/>
<HelperText type="error">
{errors.blockExplorerUrl?.message}
@ -156,7 +165,9 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
</>
)}
/>
{networkData.namespace === COSMOS && (
</Grid>
{isCosmos && (
<Grid item xs={6}>
<Controller
control={control}
name="gasPrice"
@ -182,16 +193,19 @@ const EditNetwork = ({ route }: EditNetworkProps) => {
</>
)}
/>
</Grid>
)}
<Button
mode="contained"
</Grid>
<LoadingButton
variant="contained"
loading={isSubmitting}
disabled={isSubmitting}
style={styles.networksButton}
onPress={handleSubmit(submit)}>
{isSubmitting ? 'Adding' : 'Submit'}
</Button>
</View>
onClick={handleSubmit(submit)}
sx={{ minWidth: "200px", px: 4, py: 1, mt: 2 }}
>
{isSubmitting ? "Adding" : "Submit"}
</LoadingButton>
</Layout>
);
};

View File

@ -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 (
<Image
style={styles.walletConnectLogo}
source={require('../assets/WalletConnect-Icon-Blueberry.png')}
/>
);
};
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<NativeStackNavigationProp<StackParamsList>>();
useEffect(() => {
if (accounts.length > 0) {
navigation.setOptions({
// eslint-disable-next-line react/no-unstable-nested-components
headerRight: () => (
<Button onPress={() => navigation.navigate('WalletConnect')}>
{<WCLogo />}
</Button>
),
});
} else {
navigation.setOptions({
headerRight: undefined,
});
}
}, [navigation, accounts]);
const [isWalletCreated, setIsWalletCreated] = useState<boolean>(false);
const [isWalletCreating, setIsWalletCreating] = useState<boolean>(false);
const [importWalletDialog, setImportWalletDialog] = useState<boolean>(false);
const [mnemonicDialog, setMnemonicDialog] = useState<boolean>(false);
const [resetWalletDialog, setResetWalletDialog] = useState<boolean>(false);
const [toastVisible, setToastVisible] = useState(false);
const [invalidMnemonicError, setInvalidMnemonicError] = useState('');
const [invalidMnemonicError, setInvalidMnemonicError] = useState("");
const [isAccountsFetched, setIsAccountsFetched] = useState<boolean>(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,6 +120,7 @@ const HomeScreen = () => {
return (
<View style={styles.appContainer}>
<Container>
{!isAccountsFetched ? (
<View style={styles.spinnerContainer}>
<Text style={styles.LoadingText}>Loading...</Text>
@ -160,17 +132,16 @@ const HomeScreen = () => {
<View style={styles.accountComponent}>
<Accounts />
</View>
<View style={styles.resetContainer}>
<Divider flexItem sx={{ my: 4 }} />
<Button
style={styles.resetButton}
mode="contained"
buttonColor="#B82B0D"
onPress={() => {
variant="contained"
onClick={() => {
setResetWalletDialog(true);
}}>
}}
color="error"
>
Reset Wallet
</Button>
</View>
</>
) : (
<>
@ -178,13 +149,16 @@ const HomeScreen = () => {
isWalletCreating={isWalletCreating}
createWalletHandler={createWalletHandler}
/>
{ IS_IMPORT_WALLET_ENABLED && <View style={styles.createWalletContainer}>
{IS_IMPORT_WALLET_ENABLED && (
<View style={styles.createWalletContainer}>
<Button
mode="contained"
onPress={() => setImportWalletDialog(true)}>
variant="contained"
onClick={() => setImportWalletDialog(true)}
>
Import Wallet
</Button>
</View> }
</View>
)}
</>
)}
<ImportWalletDialog
@ -203,14 +177,15 @@ const HomeScreen = () => {
hideDialog={hideResetDialog}
onConfirm={confirmResetWallet}
/>
</Container>
<Portal>
<Snackbar
open={toastVisible}
autoHideDuration={3000}
message={invalidMnemonicError}
onClose={() => setToastVisible(false)}
anchorOrigin={{ horizontal: 'center', vertical: 'bottom' }}
ContentProps={{ style: { backgroundColor: 'red', color: 'white'} }}
anchorOrigin={{ horizontal: "center", vertical: "bottom" }}
ContentProps={{ style: { backgroundColor: "red", color: "white" } }}
/>
</Portal>
</View>

View File

@ -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<StackParamsList, 'SignMessage'>;
type SignProps = NativeStackScreenProps<StackParamsList, "SignMessage">;
const SignMessage = ({ route }: SignProps) => {
const namespace = route.params.selectedNamespace;
const chainId = route.params.selectedChainId;
const account = route.params.accountInfo;
const [message, setMessage] = useState<string>('');
const [message, setMessage] = useState<string>("");
const signMessageHandler = async () => {
const signedMessage = await signMessage({
@ -29,31 +29,30 @@ const SignMessage = ({ route }: SignProps) => {
};
return (
<View style={styles.signPage}>
<View style={styles.accountInfo}>
<View>
<Layout title="Sign Message">
<Text variant="titleMedium">
{account && `Account ${account.index + 1}`}
</Text>
</View>
<View style={styles.accountContainer}>
<AccountDetails account={account} />
</View>
</View>
<Stack spacing={4}>
<Divider flexItem />
<TextInput
mode="outlined"
placeholder="Enter your message"
onChangeText={text => setMessage(text)}
onChangeText={(text) => setMessage(text)}
value={message}
/>
<View style={styles.signButton}>
<Button mode="contained" onPress={signMessageHandler}>
<Button
variant="contained"
onClick={signMessageHandler}
sx={{ width: "200px", px: 4, py: 1, mt: 2 }}
>
Sign
</Button>
</View>
</View>
</Stack>
</Layout>
);
};

View File

@ -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 (
<View>
<Layout title="Active Sessions">
{Object.keys(activeSessions).length > 0 ? (
<>
<View style={styles.sessionsContainer}>
<Text variant="titleMedium">Active Sessions</Text>
</View>
<View style={styles.sessionsContainer} />
<List.Section>
{Object.entries(activeSessions).map(([sessionId, session]) => (
<List.Item
@ -44,7 +43,7 @@ export default function WalletConnect() {
// eslint-disable-next-line react/no-unstable-nested-components
left={() => (
<>
{session.peer.metadata.icons[0].endsWith('.svg') ? (
{session.peer.metadata.icons[0].endsWith(".svg") ? (
<View style={styles.dappLogo}>
<Text>SvgURI peerMetaDataIcon</Text>
</View>
@ -60,7 +59,8 @@ export default function WalletConnect() {
right={() => (
<TouchableOpacity
onPress={() => disconnect(sessionId)}
style={styles.disconnectSession}>
style={styles.disconnectSession}
>
<List.Icon icon="close" />
</TouchableOpacity>
)}
@ -73,6 +73,6 @@ export default function WalletConnect() {
<Text>You have no active sessions</Text>
</View>
)}
</View>
</Layout>
);
}

View File

@ -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;

138
yarn.lock
View File

@ -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"