diff --git a/package.json b/package.json index 468d95b..b952e15 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "@cosmjs/proto-signing": "^0.32.3", "@cosmjs/stargate": "^0.32.3", "@ethersproject/shims": "^5.7.0", + "@hookform/resolvers": "^3.3.4", "@json-rpc-tools/utils": "^1.7.6", "@react-native-async-storage/async-storage": "^1.22.3", "@react-native-community/netinfo": "^11.3.1", @@ -46,7 +47,8 @@ "react-native-vector-icons": "^10.0.3", "react-native-vision-camera": "^3.9.0", "text-encoding-polyfill": "^0.6.7", - "use-debounce": "^10.0.0" + "use-debounce": "^10.0.0", + "zod": "^3.22.4" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/src/components/Accounts.tsx b/src/components/Accounts.tsx index 9149f3d..3568504 100644 --- a/src/components/Accounts.tsx +++ b/src/components/Accounts.tsx @@ -21,7 +21,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { const navigation = useNavigation>(); - const { accounts, setAccounts } = useAccounts(); + const { accounts, setAccounts, setCurrentIndex } = useAccounts(); const { networksData, selectedNetwork, setNetworksData, setSelectedNetwork } = useNetworks(); const [expanded, setExpanded] = useState(false); @@ -108,6 +108,7 @@ const Accounts = ({ currentIndex, updateIndex }: AccountsProps) => { ); setSelectedNetwork(updatedNetworks[0]); + setCurrentIndex(0); setDeleteNetworkDialog(false); setNetworksData(updatedNetworks); }; diff --git a/src/components/NetworkDropdown.tsx b/src/components/NetworkDropdown.tsx index 9c9fc7c..b672285 100644 --- a/src/components/NetworkDropdown.tsx +++ b/src/components/NetworkDropdown.tsx @@ -25,7 +25,7 @@ const NetworkDropdown = ({ updateNetwork }: NetworkDropdownProps) => { onPress={() => setExpanded(!expanded)}> {networksData.map(networkData => ( handleNetworkPress(networkData)} /> diff --git a/src/screens/AddNetwork.tsx b/src/screens/AddNetwork.tsx index 6562d48..608299e 100644 --- a/src/screens/AddNetwork.tsx +++ b/src/screens/AddNetwork.tsx @@ -1,6 +1,6 @@ import React, { useCallback, useEffect, useState } from 'react'; 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 { getInternetCredentials, @@ -9,12 +9,14 @@ import { 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 styles from '../styles/stylesheet'; -import { NetworksDataState, NetworksFormData, StackParamsList } from '../types'; +import { StackParamsList } from '../types'; import { SelectNetworkType } from '../components/SelectNetworkType'; import { storeNetworkData } from '../utils/accounts'; import { useNetworks } from '../context/NetworksContext'; @@ -22,19 +24,45 @@ import { COSMOS, EIP155, CHAINID_DEBOUNCE_DELAY } from '../utils/constants'; import { getCosmosAccounts } from '../utils/accounts'; 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 navigation = useNavigation>(); + const { setNetworksData } = useNetworks(); + + const [namespace, setNamespace] = useState(EIP155); + + const networksFormDataSchema = + namespace === EIP155 ? ethNetworkDataSchema : cosmosNetworkDataSchema; + const { control, - formState: { errors, isValid }, + formState: { errors }, handleSubmit, setValue, reset, - } = useForm({ + } = useForm>({ mode: 'onChange', + resolver: zodResolver(networksFormDataSchema), }); const watchChainId = useWatch({ @@ -42,10 +70,6 @@ const AddNetwork = () => { name: 'chainId', }); - const { setNetworksData } = useNetworks(); - - const [namespace, setNamespace] = useState(EIP155); - const updateNetworkType = (newNetworkType: string) => { setNamespace(newNetworkType); }; @@ -80,7 +104,7 @@ const AddNetwork = () => { }, CHAINID_DEBOUNCE_DELAY); const submit = useCallback( - async (data: NetworksFormData) => { + async (data: z.infer) => { const newNetworkData = { ...data, namespace, @@ -110,7 +134,8 @@ const AddNetwork = () => { await getCosmosAccounts( mnemonic, hdPath, - newNetworkData.addressPrefix, + (newNetworkData as z.infer) + .addressPrefix, ) ).data.address; break; @@ -262,7 +287,10 @@ const AddNetwork = () => { onChangeText={textValue => onChange(textValue)} /> - {errors.currencySymbol?.message} + { + (errors as FieldErrors>) + .currencySymbol?.message + } )} @@ -283,7 +311,13 @@ const AddNetwork = () => { onChangeText={textValue => onChange(textValue)} /> - {errors.nativeDenom?.message} + { + ( + errors as FieldErrors< + z.infer + > + ).nativeDenom?.message + } )} @@ -302,17 +336,20 @@ const AddNetwork = () => { onChangeText={textValue => onChange(textValue)} /> - {errors.addressPrefix?.message} + { + ( + errors as FieldErrors< + z.infer + > + ).addressPrefix?.message + } )} /> )} - diff --git a/yarn.lock b/yarn.lock index 4af45ab..6d3259b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1692,6 +1692,11 @@ dependencies: "@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": version "0.11.14" 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" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" 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==