From 28628cbda88d249c67a49fa0ed44fbf1d6e83267 Mon Sep 17 00:00:00 2001 From: Nelito Junior Date: Mon, 6 Jan 2025 20:16:14 -0300 Subject: [PATCH] feat: Allow changing network name before submitting a "Add network" FE-1183 (#1725) This PR - Adds an input field to the review step when adding a network to allow adding multiple networks that have the same name. Specially useful for testing and development. - Also adds type safety for chainId to make sure it is always a String (Avoid controlled/uncontrolled input fields issue). ![image](https://github.com/user-attachments/assets/97635bf5-bbf9-4d24-85c9-5ff416764e01)
Sample docker-compse.yml to run two local nodes to test this PR. ``` version: '3' services: fuel-core-1: platform: linux/amd64 container_name: '${PROJECT:-fuel-node}_fuel-core-1' environment: FUEL_IP: ${FUEL_IP} FUEL_CORE_PORT: 4000 NETWORK_NAME: '${PROJECT} local 1' MIN_GAS_PRICE: ${MIN_GAS_PRICE} CONSENSUS_KEY_SECRET: ${WALLET_SECRET} build: ./fuel-core ports: - '${FUEL_CORE_PORT_1:-4000}:4000' volumes: - fuel-core-db-1:/mnt/db healthcheck: test: curl --fail http://localhost:4000/v1/health || exit 1 interval: 1s timeout: 5s retries: 20 fuel-core-2: platform: linux/amd64 container_name: '${PROJECT:-fuel-node}_fuel-core-2' environment: FUEL_IP: ${FUEL_IP} FUEL_CORE_PORT: 4001 NETWORK_NAME: '${PROJECT} local 2' MIN_GAS_PRICE: ${MIN_GAS_PRICE} CONSENSUS_KEY_SECRET: ${WALLET_SECRET} build: ./fuel-core ports: - '${FUEL_CORE_PORT_2:-4001}:4001' volumes: - fuel-core-db-2:/mnt/db healthcheck: test: curl --fail http://localhost:4001/v1/health || exit 1 interval: 1s timeout: 5s retries: 20 faucet-1: platform: linux/amd64 container_name: '${PROJECT:-fuel-node}_faucet-1' environment: MIN_GAS_PRICE: ${MIN_GAS_PRICE} WALLET_SECRET_KEY: ${WALLET_SECRET} DISPENSE_AMOUNT: ${DISPENSE_AMOUNT} FUEL_NODE_URL: http://${PROJECT:-fuel-node}_fuel-core-1:4000/v1/graphql image: ghcr.io/fuellabs/faucet:4f7bec0 ports: - '${FUEL_FAUCET_PORT_1:-4040}:3000' links: - fuel-core-1 depends_on: fuel-core-1: condition: service_healthy faucet-2: platform: linux/amd64 container_name: '${PROJECT:-fuel-node}_faucet-2' environment: MIN_GAS_PRICE: ${MIN_GAS_PRICE} WALLET_SECRET_KEY: ${WALLET_SECRET} DISPENSE_AMOUNT: ${DISPENSE_AMOUNT} FUEL_NODE_URL: http://${PROJECT:-fuel-node}_fuel-core-2:4001/v1/graphql image: ghcr.io/fuellabs/faucet:4f7bec0 ports: - '${FUEL_FAUCET_PORT_2:-4041}:3000' links: - fuel-core-2 depends_on: fuel-core-2: condition: service_healthy volumes: fuel-core-db-1: name: '${PROJECT:-fuel-node}_fuel-core-db-1' fuel-core-db-2: name: '${PROJECT:-fuel-node}_fuel-core-db-2' ```
--------- Co-authored-by: Luiz Gomes <8636507+LuizAsFight@users.noreply.github.com> --- .changeset/hip-beds-do.md | 5 ++ .../components/NetworkForm/NetworkForm.tsx | 49 ++++++++++++++++--- .../systems/Network/hooks/useNetworkForm.ts | 35 +++++++++---- .../Network/machines/networksMachine.ts | 5 +- .../Network/pages/AddNetwork/AddNetwork.tsx | 30 ++++++++---- .../src/systems/Network/services/network.ts | 8 ++- 6 files changed, 102 insertions(+), 30 deletions(-) create mode 100644 .changeset/hip-beds-do.md diff --git a/.changeset/hip-beds-do.md b/.changeset/hip-beds-do.md new file mode 100644 index 0000000000..e3b5f68a1a --- /dev/null +++ b/.changeset/hip-beds-do.md @@ -0,0 +1,5 @@ +--- +"fuels-wallet": patch +--- + +Allow editing network name when adding. diff --git a/packages/app/src/systems/Network/components/NetworkForm/NetworkForm.tsx b/packages/app/src/systems/Network/components/NetworkForm/NetworkForm.tsx index 0eebab47de..b8615028aa 100644 --- a/packages/app/src/systems/Network/components/NetworkForm/NetworkForm.tsx +++ b/packages/app/src/systems/Network/components/NetworkForm/NetworkForm.tsx @@ -40,10 +40,17 @@ export function NetworkForm({ }: NetworkFormProps) { const [isFirstShownTestConnectionBtn, setIsFirstShownTestConnectionBtn] = useState(false); - const { control, formState } = form; + const { control, formState, setValue } = form; const url = useWatch({ control, name: 'url' }); const chainId = useWatch({ control, name: 'chainId' }); + const customName = useWatch({ control, name: 'name' }); + + useEffect(() => { + if (isReviewing && chainName) { + setValue('name', chainName); + } + }, [isReviewing, chainName, setValue]); useEffect(() => { if (isValid && chainId) { @@ -54,12 +61,40 @@ export function NetworkForm({ return ( {isReviewing && ( - + <> + + + Network Name + + } + isRequired + isInvalid={Boolean(formState.errors?.name)} + render={({ field }) => ( + + + + )} + /> + {formState.errors?.name && ( + + {formState.errors.name.message} + + )} + )} {!isReviewing && ( <> diff --git a/packages/app/src/systems/Network/hooks/useNetworkForm.ts b/packages/app/src/systems/Network/hooks/useNetworkForm.ts index f380393b70..f4ebbed473 100644 --- a/packages/app/src/systems/Network/hooks/useNetworkForm.ts +++ b/packages/app/src/systems/Network/hooks/useNetworkForm.ts @@ -1,3 +1,4 @@ +import type { NetworkData } from '@fuel-wallet/types'; import { yupResolver } from '@hookform/resolvers/yup'; import { useEffect } from 'react'; import { useForm } from 'react-hook-form'; @@ -12,11 +13,13 @@ const schema = yup .object({ name: yup .string() + .default('') .test('is-required', 'Name is required', function (value) { return !this.options?.context?.isEditing || !!value; }), url: yup .string() + .default('') .test('is-url-valid', 'URL is not valid', isValidNetworkUrl) .test('is-network-valid', 'Network is not valid', function (url) { return ( @@ -26,6 +29,7 @@ const schema = yup .required('URL is required'), explorerUrl: yup .string() + .default('') .test( 'is-url-valid', 'Explorer URL is not valid', @@ -33,10 +37,8 @@ const schema = yup ) .optional(), chainId: yup - .mixed() - .transform((value) => - value != null && value !== '' ? Number(value) : undefined - ) + .string() + .default('') .required('Chain ID is required') .test( 'chainId-match', @@ -51,22 +53,26 @@ const schema = yup .test( 'is-numbers-only', 'Chain ID must contain only numbers', - (value) => value == null || Number.isInteger(value) + (value) => { + if (!value) return true; + const num = Number(value); + return !Number.isNaN(num) && Number.isInteger(num); + } ), }) .required(); -const DEFAULT_VALUES = { +const DEFAULT_VALUES: NetworkFormValues = { name: '', url: '', explorerUrl: '', - chainId: undefined, + chainId: '', }; export type UseNetworkFormReturn = ReturnType; export type UseAddNetworkOpts = { - defaultValues?: Maybe; + defaultValues?: Maybe>; context?: { providerChainId?: number; isEditing?: boolean; @@ -82,13 +88,22 @@ export function useNetworkForm({ defaultValues, context }: UseAddNetworkOpts) { resetOptions: { keepValues: true, }, - defaultValues: defaultValues || DEFAULT_VALUES, + defaultValues: defaultValues + ? { + ...DEFAULT_VALUES, + ...defaultValues, + chainId: defaultValues.chainId?.toString() || '', + } + : DEFAULT_VALUES, context, }); useEffect(() => { if (defaultValues) { - form.reset(defaultValues); + form.reset({ + ...defaultValues, + chainId: defaultValues.chainId?.toString() || '', + }); } }, [defaultValues, form]); diff --git a/packages/app/src/systems/Network/machines/networksMachine.ts b/packages/app/src/systems/Network/machines/networksMachine.ts index d357069227..6611f52d62 100644 --- a/packages/app/src/systems/Network/machines/networksMachine.ts +++ b/packages/app/src/systems/Network/machines/networksMachine.ts @@ -146,8 +146,11 @@ export const networksMachine = createMachine( }, onDone: [ { - target: 'idle', + target: 'waitingAddNetwork', cond: FetchMachine.hasError, + actions: assign({ + error: (_, ev) => ev.data, + }), }, { actions: [ diff --git a/packages/app/src/systems/Network/pages/AddNetwork/AddNetwork.tsx b/packages/app/src/systems/Network/pages/AddNetwork/AddNetwork.tsx index b31643cf84..ca140f5388 100644 --- a/packages/app/src/systems/Network/pages/AddNetwork/AddNetwork.tsx +++ b/packages/app/src/systems/Network/pages/AddNetwork/AddNetwork.tsx @@ -31,6 +31,7 @@ export function AddNetwork() { const form = useNetworkForm({ context }); const url = useWatch({ control: form.control, name: 'url' }); const chainId = useWatch({ control: form.control, name: 'chainId' }); + const name = useWatch({ control: form.control, name: 'name' }); const isValid = form.formState.isDirty && form.formState.isValid && @@ -61,15 +62,24 @@ export function AddNetwork() { }); } - function onAddNetwork() { - const name = chainInfoToAdd?.name || ''; - handlers.addNetwork({ - data: { - chainId: Number(chainId), - name, - url, - }, - }); + async function onAddNetwork() { + if (!name) return; + try { + await handlers.addNetwork({ + data: { + chainId: Number(chainId), + name, + url, + }, + }); + } catch (error) { + if (error instanceof Error && error.message.includes('already exists')) { + form.setError('name', { + type: 'manual', + message: 'A network with this name already exists', + }); + } + } } return ( @@ -101,7 +111,7 @@ export function AddNetwork() {