diff --git a/src/components/domain-input/index.tsx b/src/components/domain-input/index.tsx new file mode 100644 index 0000000..1f92995 --- /dev/null +++ b/src/components/domain-input/index.tsx @@ -0,0 +1,48 @@ +import React from "react"; +import { Icons } from "../icons"; +import "./styles.scss"; + +type DomainInputProbs = { + id?: string; + name?: string; + value: string; + setValue: React.Dispatch>; + root_domain: string; + placeholder?: string; + is_valid: boolean; +}; + +export const DomainInput = ({ + id, + name, + value, + setValue, + root_domain, + is_valid, + placeholder, +}: DomainInputProbs) => { + return ( + <> +
+
+ setValue(e.target.value)} + /> +
+ {is_valid ? : } +
+
+
+

{root_domain}

+
+
+ + ); +}; + +export default DomainInput; diff --git a/src/components/domain-input/styles.scss b/src/components/domain-input/styles.scss new file mode 100644 index 0000000..bc35b91 --- /dev/null +++ b/src/components/domain-input/styles.scss @@ -0,0 +1,55 @@ +@use "../../styles/vars"; +@use "../../styles/mixins"; +@use "@jambonz/ui-kit/src/styles/vars" as ui-vars; +@use "@jambonz/ui-kit/src/styles/mixins" as ui-mixins; + +.input-container { + position: relative; + display: inline-block; + width: 100%; +} + +.clipboard-domain { + display: flex; + align-items: center; + + input[type="text"], + input[type="number"] { + border-bottom-right-radius: 0; + border-top-right-radius: 0; + width: 100%; + height: vars.$clipheight; + + &:focus-visible { + outline: 0; + } + } + + .internal form & { + max-width: calc(#{vars.$widthinput} - #{vars.$clipheight}); + } + + .input-icon { + position: absolute; + right: 5%; + top: 50%; + transform: translateY(-50%); + border-left: 0; + } + + .root-domain { + height: vars.$clipheight; + border-bottom-right-radius: ui-vars.$px01; + border-top-right-radius: ui-vars.$px01; + border: 2px solid ui-vars.$grey; + border-left: 0; + background-color: ui-vars.$pink; + padding: ui-vars.$px01; + display: flex; + align-items: center; + + &[disabled] { + @include mixins.disabled(); + } + } +} diff --git a/src/containers/internal/views/accounts/edit-sip-realm.tsx b/src/containers/internal/views/accounts/edit-sip-realm.tsx index b254fcd..8a55a2e 100644 --- a/src/containers/internal/views/accounts/edit-sip-realm.tsx +++ b/src/containers/internal/views/accounts/edit-sip-realm.tsx @@ -1,41 +1,59 @@ import { Button, ButtonGroup, H1, MS } from "@jambonz/ui-kit"; -import React, { useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Link, useNavigate } from "react-router-dom"; import { getAvailability, postSipRealms, useApiData } from "src/api"; import { CurrentUserData } from "src/api/types"; import { Section } from "src/components"; +import DomainInput from "src/components/domain-input"; import { Message } from "src/components/forms"; import { ROUTE_INTERNAL_ACCOUNTS } from "src/router/routes"; +import { hasValue } from "src/utils"; export const EditSipRealm = () => { const [name, setName] = useState(""); const [errorMessage, setErrorMessage] = useState(""); const navigate = useNavigate(); const [userData] = useApiData("Users/me"); + const typingTimeoutRef = useRef(null); + const [isValidDomain, setIsValidDomain] = useState(false); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); const rootDomain = userData?.account?.root_domain; const account_sid = userData?.account?.account_sid; - getAvailability(`${name}.${rootDomain}`) - .then(({ json }) => { - if (!json.available) { - setErrorMessage("That subdomain is not available."); - return; - } - postSipRealms(account_sid || "", `${name}.${rootDomain}`) - .then(() => { - navigate(`${ROUTE_INTERNAL_ACCOUNTS}/${account_sid}/edit`); - }) - .catch((error) => { - setErrorMessage(error.msg); - }); + postSipRealms(account_sid || "", `${name}.${rootDomain}`) + .then(() => { + navigate(`${ROUTE_INTERNAL_ACCOUNTS}/${account_sid}/edit`); }) .catch((error) => { setErrorMessage(error.msg); }); }; + + useEffect(() => { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + if (!name || name.length < 3) { + setIsValidDomain(false); + return; + } + setIsValidDomain(false); + typingTimeoutRef.current = setTimeout(() => { + getAvailability(`${name}.${userData?.account?.root_domain}`) + .then(({ json }) => + setIsValidDomain( + Boolean(json.available) && hasValue(name) && name.length != 0 + ) + ) + .catch((error) => { + setErrorMessage(error.msg); + setIsValidDomain(false); + }); + }, 500); + }, [name]); + return ( <>

Edit Sip Realm

@@ -48,18 +66,15 @@ export const EditSipRealm = () => { {errorMessage && }
- setName(e.target.value)} + setValue={setName} + placeholder="Your name here" + root_domain={`.${userData?.account?.root_domain || ""}`} + is_valid={isValidDomain} /> -
@@ -71,7 +86,7 @@ export const EditSipRealm = () => { > Cancel - diff --git a/src/containers/internal/views/carriers/index.tsx b/src/containers/internal/views/carriers/index.tsx index f61b424..9a195b8 100644 --- a/src/containers/internal/views/carriers/index.tsx +++ b/src/containers/internal/views/carriers/index.tsx @@ -30,16 +30,24 @@ import { API_SIP_GATEWAY, API_SMPP_GATEWAY, CARRIER_REG_OK, + ENABLE_HOSTED_SYSTEM, USER_ACCOUNT, } from "src/api/constants"; import { DeleteCarrier } from "./delete"; -import type { Account, Carrier, SipGateway, SmppGateway } from "src/api/types"; +import type { + Account, + Carrier, + CurrentUserData, + SipGateway, + SmppGateway, +} from "src/api/types"; import { Scope } from "src/store/types"; import { getAccountFilter, setLocation } from "src/store/localStore"; export const Carriers = () => { const user = useSelectState("user"); + const [userData] = useApiData("Users/me"); const currentServiceProvider = useSelectState("currentServiceProvider"); const [apiUrl, setApiUrl] = useState(""); const [carrier, setCarrier] = useState(null); @@ -130,7 +138,16 @@ export const Carriers = () => { return ( <>
-

Carriers

+
+

Carriers

+ {ENABLE_HOSTED_SYSTEM && ( + + Have your carrier send calls to{" "} + {userData?.account?.sip_realm} + + )} +
+ {" "} diff --git a/src/containers/login/sub-domain.tsx b/src/containers/login/sub-domain.tsx index 1c3d8ba..7f64661 100644 --- a/src/containers/login/sub-domain.tsx +++ b/src/containers/login/sub-domain.tsx @@ -1,41 +1,58 @@ import { Button, H1, MS } from "@jambonz/ui-kit"; -import React, { useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { useNavigate } from "react-router-dom"; import { getAvailability, postSipRealms } from "src/api"; +import DomainInput from "src/components/domain-input"; import { Message } from "src/components/forms"; import { getToken, parseJwt } from "src/router/auth"; import { ROUTE_INTERNAL_ACCOUNTS } from "src/router/routes"; import { getRootDomain } from "src/store/localStore"; import { UserData } from "src/store/types"; +import { hasValue } from "src/utils"; export const RegisterChooseSubdomain = () => { const [name, setName] = useState(""); const [errorMessage, setErrorMessage] = useState(""); + const [isValidDomain, setIsValidDomain] = useState(false); const rootDomain = getRootDomain(); const userData: UserData = parseJwt(getToken()); const navigate = useNavigate(); + const typingTimeoutRef = useRef(null); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); setErrorMessage(""); - getAvailability(`${name}.${rootDomain}`) - .then(({ json }) => { - if (!json.available) { - setErrorMessage("That subdomain is not available."); - return; - } - postSipRealms(userData.account_sid || "", `${name}.${rootDomain}`) - .then(() => { - navigate(`${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`); - }) - .catch((error) => { - setErrorMessage(error.msg); - }); + postSipRealms(userData.account_sid || "", `${name}.${rootDomain}`) + .then(() => { + navigate(`${ROUTE_INTERNAL_ACCOUNTS}/${userData.account_sid}/edit`); }) .catch((error) => { setErrorMessage(error.msg); }); }; + + useEffect(() => { + if (typingTimeoutRef.current) { + clearTimeout(typingTimeoutRef.current); + } + if (!name || name.length < 3) { + setIsValidDomain(false); + return; + } + setIsValidDomain(false); + typingTimeoutRef.current = setTimeout(() => { + getAvailability(`${name}.${rootDomain}`) + .then(({ json }) => + setIsValidDomain( + Boolean(json.available) && hasValue(name) && name.length != 0 + ) + ) + .catch((error) => { + setErrorMessage(error.msg); + setIsValidDomain(false); + }); + }, 500); + }, [name]); return ( <>

Choose a subdomain

@@ -46,15 +63,18 @@ export const RegisterChooseSubdomain = () => { This will be the FQDN where your carrier will send calls, and where you can register devices to. This can be changed at any time. - setName(e.target.value)} + setValue={setName} + placeholder="Your name here" + root_domain={rootDomain ? `.${rootDomain}` : ""} + is_valid={isValidDomain} /> - + );