forked from cerc-io/laconic-wallet
Add form validation for add network form (#101)
* Use zod for validation * Return to index 0 after deleting network * Make url optional * Use form schema according to the selected nw * Fix type for networks data * Accept numeric value for coin type * Fix form type issue
This commit is contained in:
parent
b947dd1151
commit
670d6f4a54
@ -16,6 +16,7 @@
|
|||||||
"@cosmjs/proto-signing": "^0.32.3",
|
"@cosmjs/proto-signing": "^0.32.3",
|
||||||
"@cosmjs/stargate": "^0.32.3",
|
"@cosmjs/stargate": "^0.32.3",
|
||||||
"@ethersproject/shims": "^5.7.0",
|
"@ethersproject/shims": "^5.7.0",
|
||||||
|
"@hookform/resolvers": "^3.3.4",
|
||||||
"@json-rpc-tools/utils": "^1.7.6",
|
"@json-rpc-tools/utils": "^1.7.6",
|
||||||
"@react-native-async-storage/async-storage": "^1.22.3",
|
"@react-native-async-storage/async-storage": "^1.22.3",
|
||||||
"@react-native-community/netinfo": "^11.3.1",
|
"@react-native-community/netinfo": "^11.3.1",
|
||||||
@ -46,7 +47,8 @@
|
|||||||
"react-native-vector-icons": "^10.0.3",
|
"react-native-vector-icons": "^10.0.3",
|
||||||
"react-native-vision-camera": "^3.9.0",
|
"react-native-vision-camera": "^3.9.0",
|
||||||
"text-encoding-polyfill": "^0.6.7",
|
"text-encoding-polyfill": "^0.6.7",
|
||||||
"use-debounce": "^10.0.0"
|
"use-debounce": "^10.0.0",
|
||||||
|
"zod": "^3.22.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.20.0",
|
"@babel/core": "^7.20.0",
|
||||||
|
@ -21,7 +21,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
|
|||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
const { accounts, setAccounts } = useAccounts();
|
const { accounts, setAccounts, setCurrentIndex } = useAccounts();
|
||||||
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } =
|
||||||
useNetworks();
|
useNetworks();
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
@ -108,6 +108,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
setSelectedNetwork(updatedNetworks[0]);
|
setSelectedNetwork(updatedNetworks[0]);
|
||||||
|
setCurrentIndex(0);
|
||||||
setDeleteNetworkDialog(false);
|
setDeleteNetworkDialog(false);
|
||||||
setNetworksData(updatedNetworks);
|
setNetworksData(updatedNetworks);
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => {
|
|||||||
onPress={() => setExpanded(!expanded)}>
|
onPress={() => setExpanded(!expanded)}>
|
||||||
{networksData.map(networkData => (
|
{networksData.map(networkData => (
|
||||||
<List.Item
|
<List.Item
|
||||||
key={networkData.chainId}
|
key={networkData.networkId}
|
||||||
title={networkData.networkName}
|
title={networkData.networkName}
|
||||||
onPress={() => handleNetworkPress(networkData)}
|
onPress={() => handleNetworkPress(networkData)}
|
||||||
/>
|
/>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
import { ScrollView } from 'react-native';
|
import { ScrollView } from 'react-native';
|
||||||
import { useForm, Controller, useWatch } from 'react-hook-form';
|
import { useForm, Controller, useWatch, FieldErrors } from 'react-hook-form';
|
||||||
import { TextInput, Button, HelperText } from 'react-native-paper';
|
import { TextInput, Button, HelperText } from 'react-native-paper';
|
||||||
import {
|
import {
|
||||||
getInternetCredentials,
|
getInternetCredentials,
|
||||||
@ -9,12 +9,14 @@ import {
|
|||||||
import { HDNode } from 'ethers/lib/utils';
|
import { HDNode } from 'ethers/lib/utils';
|
||||||
import { chains } from 'chain-registry';
|
import { chains } from 'chain-registry';
|
||||||
import { useDebouncedCallback } from 'use-debounce';
|
import { useDebouncedCallback } from 'use-debounce';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
|
||||||
import { useNavigation } from '@react-navigation/native';
|
import { useNavigation } from '@react-navigation/native';
|
||||||
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
|
|
||||||
import styles from '../styles/stylesheet';
|
import styles from '../styles/stylesheet';
|
||||||
import { NetworksDataState, NetworksFormData, StackParamsList } from '../types';
|
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';
|
||||||
@ -22,19 +24,45 @@ import { COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY } 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';
|
||||||
|
|
||||||
// TODO: Add validation to form inputs
|
const ethNetworkDataSchema = z.object({
|
||||||
|
chainId: z.string().min(1),
|
||||||
|
networkName: z.string().min(1),
|
||||||
|
rpcUrl: z.string().url(),
|
||||||
|
blockExplorerUrl: z.string().url().or(z.literal('')),
|
||||||
|
coinType: z.string().regex(/^\d+$/).min(1),
|
||||||
|
currencySymbol: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
|
const cosmosNetworkDataSchema = z.object({
|
||||||
|
chainId: z.string().min(1),
|
||||||
|
networkName: z.string().min(1),
|
||||||
|
rpcUrl: z.string().url(),
|
||||||
|
blockExplorerUrl: z.string().url().or(z.literal('')),
|
||||||
|
coinType: z.string().regex(/^\d+$/).min(1),
|
||||||
|
nativeDenom: z.string().min(1),
|
||||||
|
addressPrefix: z.string().min(1),
|
||||||
|
});
|
||||||
|
|
||||||
const AddNetwork = () => {
|
const AddNetwork = () => {
|
||||||
const navigation =
|
const navigation =
|
||||||
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
useNavigation<NativeStackNavigationProp<StackParamsList>>();
|
||||||
|
|
||||||
|
const { setNetworksData } = useNetworks();
|
||||||
|
|
||||||
|
const [namespace, setNamespace] = useState<string>(EIP155);
|
||||||
|
|
||||||
|
const networksFormDataSchema =
|
||||||
|
namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
control,
|
control,
|
||||||
formState: { errors, isValid },
|
formState: { errors },
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
setValue,
|
setValue,
|
||||||
reset,
|
reset,
|
||||||
} = useForm<NetworksDataState>({
|
} = useForm<z.infer<typeof networksFormDataSchema>>({
|
||||||
mode: 'onChange',
|
mode: 'onChange',
|
||||||
|
resolver: zodResolver(networksFormDataSchema),
|
||||||
});
|
});
|
||||||
|
|
||||||
const watchChainId = useWatch({
|
const watchChainId = useWatch({
|
||||||
@ -42,10 +70,6 @@ const AddNetwork = () => {
|
|||||||
name: 'chainId',
|
name: 'chainId',
|
||||||
});
|
});
|
||||||
|
|
||||||
const { setNetworksData } = useNetworks();
|
|
||||||
|
|
||||||
const [namespace, setNamespace] = useState<string>(EIP155);
|
|
||||||
|
|
||||||
const updateNetworkType = (newNetworkType: string) => {
|
const updateNetworkType = (newNetworkType: string) => {
|
||||||
setNamespace(newNetworkType);
|
setNamespace(newNetworkType);
|
||||||
};
|
};
|
||||||
@ -80,7 +104,7 @@ const AddNetwork = () => {
|
|||||||
}, CHAINID_DEBOUNCE_DELAY);
|
}, CHAINID_DEBOUNCE_DELAY);
|
||||||
|
|
||||||
const submit = useCallback(
|
const submit = useCallback(
|
||||||
async (data: NetworksFormData) => {
|
async (data: z.infer<typeof networksFormDataSchema>) => {
|
||||||
const newNetworkData = {
|
const newNetworkData = {
|
||||||
...data,
|
...data,
|
||||||
namespace,
|
namespace,
|
||||||
@ -110,7 +134,8 @@ const AddNetwork = () => {
|
|||||||
await getCosmosAccounts(
|
await getCosmosAccounts(
|
||||||
mnemonic,
|
mnemonic,
|
||||||
hdPath,
|
hdPath,
|
||||||
newNetworkData.addressPrefix,
|
(newNetworkData as z.infer<typeof cosmosNetworkDataSchema>)
|
||||||
|
.addressPrefix,
|
||||||
)
|
)
|
||||||
).data.address;
|
).data.address;
|
||||||
break;
|
break;
|
||||||
@ -262,7 +287,10 @@ const AddNetwork = () => {
|
|||||||
onChangeText={textValue => onChange(textValue)}
|
onChangeText={textValue => onChange(textValue)}
|
||||||
/>
|
/>
|
||||||
<HelperText type="error">
|
<HelperText type="error">
|
||||||
{errors.currencySymbol?.message}
|
{
|
||||||
|
(errors as FieldErrors<z.infer<typeof ethNetworkDataSchema>>)
|
||||||
|
.currencySymbol?.message
|
||||||
|
}
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -283,7 +311,13 @@ const AddNetwork = () => {
|
|||||||
onChangeText={textValue => onChange(textValue)}
|
onChangeText={textValue => onChange(textValue)}
|
||||||
/>
|
/>
|
||||||
<HelperText type="error">
|
<HelperText type="error">
|
||||||
{errors.nativeDenom?.message}
|
{
|
||||||
|
(
|
||||||
|
errors as FieldErrors<
|
||||||
|
z.infer<typeof cosmosNetworkDataSchema>
|
||||||
|
>
|
||||||
|
).nativeDenom?.message
|
||||||
|
}
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -302,17 +336,20 @@ const AddNetwork = () => {
|
|||||||
onChangeText={textValue => onChange(textValue)}
|
onChangeText={textValue => onChange(textValue)}
|
||||||
/>
|
/>
|
||||||
<HelperText type="error">
|
<HelperText type="error">
|
||||||
{errors.addressPrefix?.message}
|
{
|
||||||
|
(
|
||||||
|
errors as FieldErrors<
|
||||||
|
z.infer<typeof cosmosNetworkDataSchema>
|
||||||
|
>
|
||||||
|
).addressPrefix?.message
|
||||||
|
}
|
||||||
</HelperText>
|
</HelperText>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button mode="contained" onPress={handleSubmit(submit)}>
|
||||||
mode="contained"
|
|
||||||
onPress={handleSubmit(submit)}
|
|
||||||
disabled={!isValid}>
|
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
10
yarn.lock
10
yarn.lock
@ -1692,6 +1692,11 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
"@hapi/hoek" "^9.0.0"
|
"@hapi/hoek" "^9.0.0"
|
||||||
|
|
||||||
|
"@hookform/resolvers@^3.3.4":
|
||||||
|
version "3.3.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.3.4.tgz#de9b668c2835eb06892290192de6e2a5c906229b"
|
||||||
|
integrity sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==
|
||||||
|
|
||||||
"@humanwhocodes/config-array@^0.11.13":
|
"@humanwhocodes/config-array@^0.11.13":
|
||||||
version "0.11.14"
|
version "0.11.14"
|
||||||
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
|
resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b"
|
||||||
@ -9361,3 +9366,8 @@ yocto-queue@^0.1.0:
|
|||||||
version "0.1.0"
|
version "0.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
|
||||||
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
|
||||||
|
|
||||||
|
zod@^3.22.4:
|
||||||
|
version "3.22.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff"
|
||||||
|
integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==
|
||||||
|
Loading…
Reference in New Issue
Block a user