diff --git a/apps/desktop/package.json b/apps/desktop/package.json
index 4bdb6051f6..ce5b26ce6a 100644
--- a/apps/desktop/package.json
+++ b/apps/desktop/package.json
@@ -150,7 +150,8 @@
"vite": "^5.4.8",
"vite-plugin-checker": "^0.8.0",
"vite-plugin-node-polyfills": "^0.17.0",
- "zod": "^3.23.8"
+ "zod": "^3.23.8",
+ "@hookform/resolvers": "^3.9.0"
},
"packageManager": "pnpm@9.9.0",
"dependencies": {
diff --git a/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx b/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx
index fd1524f60c..e6e3714f7c 100644
--- a/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx
+++ b/apps/desktop/src/views/settings/network/UpsertNetworkModal.test.tsx
@@ -12,6 +12,14 @@ beforeEach(() => {
});
describe("", () => {
+ const updatedNetwork = {
+ ...customNetwork,
+ rpcUrl: "https://rpc.com",
+ tzktApiUrl: "https://tzkt.com",
+ tzktExplorerUrl: "https://explorer.com",
+ buyTezUrl: "",
+ };
+
describe("edit mode", () => {
beforeEach(() => {
store.dispatch(networksActions.upsertNetwork(customNetwork));
@@ -27,14 +35,6 @@ describe("", () => {
const user = userEvent.setup();
render(, { store });
- const updatedNetwork = {
- ...customNetwork,
- rpcUrl: "https://rpc",
- tzktApiUrl: "https://tzkt",
- tzktExplorerUrl: "https://explorer",
- buyTezUrl: "",
- };
-
await act(() => user.clear(screen.getByLabelText("RPC URL")));
await act(() => user.clear(screen.getByLabelText("Tzkt API URL")));
await act(() => user.clear(screen.getByLabelText("Tzkt Explorer URL")));
@@ -55,14 +55,6 @@ describe("", () => {
const user = userEvent.setup();
render(, { store });
- const updatedNetwork = {
- ...customNetwork,
- rpcUrl: "https://rpc",
- tzktApiUrl: "https://tzkt",
- tzktExplorerUrl: "https://explorer",
- buyTezUrl: "",
- };
-
await act(() => user.clear(screen.getByLabelText("RPC URL")));
await act(() => user.clear(screen.getByLabelText("Tzkt API URL")));
await act(() => user.clear(screen.getByLabelText("Tzkt Explorer URL")));
@@ -85,6 +77,48 @@ describe("", () => {
});
});
+ describe("URL fields validation", () => {
+ const urlFields = [
+ { label: "RPC URL", required: true },
+ { label: "Tzkt API URL", required: true },
+ { label: "Tzkt Explorer URL", required: true },
+ { label: "Buy Tez URL", required: false },
+ ];
+
+ it.each(urlFields)("validates $label field", async ({ label, required }) => {
+ const user = userEvent.setup();
+ render(, { store });
+
+ await user.type(screen.getByLabelText(label), "invalid-url");
+ await user.tab();
+
+ await waitFor(() => {
+ expect(screen.getByText(`Enter a valid ${label}`)).toBeVisible();
+ });
+
+ await user.clear(screen.getByLabelText(label));
+ await user.tab();
+
+ if (required) {
+ await waitFor(() => {
+ expect(screen.getByText(`${label} is required`)).toBeVisible();
+ });
+ } else {
+ await waitFor(() => {
+ expect(screen.queryByText(`${label} is required`)).not.toBeInTheDocument();
+ });
+ }
+
+ await user.type(screen.getByLabelText(label), "https://valid-url.com");
+ await user.tab();
+
+ await waitFor(() => {
+ expect(screen.queryByText(`Enter a valid ${label}`)).not.toBeInTheDocument();
+ });
+ expect(screen.queryByText(`${label} is required`)).not.toBeInTheDocument();
+ });
+ });
+
describe("create mode", () => {
describe("name field", () => {
it("validates uniqueness", async () => {
diff --git a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx
index 419df9af0f..6023157048 100644
--- a/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx
+++ b/apps/desktop/src/views/settings/network/UpsertNetworkModal.tsx
@@ -9,7 +9,8 @@ import {
ModalFooter,
ModalHeader,
} from "@chakra-ui/react";
-import { useDynamicModalContext } from "@umami/components";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { getNetworkValidationScheme, useDynamicModalContext } from "@umami/components";
import { networksActions, useAvailableNetworks } from "@umami/state";
import { type Network } from "@umami/tezos";
import { useForm } from "react-hook-form";
@@ -30,7 +31,11 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
formState: { errors, isValid },
register,
handleSubmit,
- } = useForm({ mode: "onBlur", defaultValues: network });
+ } = useForm({
+ mode: "onBlur",
+ defaultValues: network,
+ resolver: zodResolver(getNetworkValidationScheme(availableNetworks, network)),
+ });
const onSubmit = (network: Network) => {
dispatch(networksActions.upsertNetwork(network));
@@ -51,12 +56,7 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
{
- if (availableNetworks.find(n => n.name === name)) {
- return "Network with this name already exists";
- }
- },
+ setValueAs: removeTrailingSlashes,
})}
/>
{errors.name && {errors.name.message}}
@@ -67,7 +67,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
@@ -78,7 +77,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
@@ -89,7 +87,6 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
@@ -98,9 +95,10 @@ export const UpsertNetworkModal = ({ network }: { network?: Network }) => {
)}
-
+
Buy Tez URL
+ {errors.buyTezUrl && {errors.buyTezUrl.message}}
-
+
Buy Tez URL
+ {errors.buyTezUrl && {errors.buyTezUrl.message}}