Skip to content

Commit

Permalink
feat: Allow changing network name before submitting a "Add network" F…
Browse files Browse the repository at this point in the history
…E-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)

<details>

<summary>Sample docker-compse.yml to run two local nodes to test this
PR.</summary>

```
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'
```
</details>

---------

Co-authored-by: Luiz Gomes <[email protected]>
  • Loading branch information
nelitow and LuizAsFight authored Jan 6, 2025
1 parent 0a3fbed commit 28628cb
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 30 deletions.
5 changes: 5 additions & 0 deletions .changeset/hip-beds-do.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

Allow editing network name when adding.
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -54,12 +61,40 @@ export function NetworkForm({
return (
<Box.Stack css={{ width: '100%' }} gap="$4">
{isReviewing && (
<NetworkReviewCard
headerText="You're adding this network"
name={chainName || ''}
chainId={chainId}
url={url}
/>
<>
<NetworkReviewCard
headerText="You're adding this network"
name={customName || chainName || ''}
chainId={chainId}
url={url}
/>
<ControlledField
control={control}
name="name"
label={
<HelperIcon message="Customize the network name to avoid conflicts">
Network Name
</HelperIcon>
}
isRequired
isInvalid={Boolean(formState.errors?.name)}
render={({ field }) => (
<MotionInput {...animations.slideInTop()}>
<Input.Field
{...field}
id="network-name"
aria-label="Network name"
placeholder={chainName || 'Enter network name'}
/>
</MotionInput>
)}
/>
{formState.errors?.name && (
<Form.ErrorMessage>
{formState.errors.name.message}
</Form.ErrorMessage>
)}
</>
)}
{!isReviewing && (
<>
Expand Down
35 changes: 25 additions & 10 deletions packages/app/src/systems/Network/hooks/useNetworkForm.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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 (
Expand All @@ -26,17 +29,16 @@ const schema = yup
.required('URL is required'),
explorerUrl: yup
.string()
.default('')
.test(
'is-url-valid',
'Explorer URL is not valid',
(url) => !url || isValidNetworkUrl(url)
)
.optional(),
chainId: yup
.mixed<string | number>()
.transform((value) =>
value != null && value !== '' ? Number(value) : undefined
)
.string()
.default('')
.required('Chain ID is required')
.test(
'chainId-match',
Expand All @@ -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<typeof useNetworkForm>;

export type UseAddNetworkOpts = {
defaultValues?: Maybe<NetworkFormValues>;
defaultValues?: Maybe<Partial<NetworkData>>;
context?: {
providerChainId?: number;
isEditing?: boolean;
Expand All @@ -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]);

Expand Down
5 changes: 4 additions & 1 deletion packages/app/src/systems/Network/machines/networksMachine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,11 @@ export const networksMachine = createMachine(
},
onDone: [
{
target: 'idle',
target: 'waitingAddNetwork',
cond: FetchMachine.hasError,
actions: assign({
error: (_, ev) => ev.data,
}),
},
{
actions: [
Expand Down
30 changes: 20 additions & 10 deletions packages/app/src/systems/Network/pages/AddNetwork/AddNetwork.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -101,7 +111,7 @@ export function AddNetwork() {
<Button
onPress={onAddNetwork}
intent="primary"
isDisabled={!isReviewingAddNetwork}
isDisabled={!isReviewingAddNetwork || !name}
isLoading={isLoading}
leftIcon={<Icon icon="Plus" />}
aria-label="Add new network"
Expand Down
8 changes: 6 additions & 2 deletions packages/app/src/systems/Network/services/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,8 +353,12 @@ export class NetworkService {
name,
url,
});
if (network) {
throw new Error('Network with Name or URL already exists');

if (network?.url === url) {
throw new Error('Network with same URL already exists');
}
if (network?.name === name) {
throw new Error('Network with same Name already exists');
}
}

Expand Down

0 comments on commit 28628cb

Please sign in to comment.