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:
@@ -281,14 +281,20 @@ function Step2Domain({
|
|||||||
const [picAvail, setPicAvail] = useState(null); // null|checking|available|taken|unknown
|
const [picAvail, setPicAvail] = useState(null); // null|checking|available|taken|unknown
|
||||||
const [cfStatus, setCfStatus] = useState(null);
|
const [cfStatus, setCfStatus] = useState(null);
|
||||||
const [dnsStatus, setDnsStatus] = useState(null);
|
const [dnsStatus, setDnsStatus] = useState(null);
|
||||||
|
const [nextLoading, setNextLoading] = useState(false);
|
||||||
|
|
||||||
const checkPicAvail = async name => {
|
const checkPicAvail = async name => {
|
||||||
if (!CELL_NAME_RE.test(name)) return;
|
if (!CELL_NAME_RE.test(name)) return null;
|
||||||
setPicAvail('checking');
|
setPicAvail('checking');
|
||||||
try {
|
try {
|
||||||
const res = await setupAPI.validate('pic_ngo_available', { cell_name: name });
|
const res = await setupAPI.validate('pic_ngo_available', { cell_name: name });
|
||||||
setPicAvail(res.data?.available ? 'available' : 'taken');
|
const result = res.data?.available ? 'available' : 'taken';
|
||||||
} catch { setPicAvail('unknown'); }
|
setPicAvail(result);
|
||||||
|
return result;
|
||||||
|
} catch {
|
||||||
|
setPicAvail('unknown');
|
||||||
|
return 'unknown';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const verifyCf = async () => {
|
const verifyCf = async () => {
|
||||||
@@ -307,11 +313,19 @@ function Step2Domain({
|
|||||||
} catch { setDnsStatus('invalid'); }
|
} catch { setDnsStatus('invalid'); }
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNext = () => {
|
const handleNext = async () => {
|
||||||
const e = {};
|
const e = {};
|
||||||
if (domainType === 'pic_ngo') {
|
if (domainType === 'pic_ngo') {
|
||||||
if (!picName) e.name = 'Subdomain is required.';
|
if (!picName) {
|
||||||
else if (!CELL_NAME_RE.test(picName)) e.name = 'Lowercase letters, digits, hyphens. 2–31 chars, must start with a letter.';
|
e.name = 'Subdomain is required.';
|
||||||
|
} else if (!CELL_NAME_RE.test(picName)) {
|
||||||
|
e.name = 'Lowercase letters, digits, hyphens. 2–31 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') {
|
} else if (domainType === 'cloudflare') {
|
||||||
if (!customDomain || !DOMAIN_RE.test(customDomain)) e.domain = 'Enter a valid domain (e.g. home.example.com).';
|
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.';
|
if (!cloudflareToken.trim()) e.token = 'Cloudflare API token is required.';
|
||||||
@@ -441,7 +455,7 @@ function Step2Domain({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<NavButtons onBack={onBack} onNext={handleNext} />
|
<NavButtons onBack={onBack} onNext={handleNext} loading={nextLoading} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user