diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 3717855e..e8c85487 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirecting to login...", "autoLoginError": "Auto Login Error", "autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.", - "autoLoginErrorGeneratingUrl": "Failed to generate authentication URL." + "autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.", + "internationaldomaindetected": "International Domain Detected", + "willbestoredas": "Will be stored as:" } diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index fc03108a..fe9530d3 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirecting to login...", "autoLoginError": "Auto Login Error", "autoLoginErrorNoRedirectUrl": "No redirect URL received from the identity provider.", - "autoLoginErrorGeneratingUrl": "Failed to generate authentication URL." + "autoLoginErrorGeneratingUrl": "Failed to generate authentication URL.", + "internationaldomaindetected": "Detekována mezinárodní doména", + "willbestoredas": "Bude uloženo jako:" } diff --git a/messages/de-DE.json b/messages/de-DE.json index 062b61af..c79a5f64 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Weiterleitung zur Anmeldung...", "autoLoginError": "Fehler bei der automatischen Anmeldung", "autoLoginErrorNoRedirectUrl": "Keine Weiterleitungs-URL vom Identitätsanbieter erhalten.", - "autoLoginErrorGeneratingUrl": "Fehler beim Generieren der Authentifizierungs-URL." + "autoLoginErrorGeneratingUrl": "Fehler beim Generieren der Authentifizierungs-URL.", + "internationaldomaindetected": "Internationale Domäne erkannt", + "willbestoredas": "Wird gespeichert als:" } diff --git a/messages/en-US.json b/messages/en-US.json index f9bb4f6b..dbfa817e 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -1494,5 +1494,7 @@ "documentation": "documentation" }, "convertButton": "Convert This Node to Managed Self-Hosted" - } + }, + "internationaldomaindetected": "International Domain Detected", + "willbestoredas": "Will be stored as:" } diff --git a/messages/es-ES.json b/messages/es-ES.json index 2b49d9bb..0bb67191 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirigiendo al inicio de sesión...", "autoLoginError": "Error de inicio de sesión automático", "autoLoginErrorNoRedirectUrl": "No se recibió URL de redirección del proveedor de identidad.", - "autoLoginErrorGeneratingUrl": "Error al generar URL de autenticación." + "autoLoginErrorGeneratingUrl": "Error al generar URL de autenticación.", + "internationaldomaindetected": "Dominio internacional detectado", + "willbestoredas": "Se almacenará como: " } diff --git a/messages/fr-FR.json b/messages/fr-FR.json index ee08d77b..28bcce6a 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirection vers la connexion...", "autoLoginError": "Erreur de connexion automatique", "autoLoginErrorNoRedirectUrl": "Aucune URL de redirection reçue du fournisseur d'identité.", - "autoLoginErrorGeneratingUrl": "Échec de la génération de l'URL d'authentification." + "autoLoginErrorGeneratingUrl": "Échec de la génération de l'URL d'authentification.", + "internationaldomaindetected": "Domaine international détecté", + "willbestoredas": "Sera stocké comme:" } diff --git a/messages/it-IT.json b/messages/it-IT.json index 83708597..b41079c3 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Reindirizzamento al login...", "autoLoginError": "Errore di Accesso Automatico", "autoLoginErrorNoRedirectUrl": "Nessun URL di reindirizzamento ricevuto dal provider di identità.", - "autoLoginErrorGeneratingUrl": "Impossibile generare l'URL di autenticazione." + "autoLoginErrorGeneratingUrl": "Impossibile generare l'URL di autenticazione.", + "internationaldomaindetected": "Rilevato dominio internazionale", + "willbestoredas": "Verrà archiviato come:" } diff --git a/messages/ko-KR.json b/messages/ko-KR.json index b13dd19d..4b27bf55 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "로그인으로 리디렉션 중...", "autoLoginError": "자동 로그인 오류", "autoLoginErrorNoRedirectUrl": "ID 공급자로부터 리디렉션 URL을 받지 못했습니다.", - "autoLoginErrorGeneratingUrl": "인증 URL 생성 실패." + "autoLoginErrorGeneratingUrl": "인증 URL 생성 실패.", + "internationaldomaindetected": "국제 도메인 감지됨", + "willbestoredas": "다음과 같이 저장됩니다." } diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 76c28cb5..91e0be86 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirecting naar inloggen...", "autoLoginError": "Auto Login Fout", "autoLoginErrorNoRedirectUrl": "Geen redirect URL ontvangen van de identity provider.", - "autoLoginErrorGeneratingUrl": "Genereren van authenticatie-URL mislukt." + "autoLoginErrorGeneratingUrl": "Genereren van authenticatie-URL mislukt.", + "internationaldomaindetected": "Internationaal Domein Gedetecteerd", + "willbestoredas": "Wordt opgeslagen als:" } diff --git a/messages/pl-PL.json b/messages/pl-PL.json index be33709c..9491a94d 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Przekierowanie do logowania...", "autoLoginError": "Błąd automatycznego logowania", "autoLoginErrorNoRedirectUrl": "Nie otrzymano URL przekierowania od dostawcy tożsamości.", - "autoLoginErrorGeneratingUrl": "Nie udało się wygenerować URL uwierzytelniania." + "autoLoginErrorGeneratingUrl": "Nie udało się wygenerować URL uwierzytelniania.", + "internationaldomaindetected": "Wykryto domenę międzynarodową", + "willbestoredas": "Będzie przechowywane jako:" } diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 4efd0237..050280fa 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Redirecionando para login...", "autoLoginError": "Erro de Login Automático", "autoLoginErrorNoRedirectUrl": "Nenhum URL de redirecionamento recebido do provedor de identidade.", - "autoLoginErrorGeneratingUrl": "Falha ao gerar URL de autenticação." + "autoLoginErrorGeneratingUrl": "Falha ao gerar URL de autenticação.", + "internationaldomaindetected": "Domínio internacional detetado", + "willbestoredas": "Será armazenado como:" } diff --git a/messages/ru-RU.json b/messages/ru-RU.json index ed44702d..eef9aad6 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Перенаправление к входу...", "autoLoginError": "Ошибка автоматического входа", "autoLoginErrorNoRedirectUrl": "URL-адрес перенаправления не получен от провайдера удостоверения.", - "autoLoginErrorGeneratingUrl": "Не удалось сгенерировать URL-адрес аутентификации." + "autoLoginErrorGeneratingUrl": "Не удалось сгенерировать URL-адрес аутентификации.", + "internationaldomaindetected": "Обнаружен международный домен", + "willbestoredas": "Будет сохранен как:" } diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 89de6876..09115e9a 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "Girişe yönlendiriliyorsunuz...", "autoLoginError": "Otomatik Giriş Hatası", "autoLoginErrorNoRedirectUrl": "Kimlik sağlayıcıdan yönlendirme URL'si alınamadı.", - "autoLoginErrorGeneratingUrl": "Kimlik doğrulama URL'si oluşturulamadı." + "autoLoginErrorGeneratingUrl": "Kimlik doğrulama URL'si oluşturulamadı.", + "internationaldomaindetected": "Uluslararası Etki Alanı Algılandı", + "willbestoredas": "Şu şekilde saklanacaktır:" } diff --git a/messages/zh-CN.json b/messages/zh-CN.json index 06cd8549..dee4cff1 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -1457,5 +1457,7 @@ "autoLoginRedirecting": "重定向到登录...", "autoLoginError": "自动登录错误", "autoLoginErrorNoRedirectUrl": "未从身份提供商收到重定向URL。", - "autoLoginErrorGeneratingUrl": "生成身份验证URL失败。" + "autoLoginErrorGeneratingUrl": "生成身份验证URL失败。", + "internationaldomaindetected": "检测到国际域名", + "willbestoredas": "将存储为:" } diff --git a/src/app/[orgId]/settings/domains/CreateDomainForm.tsx b/src/app/[orgId]/settings/domains/CreateDomainForm.tsx index 31bf82f1..60b5fa02 100644 --- a/src/app/[orgId]/settings/domains/CreateDomainForm.tsx +++ b/src/app/[orgId]/settings/domains/CreateDomainForm.tsx @@ -7,12 +7,13 @@ import { FormField, FormItem, FormLabel, - FormMessage + FormMessage, + FormDescription } from "@app/components/ui/form"; import { Input } from "@app/components/ui/input"; import { useToast } from "@app/hooks/useToast"; import { zodResolver } from "@hookform/resolvers/zod"; -import { useState } from "react"; +import { useState, useMemo } from "react"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { @@ -33,7 +34,7 @@ import { CreateDomainResponse } from "@server/routers/domain/createOrgDomain"; import { StrategySelect } from "@app/components/StrategySelect"; import { AxiosResponse } from "axios"; import { Alert, AlertDescription, AlertTitle } from "@app/components/ui/alert"; -import { InfoIcon, AlertTriangle } from "lucide-react"; +import { InfoIcon, AlertTriangle, Globe } from "lucide-react"; import CopyToClipboard from "@app/components/CopyToClipboard"; import { InfoSection, @@ -43,9 +44,58 @@ import { } from "@app/components/InfoSection"; import { useOrgContext } from "@app/hooks/useOrgContext"; import { build } from "@server/build"; +import { toASCII, toUnicode } from 'punycode'; + + +// Helper functions for Unicode domain handling +function toPunycode(domain: string): string { + try { + const parts = toASCII(domain); + return parts; + } catch (error) { + return domain.toLowerCase(); + } +} + +function fromPunycode(domain: string): string { + try { + const parts = toUnicode(domain) + return parts; + } catch (error) { + return domain; + } +} + +function isValidDomainFormat(domain: string): boolean { + const unicodeRegex = /^(?!:\/\/)([^\s.]+\.)*[^\s.]+$/; + + if (!unicodeRegex.test(domain)) { + return false; + } + + const parts = domain.split('.'); + for (const part of parts) { + if (part.length === 0 || part.startsWith('-') || part.endsWith('-')) { + return false; + } + if (part.length > 63) { + return false; + } + } + + if (domain.length > 253) { + return false; + } + + return true; +} const formSchema = z.object({ - baseDomain: z.string().min(1, "Domain is required"), + baseDomain: z + .string() + .min(1, "Domain is required") + .refine((val) => isValidDomainFormat(val), "Invalid domain format") + .transform((val) => toPunycode(val)), type: z.enum(["ns", "cname", "wildcard"]) }); @@ -109,8 +159,14 @@ export default function CreateDomainForm({ } } - const domainType = form.watch("type"); const baseDomain = form.watch("baseDomain"); + const domainInputValue = form.watch("baseDomain") || ""; + + const punycodePreview = useMemo(() => { + if (!domainInputValue) return ""; + const punycode = toPunycode(domainInputValue); + return punycode !== domainInputValue.toLowerCase() ? punycode : ""; + }, [domainInputValue]); let domainOptions: any = []; if (build == "enterprise" || build == "saas") { @@ -182,10 +238,23 @@ export default function CreateDomainForm({ {t("domain")} + {punycodePreview && ( + + + + {t("internationaldomaindetected")} + +
+

{t("willbestoredas")} {punycodePreview}

+
+
+
+
+ )} )} @@ -206,66 +275,73 @@ export default function CreateDomainForm({
{createdDomain.nsRecords && - createdDomain.nsRecords.length > 0 && ( -
-

- {t("createDomainNsRecords")} -

- - - - {t("createDomainRecord")} - - -
-
- - {t( - "createDomainType" - )} - - - NS - -
-
- - {t( - "createDomainName" - )} - - - {baseDomain} - -
- - {t( - "createDomainValue" - )} - - {createdDomain.nsRecords.map( - ( - nsRecord, - index - ) => ( -
- + createdDomain.nsRecords.length > 0 && ( +
+

+ {t("createDomainNsRecords")} +

+ + + + {t("createDomainRecord")} + + +
+
+ + {t( + "createDomainType" + )} + + + NS + +
+
+ + {t( + "createDomainName" + )} + +
+ + {fromPunycode(baseDomain)} + + {fromPunycode(baseDomain) !== baseDomain && ( + + ({baseDomain}) + + )}
- ) - )} -
- - - -
- )} +
+ + {t( + "createDomainValue" + )} + + {createdDomain.nsRecords.map( + ( + nsRecord, + index + ) => ( +
+ +
+ ) + )} +
+ + + +
+ )} {createdDomain.cnameRecords && createdDomain.cnameRecords.length > 0 && ( @@ -307,11 +383,16 @@ export default function CreateDomainForm({ "createDomainName" )} - - { - cnameRecord.baseDomain - } - +
+ + {fromPunycode(cnameRecord.baseDomain)} + + {fromPunycode(cnameRecord.baseDomain) !== cnameRecord.baseDomain && ( + + ({cnameRecord.baseDomain}) + + )} +
@@ -374,11 +455,16 @@ export default function CreateDomainForm({ "createDomainName" )} - - { - aRecord.baseDomain - } - +
+ + {fromPunycode(aRecord.baseDomain)} + + {fromPunycode(aRecord.baseDomain) !== aRecord.baseDomain && ( + + ({aRecord.baseDomain}) + + )} +
@@ -390,7 +476,7 @@ export default function CreateDomainForm({ { aRecord.value } - +
@@ -440,11 +526,16 @@ export default function CreateDomainForm({ "createDomainName" )} - - { - txtRecord.baseDomain - } - +
+ + {fromPunycode(txtRecord.baseDomain)} + + {fromPunycode(txtRecord.baseDomain) !== txtRecord.baseDomain && ( + + ({txtRecord.baseDomain}) + + )} +
@@ -513,4 +604,4 @@ export default function CreateDomainForm({ ); -} +} \ No newline at end of file diff --git a/src/app/[orgId]/settings/domains/page.tsx b/src/app/[orgId]/settings/domains/page.tsx index d20e431f..c85fe10d 100644 --- a/src/app/[orgId]/settings/domains/page.tsx +++ b/src/app/[orgId]/settings/domains/page.tsx @@ -9,6 +9,7 @@ import { GetOrgResponse } from "@server/routers/org"; import { redirect } from "next/navigation"; import OrgProvider from "@app/providers/OrgProvider"; import { ListDomainsResponse } from "@server/routers/domain"; +import { toUnicode } from 'punycode'; type Props = { params: Promise<{ orgId: string }>; @@ -22,7 +23,13 @@ export default async function DomainsPage(props: Props) { const res = await internal.get< AxiosResponse >(`/org/${params.orgId}/domains`, await authCookieHeader()); - domains = res.data.data.domains as DomainRow[]; + + const rawDomains = res.data.data.domains as DomainRow[]; + + domains = rawDomains.map((domain) => ({ + ...domain, + baseDomain: toUnicode(domain.baseDomain), + })); } catch (e) { console.error(e); } diff --git a/src/app/[orgId]/settings/resources/[resourceId]/CustomDomainInput.tsx b/src/app/[orgId]/settings/resources/[resourceId]/CustomDomainInput.tsx index 0764d740..171f5683 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/CustomDomainInput.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/CustomDomainInput.tsx @@ -9,6 +9,7 @@ import { SelectTrigger, SelectValue } from "@/components/ui/select"; +import { toUnicode } from "punycode"; interface DomainOption { baseDomain: string; @@ -91,7 +92,7 @@ export default function CustomDomainInput({ key={option.domainId} value={option.domainId} > - .{option.baseDomain} + .{toUnicode(option.baseDomain)} ))} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx index af7d96fc..8da95ec0 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/ResourceInfoBox.tsx @@ -12,15 +12,19 @@ import { } from "@app/components/InfoSection"; import { useTranslations } from "next-intl"; import { build } from "@server/build"; +import { toUnicode } from 'punycode'; type ResourceInfoBoxType = {}; -export default function ResourceInfoBox({}: ResourceInfoBoxType) { +export default function ResourceInfoBox({ }: ResourceInfoBoxType) { const { resource, authInfo } = useResourceContext(); const t = useTranslations(); - const fullUrl = `${resource.ssl ? "https" : "http"}://${resource.fullDomain}`; + + const fullUrl = `${resource.ssl ? "https" : "http"}://${toUnicode(resource.fullDomain || "")}`; + + return ( @@ -34,9 +38,9 @@ export default function ResourceInfoBox({}: ResourceInfoBoxType) { {authInfo.password || - authInfo.pincode || - authInfo.sso || - authInfo.whitelist ? ( + authInfo.pincode || + authInfo.sso || + authInfo.whitelist ? (
{t("protected")} diff --git a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx index b95ecef2..37c5c363 100644 --- a/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx +++ b/src/app/[orgId]/settings/resources/[resourceId]/general/page.tsx @@ -54,6 +54,8 @@ import DomainPicker from "@app/components/DomainPicker"; import { Globe } from "lucide-react"; import { build } from "@server/build"; import { finalizeSubdomainSanitize } from "@app/lib/subdomain-utils"; +import { DomainRow } from "../../../domains/DomainsTable"; +import { toUnicode } from "punycode"; export default function GeneralForm() { const [formKey, setFormKey] = useState(0); @@ -155,7 +157,11 @@ export default function GeneralForm() { }); if (res?.status === 200) { - const domains = res.data.data.domains; + const rawDomains = res.data.data.domains as DomainRow[]; + const domains = rawDomains.map((domain) => ({ + ...domain, + baseDomain: toUnicode(domain.baseDomain), + })); setBaseDomains(domains); setFormKey((key) => key + 1); } @@ -319,10 +325,10 @@ export default function GeneralForm() { .target .value ? parseInt( - e - .target - .value - ) + e + .target + .value + ) : undefined ) } diff --git a/src/app/[orgId]/settings/resources/create/page.tsx b/src/app/[orgId]/settings/resources/create/page.tsx index 9caa3655..6436ac15 100644 --- a/src/app/[orgId]/settings/resources/create/page.tsx +++ b/src/app/[orgId]/settings/resources/create/page.tsx @@ -89,6 +89,8 @@ import { isTargetValid } from "@server/lib/validators"; import { ListTargetsResponse } from "@server/routers/target"; import { DockerManager, DockerState } from "@app/lib/docker"; import { parseHostTarget } from "@app/lib/parseHostTarget"; +import { toUnicode } from 'punycode'; +import { DomainRow } from "../../domains/DomainsTable"; const baseResourceFormSchema = z.object({ name: z.string().min(1).max(255), @@ -469,7 +471,11 @@ export default function Page() { }); if (res?.status === 200) { - const domains = res.data.data.domains; + const rawDomains = res.data.data.domains as DomainRow[]; + const domains = rawDomains.map((domain) => ({ + ...domain, + baseDomain: toUnicode(domain.baseDomain), + })); setBaseDomains(domains); // if (domains.length) { // httpForm.setValue("domainId", domains[0].domainId); diff --git a/src/app/[orgId]/settings/resources/page.tsx b/src/app/[orgId]/settings/resources/page.tsx index d5af500b..f8ef5397 100644 --- a/src/app/[orgId]/settings/resources/page.tsx +++ b/src/app/[orgId]/settings/resources/page.tsx @@ -14,6 +14,7 @@ import { GetOrgResponse } from "@server/routers/org"; import OrgProvider from "@app/providers/OrgProvider"; import { getTranslations } from "next-intl/server"; import { pullEnv } from "@app/lib/pullEnv"; +import { toUnicode } from "punycode"; type ResourcesPageProps = { params: Promise<{ orgId: string }>; @@ -75,7 +76,9 @@ export default async function ResourcesPage(props: ResourcesPageProps) { id: resource.resourceId, name: resource.name, orgId: params.orgId, - domain: `${resource.ssl ? "https://" : "http://"}${resource.fullDomain}`, + + + domain: `${resource.ssl ? "https://" : "http://"}${toUnicode(resource.fullDomain || "")}`, protocol: resource.protocol, proxyPort: resource.proxyPort, http: resource.http, diff --git a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx index 44891980..18c989ab 100644 --- a/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx +++ b/src/app/[orgId]/settings/share-links/CreateShareLinkForm.tsx @@ -67,6 +67,7 @@ import { } from "@app/components/ui/collapsible"; import AccessTokenSection from "./AccessTokenUsage"; import { useTranslations } from "next-intl"; +import { toUnicode } from 'punycode'; type FormProps = { open: boolean; @@ -159,7 +160,7 @@ export default function CreateShareLinkForm({ .map((r) => ({ resourceId: r.resourceId, name: r.name, - resourceUrl: `${r.ssl ? "https://" : "http://"}${r.fullDomain}/` + resourceUrl: `${r.ssl ? "https://" : "http://"}${toUnicode(r.fullDomain || "")}/` })) ); } diff --git a/src/components/DomainPicker.tsx b/src/components/DomainPicker.tsx index ab54ccce..572ff913 100644 --- a/src/components/DomainPicker.tsx +++ b/src/components/DomainPicker.tsx @@ -43,6 +43,7 @@ import { validateByDomainType, isValidSubdomainStructure } from "@/lib/subdomain-utils"; +import { toUnicode } from "punycode"; type OrganizationDomain = { domainId: string; @@ -126,6 +127,7 @@ export default function DomainPicker2({ ) .map((domain) => ({ ...domain, + baseDomain: toUnicode(domain.baseDomain), type: domain.type as "ns" | "cname" | "wildcard" })); setOrganizationDomains(domains); @@ -406,6 +408,12 @@ export default function DomainPicker2({ const hasMoreProvided = sortedAvailableOptions.length > providedDomainsShown; + + const isValidDomainCharacter = (char: string) => { + // Allow Unicode letters, numbers, hyphens, and periods + return /[\p{L}\p{N}.-]/u.test(char); + }; + return (
@@ -424,8 +432,8 @@ export default function DomainPicker2({ showProvidedDomainSearch ? "" : showSubdomainInput - ? "" - : t("domainPickerNotAvailableForCname") + ? "" + : t("domainPickerNotAvailableForCname") } disabled={ !showSubdomainInput && !showProvidedDomainSearch @@ -436,10 +444,16 @@ export default function DomainPicker2({ "border-red-500 focus:border-red-500" )} onChange={(e) => { + const rawInput = e.target.value; + const validInput = rawInput + .split("") + .filter((char) => isValidDomainCharacter(char)) + .join(""); + if (showProvidedDomainSearch) { - handleProvidedDomainInputChange(e.target.value); + handleProvidedDomainInputChange(validInput); } else { - handleSubdomainChange(e.target.value); + handleSubdomainChange(validInput); } }} /> @@ -448,7 +462,6 @@ export default function DomainPicker2({ This subdomain contains invalid characters or structure. It will be sanitized automatically when you save.

)} - {showSubdomainInput && !subdomainInput && (

{t("domainPickerEnterSubdomainOrLeaveBlank")} @@ -474,7 +487,7 @@ export default function DomainPicker2({ {selectedBaseDomain ? (

{selectedBaseDomain.type === - "organization" ? null : ( + "organization" ? null : ( )} @@ -568,67 +581,67 @@ export default function DomainPicker2({ {(build === "saas" || build === "enterprise") && ( - - )} + + )} )} {(build === "saas" || build === "enterprise") && ( - - - - handleBaseDomainSelect({ - id: "provided-search", - domain: - build === - "enterprise" + + + + handleBaseDomainSelect({ + id: "provided-search", + domain: + build === + "enterprise" + ? "Provided Domain" + : "Free Provided Domain", + type: "provided-search" + }) + } + className="mx-2 rounded-md" + > +
+ +
+
+ + {build === "enterprise" ? "Provided Domain" - : "Free Provided Domain", - type: "provided-search" - }) - } - className="mx-2 rounded-md" - > -
- -
-
- - {build === "enterprise" - ? "Provided Domain" - : "Free Provided Domain"} - - - {t( - "domainPickerSearchForAvailableDomains" + : "Free Provided Domain"} + + + {t( + "domainPickerSearchForAvailableDomains" + )} + +
+ -
- -
-
-
- )} + /> +
+
+
+ )} @@ -684,7 +697,7 @@ export default function DomainPicker2({ htmlFor={option.domainNamespaceId} data-state={ selectedProvidedDomain?.domainNamespaceId === - option.domainNamespaceId + option.domainNamespaceId ? "checked" : "unchecked" } @@ -764,4 +777,4 @@ function debounce any>( func(...args); }, wait); }; -} +} \ No newline at end of file