wizard: check pic.ngo availability on Next, not just on blur

The availability check was only triggered onBlur, so clicking Next
without blurring the field skipped the DDNS request entirely. Now
handleNext awaits the check and blocks with an error if the name is
taken. Unknown/unreachable DDNS is treated as available to avoid
blocking the wizard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-26 07:56:59 -04:00
parent 900781032a
commit 99dcb1332a
+21 -7
View File
@@ -281,14 +281,20 @@ function Step2Domain({
const [picAvail, setPicAvail] = useState(null); // null|checking|available|taken|unknown
const [cfStatus, setCfStatus] = useState(null);
const [dnsStatus, setDnsStatus] = useState(null);
const [nextLoading, setNextLoading] = useState(false);
const checkPicAvail = async name => {
if (!CELL_NAME_RE.test(name)) return;
if (!CELL_NAME_RE.test(name)) return null;
setPicAvail('checking');
try {
const res = await setupAPI.validate('pic_ngo_available', { cell_name: name });
setPicAvail(res.data?.available ? 'available' : 'taken');
} catch { setPicAvail('unknown'); }
const result = res.data?.available ? 'available' : 'taken';
setPicAvail(result);
return result;
} catch {
setPicAvail('unknown');
return 'unknown';
}
};
const verifyCf = async () => {
@@ -307,11 +313,19 @@ function Step2Domain({
} catch { setDnsStatus('invalid'); }
};
const handleNext = () => {
const handleNext = async () => {
const e = {};
if (domainType === 'pic_ngo') {
if (!picName) e.name = 'Subdomain is required.';
else if (!CELL_NAME_RE.test(picName)) e.name = 'Lowercase letters, digits, hyphens. 231 chars, must start with a letter.';
if (!picName) {
e.name = 'Subdomain is required.';
} else if (!CELL_NAME_RE.test(picName)) {
e.name = 'Lowercase letters, digits, hyphens. 231 chars, must start with a letter.';
} else if (picAvail !== 'available') {
setNextLoading(true);
const result = await checkPicAvail(picName);
setNextLoading(false);
if (result === 'taken') e.name = 'This subdomain is already taken. Choose another.';
}
} else if (domainType === 'cloudflare') {
if (!customDomain || !DOMAIN_RE.test(customDomain)) e.domain = 'Enter a valid domain (e.g. home.example.com).';
if (!cloudflareToken.trim()) e.token = 'Cloudflare API token is required.';
@@ -441,7 +455,7 @@ function Step2Domain({
</div>
)}
<NavButtons onBack={onBack} onNext={handleNext} />
<NavButtons onBack={onBack} onNext={handleNext} loading={nextLoading} />
</div>
);
}