allows typing flow while providing helpful validation

This commit is contained in:
Pallavi
2025-08-29 02:44:39 +05:30
parent bc335d15c0
commit 18bb6caf8f

View File

@@ -262,16 +262,21 @@ export default function DomainPicker2({
if (!baseDomain) return false; if (!baseDomain) return false;
if (baseDomain.type === "provided-search") { if (baseDomain.type === "provided-search") {
return /^[a-zA-Z0-9-]+$/.test(subdomain); return subdomain === "" || (
/^[a-zA-Z0-9.-]+$/.test(subdomain) &&
isValidSubdomainStructure(subdomain)
);
} }
if (baseDomain.type === "organization") { if (baseDomain.type === "organization") {
if (baseDomain.domainType === "cname") { if (baseDomain.domainType === "cname") {
return subdomain === ""; return subdomain === "";
} else if (baseDomain.domainType === "ns") { } else if (baseDomain.domainType === "ns" || baseDomain.domainType === "wildcard") {
return /^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/.test(subdomain); // NS and wildcard domains support multi-level subdomains with dots and hyphens
} else if (baseDomain.domainType === "wildcard") { return subdomain === "" || (
return /^[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*$/.test(subdomain); /^[a-zA-Z0-9.-]+$/.test(subdomain) &&
isValidSubdomainStructure(subdomain)
);
} }
} }
@@ -279,14 +284,30 @@ export default function DomainPicker2({
}; };
const sanitizeSubdomain = (input: string): string => { const sanitizeSubdomain = (input: string): string => {
if (!input) return "";
return input return input
.toLowerCase() .toLowerCase()
.replace(/[^a-z0-9.-]/g, "") .replace(/[^a-z0-9.-]/g, "");
.replace(/\.{2,}/g, ".") // collapse multiple dots → single dot
.replace(/^-+|-+$/g, "") // trim leading/trailing hyphens
.replace(/^\.+|\.+$/g, ""); // trim leading/trailing dots
}; };
const isValidSubdomainStructure = (subdomain: string): boolean => {
if (!subdomain) return true;
// check for consecutive dots or hyphens
if (/\.{2,}|-{2,}/.test(subdomain)) return false;
// check if starts or ends with hyphen or dot
if (/^[-.]|[-.]$/.test(subdomain)) return false;
// check each label >> (part between dots)
const parts = subdomain.split(".");
for (const part of parts) {
if (!part) return false; // Empty label
if (/^-|-$/.test(part)) return false; // Label starts/ends with hyphen
}
return true;
};
// Handle base domain selection // Handle base domain selection
const handleBaseDomainSelect = (option: DomainOption) => { const handleBaseDomainSelect = (option: DomainOption) => {
@@ -303,11 +324,11 @@ export default function DomainPicker2({
setSubdomainInput(sanitized); setSubdomainInput(sanitized);
} }
if (!validateSubdomain(sub, option)) { if (sanitized && !validateSubdomain(sanitized, option)) {
toast({ toast({
variant: "destructive", variant: "destructive",
title: "Invalid subdomain", title: "Invalid subdomain",
description: `"${sub}" cannot be used with ${option.domain}`, description: `"${sanitized}" is not valid for ${option.domain}. Subdomain labels cannot start or end with hyphens.`,
}); });
sub = ""; sub = "";
setSubdomainInput(""); setSubdomainInput("");
@@ -344,28 +365,27 @@ export default function DomainPicker2({
const sanitized = sanitizeSubdomain(value); const sanitized = sanitizeSubdomain(value);
setSubdomainInput(sanitized); setSubdomainInput(sanitized);
// Only show toast for truly invalid characters, not structure issues
if (value !== sanitized) { if (value !== sanitized) {
toast({ toast({
title: "Invalid characters removed", title: "Invalid characters removed",
description: `"${value}" was corrected to "${sanitized}"`, description: `Only letters, numbers, hyphens, and dots are allowed`,
}); });
} }
if (selectedBaseDomain?.type === "organization") { if (selectedBaseDomain?.type === "organization") {
const isValid = validateSubdomain(sanitized, selectedBaseDomain); // Always update the domain, validation will show visual feedback
if (isValid || sanitized === "") { const fullDomain = sanitized
const fullDomain = sanitized ? `${sanitized}.${selectedBaseDomain.domain}`
? `${sanitized}.${selectedBaseDomain.domain}` : selectedBaseDomain.domain;
: selectedBaseDomain.domain;
onDomainChange?.({ onDomainChange?.({
domainId: selectedBaseDomain.domainId!, domainId: selectedBaseDomain.domainId!,
type: "organization", type: "organization",
subdomain: sanitized || undefined, subdomain: sanitized || undefined,
fullDomain, fullDomain,
baseDomain: selectedBaseDomain.domain baseDomain: selectedBaseDomain.domain
}); });
}
} }
}; };
@@ -376,7 +396,7 @@ export default function DomainPicker2({
if (value !== sanitized) { if (value !== sanitized) {
toast({ toast({
title: "Invalid characters removed", title: "Invalid characters removed",
description: `"${value}" was corrected to "${sanitized}"`, description: `Only letters, numbers, hyphens, and dots are allowed`,
}); });
} }
@@ -410,7 +430,7 @@ export default function DomainPicker2({
}); });
}; };
const isSubdomainValid = selectedBaseDomain const isSubdomainValid = selectedBaseDomain && subdomainInput
? validateSubdomain(subdomainInput, selectedBaseDomain) ? validateSubdomain(subdomainInput, selectedBaseDomain)
: true; : true;
const showSubdomainInput = const showSubdomainInput =
@@ -458,8 +478,8 @@ export default function DomainPicker2({
} }
className={cn( className={cn(
!isSubdomainValid && !isSubdomainValid &&
subdomainInput && subdomainInput &&
"border-red-500" "border-red-500 focus:border-red-500"
)} )}
onChange={(e) => { onChange={(e) => {
if (showProvidedDomainSearch) { if (showProvidedDomainSearch) {
@@ -469,6 +489,11 @@ export default function DomainPicker2({
} }
}} }}
/> />
{showSubdomainInput && subdomainInput && !isSubdomainValid && (
<p className="text-sm text-red-500">
Invalid format. Subdomain cannot start/end with hyphens or dots, and cannot have consecutive dots or hyphens.
</p>
)}
{showSubdomainInput && !subdomainInput && ( {showSubdomainInput && !subdomainInput && (
<p className="text-sm text-muted-foreground"> <p className="text-sm text-muted-foreground">
{t("domainPickerEnterSubdomainOrLeaveBlank")} {t("domainPickerEnterSubdomainOrLeaveBlank")}