diff --git a/src/components/Notification.tsx b/src/components/Notification.tsx index 5d751b6..073df75 100644 --- a/src/components/Notification.tsx +++ b/src/components/Notification.tsx @@ -1,3 +1,4 @@ +import { IconCircleX } from "@tabler/icons-react"; import type { ErrorResponse } from "@utils/api"; import { cn } from "@utils/helpers"; import classNames from "classnames"; @@ -88,7 +89,7 @@ export default function Notification({ {loading ? ( ) : error ? ( - + ) : ( icon || )} diff --git a/src/contexts/RoutesProvider.tsx b/src/contexts/RoutesProvider.tsx index 7b828a6..60a160f 100644 --- a/src/contexts/RoutesProvider.tsx +++ b/src/contexts/RoutesProvider.tsx @@ -25,7 +25,7 @@ const RoutesContext = React.createContext( ); export default function RoutesProvider({ children }: Props) { - const routeRequest = useApiCall("/routes"); + const routeRequest = useApiCall("/routes", true); const { mutate } = useSWRConfig(); const updateRoute = async ( diff --git a/src/modules/dns-nameservers/NameserverModal.tsx b/src/modules/dns-nameservers/NameserverModal.tsx index 73eedde..c883b6b 100644 --- a/src/modules/dns-nameservers/NameserverModal.tsx +++ b/src/modules/dns-nameservers/NameserverModal.tsx @@ -115,7 +115,7 @@ export function NameserverModalContent({ preset, cell, }: ModalProps) { - const nsRequest = useApiCall("/dns/nameservers"); + const nsRequest = useApiCall("/dns/nameservers", true); const { mutate } = useSWRConfig(); const isUpdate = useMemo(() => { @@ -233,24 +233,31 @@ export function NameserverModalContent({ return domains.some((d) => d.name === ""); }, [domains]); + const nameLengthError = useMemo(() => { + if (name.length > 40) return "Name should be less than 40 characters"; + return ""; + }, [name]); + const hasAnyError = useMemo(() => { return ( hasNSErrors || nsError || domainError || - name == "" || nameservers.length == 0 || hasDomainErrors || - groups.length == 0 + groups.length == 0 || + nameLengthError !== "" || + name == "" ); }, [ nsError, domainError, - name, nameservers, groups, hasNSErrors, hasDomainErrors, + nameLengthError, + name, ]); return ( @@ -427,6 +434,7 @@ export function NameserverModalContent({ setName(e.target.value)} @@ -516,7 +524,7 @@ function NameserverInput({ const validCIDR = cidr.isValidAddress(ip); if (!validCIDR) { onError && onError(true); - return "Please enter a valid CIDR, e.g., 192.168.1.0/24"; + return "Please enter a valid IP, e.g., 192.168.1.0"; } onError && onError(false); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -532,7 +540,7 @@ function NameserverInput({
1) || (peerTab === "peer-group" && routingPeerGroups.length == 0) || (peerTab === "routing-peer" && !routingPeer) || - groups.length == 0 + groups.length == 0 || + networkRange == "" ); - }, [cidrError, peerTab, routingPeerGroups.length, routingPeer, groups]); + }, [ + cidrError, + peerTab, + routingPeerGroups.length, + routingPeer, + groups, + networkRange, + ]); - const isNameEntered = useMemo(() => { - return !(networkIdentifier == ""); + const networkIdentifierError = useMemo(() => { + return (networkIdentifier?.length || 0) > 40 + ? "Network Identifier must be less than 40 characters" + : ""; }, [networkIdentifier]); + const metricError = useMemo(() => { + return parseInt(metric) < 1 || parseInt(metric) > 9999 + ? "Metric must be between 1 and 9999" + : ""; + }, [metric]); + + const isNameEntered = useMemo(() => { + return networkIdentifier != "" && networkIdentifierError == ""; + }, [networkIdentifier, networkIdentifierError]); + const canCreateOrSave = useMemo(() => { - return isNetworkEntered && isNameEntered; - }, [isNetworkEntered, isNameEntered]); + return isNetworkEntered && isNameEntered && metricError == ""; + }, [isNetworkEntered, isNameEntered, metricError]); return ( @@ -250,7 +270,10 @@ export function RouteModalContent({ /> Name & Description - + setMetric(e.target.value)} customPrefix={ @@ -469,7 +495,7 @@ export function RouteModalContent({ diff --git a/src/modules/routes/RouteUpdateModal.tsx b/src/modules/routes/RouteUpdateModal.tsx index fc4f0a9..18b86b4 100644 --- a/src/modules/routes/RouteUpdateModal.tsx +++ b/src/modules/routes/RouteUpdateModal.tsx @@ -197,14 +197,21 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) { .filter((p) => p != undefined) as string[]; }, [groupedRoute]); + const metricError = useMemo(() => { + return parseInt(metric.toString()) < 1 || parseInt(metric.toString()) > 9999 + ? "Metric must be between 1 and 9999" + : ""; + }, [metric]); + // Is button disabled const isDisabled = useMemo(() => { return ( (peerTab === "peer-group" && routingPeerGroups.length == 0) || (peerTab === "routing-peer" && !routingPeer) || - groups.length == 0 + groups.length == 0 || + metricError !== "" ); - }, [peerTab, routingPeerGroups.length, routingPeer, groups]); + }, [peerTab, routingPeerGroups.length, routingPeer, groups, metricError]); const [tab, setTab] = useState( cell && cell == "metric" ? "settings" : "network", @@ -352,6 +359,8 @@ function RouteUpdateModalContent({ onSuccess, route, cell }: ModalProps) { max={9999} maxWidthClass={"max-w-[200px]"} value={metric} + error={metricError} + errorTooltip={true} type={"number"} onChange={(e) => setMetric(e.target.value)} customPrefix={ diff --git a/src/modules/setup-keys/SetupKeyModal.tsx b/src/modules/setup-keys/SetupKeyModal.tsx index dbf6a40..c5ebd44 100644 --- a/src/modules/setup-keys/SetupKeyModal.tsx +++ b/src/modules/setup-keys/SetupKeyModal.tsx @@ -126,7 +126,7 @@ type ModalProps = { }; export function SetupKeyModalContent({ onSuccess }: ModalProps) { - const setupKeyRequest = useApiCall("/setup-keys"); + const setupKeyRequest = useApiCall("/setup-keys", true); const { mutate } = useSWRConfig(); const [name, setName] = useState(""); @@ -143,10 +143,18 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) { return reusable ? "Unlimited" : "1"; }, [reusable]); + const expiresInError = useMemo(() => { + const expires = parseInt(expiresIn); + if (expires < 1 || expires > 365) { + return "Days should be between 1 and 365"; + } + return ""; + }, [expiresIn]); + const isDisabled = useMemo(() => { const trimmedName = trim(name); - return trimmedName.length === 0; - }, [name]); + return trimmedName.length === 0 || expiresInError.length > 0; + }, [name, expiresInError]); const submit = () => { if (!selectedGroups) return; @@ -245,6 +253,8 @@ export function SetupKeyModalContent({ onSuccess }: ModalProps) { min={1} max={365} value={expiresIn} + error={expiresInError} + errorTooltip={true} type={"number"} onChange={(e) => setExpiresIn(e.target.value)} customPrefix={ diff --git a/src/utils/api.tsx b/src/utils/api.tsx index 2bd6b13..0cf9a03 100644 --- a/src/utils/api.tsx +++ b/src/utils/api.tsx @@ -169,13 +169,13 @@ export function useApiErrorHandling(ignoreError = false) { return login(currentPath); } if (err.code == 401 && err.message == "token invalid") { - return setError(err); + setError(err); } if (err.code == 500 && err.message == "internal server error") { - return setError(err); + setError(err); } if (err.code > 400 && err.code <= 500) { - return setError(err); + setError(err); } return Promise.reject(err);