forked from cerc-io/laconic-wallet
Implement Edit network form (#107)
* Add edit network form * Set selected network when networks are updated * Disable buttons and add spinner after submitting * Display previous values in Edit network form * Use error msgs form constants file * Reset default networks on reset
This commit is contained in:
parent
8c0751f84b
commit
6d80f64a10
@ -28,6 +28,7 @@ import { EIP155_SIGNING_METHODS } from './utils/wallet-connect/EIP155Data';
|
|||||||
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
|
import { getSignParamsMessage } from './utils/wallet-connect/helpers';
|
||||||
import ApproveTransaction from './screens/ApproveTransaction';
|
import ApproveTransaction from './screens/ApproveTransaction';
|
||||||
import AddNetwork from './screens/AddNetwork';
|
import AddNetwork from './screens/AddNetwork';
|
||||||
|
import EditNetwork from './screens/EditNetwork';
|
||||||
import { COSMOS, EIP155 } from './utils/constants';
|
import { COSMOS, EIP155 } from './utils/constants';
|
||||||
import { useNetworks } from './context/NetworksContext';
|
import { useNetworks } from './context/NetworksContext';
|
||||||
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
|
import { NETWORK_METHODS } from './utils/wallet-connect/common-data';
|
||||||
@ -281,6 +282,13 @@ const App = (): React.JSX.Element => {
|
|||||||
title: 'Add Network',
|
title: 'Add Network',
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
<Stack.Screen
|
||||||
|
name="EditNetwork"
|
||||||
|
component={EditNetwork}
|
||||||
|
options={{
|
||||||
|
title: 'Edit Network',
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Stack.Navigator>
|
</Stack.Navigator>
|
||||||
<PairingModal
|
<PairingModal
|
||||||
visible={modalVisible}
|
visible={modalVisible}
|
||||||
|
@ -183,6 +183,21 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
|
|||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.signLink}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
navigation.navigate('EditNetwork', {
|
||||||
|
selectedNetwork: selectedNetwork!,
|
||||||
|
});
|
||||||
|
}}>
|
||||||
|
<Text
|
||||||
|
variant="titleSmall"
|
||||||
|
style={[styles.hyperlink, { color: theme.colors.primary }]}>
|
||||||
|
Edit Network
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
|
||||||
{!selectedNetwork!.isDefault && (
|
{!selectedNetwork!.isDefault && (
|
||||||
<View style={styles.signLink}>
|
<View style={styles.signLink}>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
|
@ -5,8 +5,6 @@ import { retrieveNetworksData, storeNetworkData } from '../utils/accounts';
|
|||||||
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
import { DEFAULT_NETWORKS, EIP155 } from '../utils/constants';
|
||||||
|
|
||||||
const NetworksContext = createContext<{
|
const NetworksContext = createContext<{
|
||||||
currentIndex: number;
|
|
||||||
setCurrentIndex: (index: number) => void;
|
|
||||||
networksData: NetworksDataState[];
|
networksData: NetworksDataState[];
|
||||||
setNetworksData: React.Dispatch<React.SetStateAction<NetworksDataState[]>>;
|
setNetworksData: React.Dispatch<React.SetStateAction<NetworksDataState[]>>;
|
||||||
networkType: string;
|
networkType: string;
|
||||||
@ -16,8 +14,6 @@ const NetworksContext = createContext<{
|
|||||||
React.SetStateAction<NetworksDataState | undefined>
|
React.SetStateAction<NetworksDataState | undefined>
|
||||||
>;
|
>;
|
||||||
}>({
|
}>({
|
||||||
currentIndex: 0,
|
|
||||||
setCurrentIndex: () => {},
|
|
||||||
networksData: [],
|
networksData: [],
|
||||||
setNetworksData: () => {},
|
setNetworksData: () => {},
|
||||||
networkType: '',
|
networkType: '',
|
||||||
@ -33,7 +29,6 @@ const useNetworks = () => {
|
|||||||
|
|
||||||
const NetworksProvider = ({ children }: { children: any }) => {
|
const NetworksProvider = ({ children }: { children: any }) => {
|
||||||
const [networksData, setNetworksData] = useState<NetworksDataState[]>([]);
|
const [networksData, setNetworksData] = useState<NetworksDataState[]>([]);
|
||||||
const [currentIndex, setCurrentIndex] = useState<number>(0);
|
|
||||||
const [networkType, setNetworkType] = useState<string>(EIP155);
|
const [networkType, setNetworkType] = useState<string>(EIP155);
|
||||||
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
const [selectedNetwork, setSelectedNetwork] = useState<NetworksDataState>();
|
||||||
|
|
||||||
@ -50,14 +45,22 @@ const NetworksProvider = ({ children }: { children: any }) => {
|
|||||||
setSelectedNetwork(retrievedNewNetworks[0]);
|
setSelectedNetwork(retrievedNewNetworks[0]);
|
||||||
};
|
};
|
||||||
|
|
||||||
fetchData();
|
if (networksData.length === 0) {
|
||||||
}, []);
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [networksData]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelectedNetwork(prevSelectedNetwork => {
|
||||||
|
return networksData.find(
|
||||||
|
networkData => networkData.networkId === prevSelectedNetwork?.networkId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, [networksData]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NetworksContext.Provider
|
<NetworksContext.Provider
|
||||||
value={{
|
value={{
|
||||||
currentIndex,
|
|
||||||
setCurrentIndex,
|
|
||||||
networksData,
|
networksData,
|
||||||
setNetworksData,
|
setNetworksData,
|
||||||
networkType,
|
networkType,
|
||||||
|
@ -20,13 +20,16 @@ import { StackParamsList } from '../types';
|
|||||||
import { SelectNetworkType } from '../components/SelectNetworkType';
|
import { SelectNetworkType } from '../components/SelectNetworkType';
|
||||||
import { storeNetworkData } from '../utils/accounts';
|
import { storeNetworkData } from '../utils/accounts';
|
||||||
import { useNetworks } from '../context/NetworksContext';
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
import { COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY } from '../utils/constants';
|
import {
|
||||||
|
COSMOS,
|
||||||
|
EIP155,
|
||||||
|
CHAINID_DEBOUNCE_DELAY,
|
||||||
|
EMPTY_FIELD_ERROR,
|
||||||
|
INVALID_URL_ERROR,
|
||||||
|
} from '../utils/constants';
|
||||||
import { getCosmosAccounts } from '../utils/accounts';
|
import { getCosmosAccounts } from '../utils/accounts';
|
||||||
import ETH_CHAINS from '../assets/ethereum-chains.json';
|
import ETH_CHAINS from '../assets/ethereum-chains.json';
|
||||||
|
|
||||||
const EMPTY_FIELD_ERROR = 'Field cannot be empty';
|
|
||||||
const INVALID_URL_ERROR = 'Invalid URL';
|
|
||||||
|
|
||||||
const ethNetworkDataSchema = z.object({
|
const ethNetworkDataSchema = z.object({
|
||||||
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
chainId: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
@ -59,6 +62,7 @@ const AddNetwork = () => {
|
|||||||
const { setNetworksData } = useNetworks();
|
const { setNetworksData } = useNetworks();
|
||||||
|
|
||||||
const [namespace, setNamespace] = useState<string>(EIP155);
|
const [namespace, setNamespace] = useState<string>(EIP155);
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
const networksFormDataSchema =
|
const networksFormDataSchema =
|
||||||
namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema;
|
namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema;
|
||||||
@ -114,6 +118,7 @@ const AddNetwork = () => {
|
|||||||
|
|
||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
async (data: z.infer<typeof networksFormDataSchema>) => {
|
async (data: z.infer<typeof networksFormDataSchema>) => {
|
||||||
|
setIsLoading(true);
|
||||||
const newNetworkData = {
|
const newNetworkData = {
|
||||||
...data,
|
...data,
|
||||||
namespace,
|
namespace,
|
||||||
@ -176,6 +181,7 @@ const AddNetwork = () => {
|
|||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
navigation.navigate('Laconic');
|
navigation.navigate('Laconic');
|
||||||
},
|
},
|
||||||
[navigation, namespace, setNetworksData],
|
[navigation, namespace, setNetworksData],
|
||||||
@ -358,8 +364,12 @@ const AddNetwork = () => {
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button mode="contained" onPress={handleSubmit(submit)}>
|
<Button
|
||||||
Submit
|
mode="contained"
|
||||||
|
loading={isLoading}
|
||||||
|
disabled={isLoading}
|
||||||
|
onPress={handleSubmit(submit)}>
|
||||||
|
{isLoading ? 'Adding' : 'Submit'}
|
||||||
</Button>
|
</Button>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
|
148
src/screens/EditNetwork.tsx
Normal file
148
src/screens/EditNetwork.tsx
Normal file
@ -0,0 +1,148 @@
|
|||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { ScrollView, View } from 'react-native';
|
||||||
|
import { useForm, Controller } from 'react-hook-form';
|
||||||
|
import { TextInput, Button, HelperText, Text } from 'react-native-paper';
|
||||||
|
import { setInternetCredentials } from 'react-native-keychain';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
import {
|
||||||
|
NativeStackNavigationProp,
|
||||||
|
NativeStackScreenProps,
|
||||||
|
} from '@react-navigation/native-stack';
|
||||||
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
|
||||||
|
import { StackParamsList } from '../types';
|
||||||
|
import styles from '../styles/stylesheet';
|
||||||
|
import { retrieveNetworksData } from '../utils/accounts';
|
||||||
|
import { useNetworks } from '../context/NetworksContext';
|
||||||
|
import { EMPTY_FIELD_ERROR, INVALID_URL_ERROR } from '../utils/constants';
|
||||||
|
|
||||||
|
const networksFormDataSchema = z.object({
|
||||||
|
networkName: z.string().nonempty({ message: EMPTY_FIELD_ERROR }),
|
||||||
|
rpcUrl: z.string().url({ message: INVALID_URL_ERROR }),
|
||||||
|
blockExplorerUrl: z
|
||||||
|
.string()
|
||||||
|
.url({ message: INVALID_URL_ERROR })
|
||||||
|
.or(z.literal('')),
|
||||||
|
});
|
||||||
|
|
||||||
|
type EditNetworkProps = NativeStackScreenProps<StackParamsList, 'EditNetwork'>;
|
||||||
|
|
||||||
|
const EditNetwork = ({ route }: EditNetworkProps) => {
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
|
const { setNetworksData } = useNetworks();
|
||||||
|
const navigation =
|
||||||
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
|
const {
|
||||||
|
control,
|
||||||
|
formState: { errors },
|
||||||
|
handleSubmit,
|
||||||
|
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
||||||
|
mode: 'onChange',
|
||||||
|
resolver: zodResolver(networksFormDataSchema),
|
||||||
|
});
|
||||||
|
const networkData = route.params.selectedNetwork;
|
||||||
|
|
||||||
|
const submit = useCallback(
|
||||||
|
async (data: z.infer<typeof networksFormDataSchema>) => {
|
||||||
|
setIsLoading(true);
|
||||||
|
|
||||||
|
const retrievedNetworksData = await retrieveNetworksData();
|
||||||
|
|
||||||
|
const newNetworkData = { ...networkData, ...data };
|
||||||
|
const index = retrievedNetworksData.findIndex(
|
||||||
|
network => network.networkId === networkData.networkId,
|
||||||
|
);
|
||||||
|
|
||||||
|
retrievedNetworksData.splice(index, 1, newNetworkData);
|
||||||
|
|
||||||
|
await setInternetCredentials(
|
||||||
|
'networks',
|
||||||
|
'_',
|
||||||
|
JSON.stringify(retrievedNetworksData),
|
||||||
|
);
|
||||||
|
|
||||||
|
setNetworksData(retrievedNetworksData);
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
navigation.navigate('Laconic');
|
||||||
|
},
|
||||||
|
[networkData, navigation, setNetworksData],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ScrollView contentContainerStyle={styles.signPage}>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.subHeading}>
|
||||||
|
Edit {networkData?.networkName} details
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={networkData.networkName}
|
||||||
|
name="networkName"
|
||||||
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
label="Network Name"
|
||||||
|
value={value}
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChangeText={textValue => onChange(textValue)}
|
||||||
|
/>
|
||||||
|
<HelperText type="error">{errors.networkName?.message}</HelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="rpcUrl"
|
||||||
|
defaultValue={networkData.rpcUrl}
|
||||||
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
label="New RPC URL"
|
||||||
|
onBlur={onBlur}
|
||||||
|
value={value}
|
||||||
|
onChangeText={textValue => onChange(textValue)}
|
||||||
|
/>
|
||||||
|
<HelperText type="error">{errors.rpcUrl?.message}</HelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
defaultValue={networkData.blockExplorerUrl}
|
||||||
|
name="blockExplorerUrl"
|
||||||
|
render={({ field: { onChange, onBlur, value } }) => (
|
||||||
|
<>
|
||||||
|
<TextInput
|
||||||
|
mode="outlined"
|
||||||
|
value={value}
|
||||||
|
label="Block Explorer URL (Optional)"
|
||||||
|
onBlur={onBlur}
|
||||||
|
onChangeText={textValue => onChange(textValue)}
|
||||||
|
/>
|
||||||
|
<HelperText type="error">
|
||||||
|
{errors.blockExplorerUrl?.message}
|
||||||
|
</HelperText>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
mode="contained"
|
||||||
|
loading={isLoading}
|
||||||
|
disabled={isLoading}
|
||||||
|
onPress={handleSubmit(submit)}>
|
||||||
|
{isLoading ? 'Adding' : 'Submit'}
|
||||||
|
</Button>
|
||||||
|
</ScrollView>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default EditNetwork;
|
@ -92,16 +92,13 @@ const HomeScreen = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const confirmResetWallet = useCallback(async () => {
|
const confirmResetWallet = useCallback(async () => {
|
||||||
const updatedNetworks = networksData.filter(
|
|
||||||
networkData => networkData.isDefault,
|
|
||||||
);
|
|
||||||
setNetworksData(updatedNetworks);
|
|
||||||
setSelectedNetwork(undefined);
|
|
||||||
setIsWalletCreated(false);
|
setIsWalletCreated(false);
|
||||||
setIsWalletCreating(false);
|
setIsWalletCreating(false);
|
||||||
setAccounts([]);
|
setAccounts([]);
|
||||||
setCurrentIndex(0);
|
setCurrentIndex(0);
|
||||||
await resetWallet(updatedNetworks);
|
setNetworksData([]);
|
||||||
|
setSelectedNetwork(undefined);
|
||||||
|
await resetWallet();
|
||||||
const sessions = web3wallet!.getActiveSessions();
|
const sessions = web3wallet!.getActiveSessions();
|
||||||
|
|
||||||
Object.keys(sessions).forEach(async sessionId => {
|
Object.keys(sessions).forEach(async sessionId => {
|
||||||
@ -114,7 +111,6 @@ const HomeScreen = () => {
|
|||||||
|
|
||||||
hideResetDialog();
|
hideResetDialog();
|
||||||
}, [
|
}, [
|
||||||
networksData,
|
|
||||||
setAccounts,
|
setAccounts,
|
||||||
setActiveSessions,
|
setActiveSessions,
|
||||||
setCurrentIndex,
|
setCurrentIndex,
|
||||||
|
@ -204,7 +204,9 @@ const styles = StyleSheet.create({
|
|||||||
},
|
},
|
||||||
subHeading: {
|
subHeading: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
fontWeight: '600',
|
fontWeight: 'bold',
|
||||||
|
marginBottom: 10,
|
||||||
|
marginTop: 10,
|
||||||
},
|
},
|
||||||
centerText: {
|
centerText: {
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
|
@ -26,6 +26,9 @@ export type StackParamsList = {
|
|||||||
WalletConnect: undefined;
|
WalletConnect: undefined;
|
||||||
AddSession: undefined;
|
AddSession: undefined;
|
||||||
AddNetwork: undefined;
|
AddNetwork: undefined;
|
||||||
|
EditNetwork: {
|
||||||
|
selectedNetwork: NetworksDataState;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Account = {
|
export type Account = {
|
||||||
|
@ -225,13 +225,13 @@ const retrieveSingleAccount = async (
|
|||||||
return loadedAccounts.find(account => account.address === address);
|
return loadedAccounts.find(account => account.address === address);
|
||||||
};
|
};
|
||||||
|
|
||||||
const resetWallet = async (networks: NetworksDataState[]) => {
|
const resetWallet = async () => {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
resetInternetCredentials('mnemonicServer'),
|
resetInternetCredentials('mnemonicServer'),
|
||||||
resetKeyServers(EIP155),
|
resetKeyServers(EIP155),
|
||||||
resetKeyServers(COSMOS),
|
resetKeyServers(COSMOS),
|
||||||
setInternetCredentials('networks', '_', JSON.stringify(networks)),
|
setInternetCredentials('networks', '_', JSON.stringify([])),
|
||||||
]);
|
]);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error resetting wallet:', error);
|
console.error('Error resetting wallet:', error);
|
||||||
|
@ -26,3 +26,6 @@ export const DEFAULT_NETWORKS = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const CHAINID_DEBOUNCE_DELAY = 250;
|
export const CHAINID_DEBOUNCE_DELAY = 250;
|
||||||
|
|
||||||
|
export const EMPTY_FIELD_ERROR = 'Field cannot be empty';
|
||||||
|
export const INVALID_URL_ERROR = 'Invalid URL';
|
||||||
|
Loading…
Reference in New Issue
Block a user