diff --git a/backend/app/api/portfolio.py b/backend/app/api/portfolio.py index c6521c6..38c1d66 100644 --- a/backend/app/api/portfolio.py +++ b/backend/app/api/portfolio.py @@ -780,9 +780,9 @@ async def start_dns_verification( domain=domain.domain, verification_code=domain.verification_code, dns_record_type="TXT", - dns_record_name=f"_pounce.{domain.domain}", + dns_record_name="@", dns_record_value=domain.verification_code, - instructions=f"Add a TXT record to your DNS settings:\n\nHost/Name: _pounce\nType: TXT\nValue: {domain.verification_code}\n\nDNS changes can take up to 48 hours to propagate, but usually complete within minutes.", + instructions=f"Add a TXT record to your DNS settings:\n\nHost/Name: @ (or leave empty)\nType: TXT\nValue: {domain.verification_code}\n\nDNS changes usually propagate within 5 minutes.", status=domain.verification_status, ) @@ -796,7 +796,7 @@ async def check_dns_verification( """ Check if DNS verification is complete. - Looks for the TXT record and verifies it matches the expected code. + Looks for the TXT record at root domain and verifies it contains the expected code. """ result = await db.execute( select(PortfolioDomain).where( @@ -827,8 +827,7 @@ async def check_dns_verification( detail="Verification not started. Call POST /verify-dns first.", ) - # Check DNS TXT record - txt_record_name = f"_pounce.{domain.domain}" + # Check DNS TXT record at ROOT domain (simpler for users) verified = False try: @@ -836,24 +835,26 @@ async def check_dns_verification( resolver.timeout = 5 resolver.lifetime = 10 - answers = resolver.resolve(txt_record_name, 'TXT') + # Check ROOT domain TXT records + answers = resolver.resolve(domain.domain, 'TXT') for rdata in answers: txt_value = rdata.to_text().strip('"') - if txt_value == domain.verification_code: + # Check if verification code is present anywhere in TXT records + if domain.verification_code in txt_value: verified = True break except dns.resolver.NXDOMAIN: return DNSVerificationCheckResponse( verified=False, status="pending", - message=f"TXT record not found. Please add a TXT record at _pounce.{domain.domain}", + message=f"Domain {domain.domain} not found in DNS. Check your domain configuration.", ) except dns.resolver.NoAnswer: return DNSVerificationCheckResponse( verified=False, status="pending", - message="TXT record exists but has no value. Check your DNS configuration.", + message=f"No TXT records found for {domain.domain}. Please add the TXT record.", ) except dns.resolver.Timeout: return DNSVerificationCheckResponse( @@ -883,6 +884,6 @@ async def check_dns_verification( return DNSVerificationCheckResponse( verified=False, status="pending", - message=f"TXT record found but value doesn't match. Expected: {domain.verification_code}", + message=f"TXT record found but verification code not detected. Make sure your TXT record contains: {domain.verification_code}", ) diff --git a/frontend/src/app/terminal/portfolio/page.tsx b/frontend/src/app/terminal/portfolio/page.tsx index 2979e45..ac8f7a7 100755 --- a/frontend/src/app/terminal/portfolio/page.tsx +++ b/frontend/src/app/terminal/portfolio/page.tsx @@ -397,13 +397,16 @@ function EditModal({ function AddModal({ onClose, - onAdd + onAdd, + onStartVerify }: { onClose: () => void - onAdd: (domain: string, data?: Partial) => Promise + onAdd: (domain: string, data?: Partial) => Promise + onStartVerify?: (domain: PortfolioDomain) => void }) { const [domain, setDomain] = useState('') const [showDetails, setShowDetails] = useState(false) + const [wantToVerify, setWantToVerify] = useState(false) const [form, setForm] = useState>({}) const [adding, setAdding] = useState(false) @@ -412,8 +415,14 @@ function AddModal({ if (!domain.trim()) return setAdding(true) try { - await onAdd(domain.trim(), showDetails ? form : undefined) - onClose() + const created = await onAdd(domain.trim(), showDetails ? form : undefined) as PortfolioDomain | undefined + if (wantToVerify && created && onStartVerify) { + onClose() + // Small delay to let the modal close before opening verify modal + setTimeout(() => onStartVerify(created), 100) + } else { + onClose() + } } finally { setAdding(false) } @@ -493,13 +502,34 @@ function AddModal({ )} + {/* Verify Option */} +
+ +
+ @@ -520,24 +550,32 @@ function VerifyModal({ onClose: () => void onVerified: () => void }) { - const [step, setStep] = useState<'start' | 'pending' | 'checking'>('start') - const [verificationCode, setVerificationCode] = useState(null) + const [verificationCode, setVerificationCode] = useState(domain.verification_code || null) const [copied, setCopied] = useState(false) const [error, setError] = useState(null) + const [loading, setLoading] = useState(!domain.verification_code) + const [checking, setChecking] = useState(false) + + useEffect(() => { + if (!verificationCode) { + startVerification() + } + }, []) const startVerification = async () => { - setStep('pending') + setLoading(true) try { const result = await api.startPortfolioDnsVerification(domain.id) setVerificationCode(result.verification_code) } catch (err: any) { setError(err?.message || 'Failed to start verification') - setStep('start') + } finally { + setLoading(false) } } const checkVerification = async () => { - setStep('checking') + setChecking(true) setError(null) try { const result = await api.checkPortfolioDnsVerification(domain.id) @@ -545,18 +583,18 @@ function VerifyModal({ onVerified() onClose() } else { - setError('DNS record not found. Please wait a few minutes for DNS to propagate and try again.') - setStep('pending') + setError(result.message || 'DNS record not found yet. Please wait a few minutes and try again.') } } catch (err: any) { setError(err?.message || 'Verification failed') - setStep('pending') + } finally { + setChecking(false) } } const copyCode = () => { if (!verificationCode) return - navigator.clipboard.writeText(`pounce-verify=${verificationCode}`) + navigator.clipboard.writeText(verificationCode) setCopied(true) setTimeout(() => setCopied(false), 2000) } @@ -564,80 +602,117 @@ function VerifyModal({ return (
e.stopPropagation()} > -
-
- - Verify Ownership +
+
+
+ + Verify Ownership +
+

{domain.domain}

-
-
{domain.domain}
- - {step === 'start' && ( +
+ {loading ? ( +
+ +
+ ) : verificationCode ? ( <> -

- Verify ownership to unlock Yield and For Sale features. You'll need to add a DNS TXT record. -

+ {/* Step 1 */} +
+

+ 1 + Add TXT Record at your Registrar +

+ +
+

+ Go to your registrar's DNS settings and add this TXT record: +

+ +
+
+
Type
+
TXT
+
+
+
Name / Host
+
@
+
+
+ +
+
Value (copy this)
+
+ + {verificationCode} + + +
+
+ +
+ Tip: The "@" symbol means root domain. Some registrars use "empty" or the domain name itself. +
+
+
+ + {/* Step 2 */} +
+

+ 2 + Verify +

+ +

+ After adding the TXT record, wait 2-5 minutes for DNS propagation, then click verify. +

+ + {error && ( +
+ + {error} +
+ )} + + +
+ + ) : ( +
+ +

{error || 'Failed to generate verification code'}

- - )} - - {(step === 'pending' || step === 'checking') && verificationCode && ( - <> -
-

Add this TXT record to your DNS:

-
- - pounce-verify={verificationCode} - - -
-
- -
-

1. Log in to your domain registrar

-

2. Find DNS settings for {domain.domain}

-

3. Add a new TXT record with the value above

-

4. Wait 2-5 minutes for propagation

-
- - {error && ( -
- {error} -
- )} - - - +
)}
@@ -905,11 +980,12 @@ export default function PortfolioPage() { } } - const handleAddDomain = async (domain: string, data?: any) => { + const handleAddDomain = async (domain: string, data?: any): Promise => { try { const created = await api.addPortfolioDomain({ domain, ...data }) setDomains(prev => [created, ...prev]) showToast(`${domain} added to portfolio`, 'success') + return created } catch (err: any) { showToast(err?.message || 'Failed to add domain', 'error') throw err @@ -1719,7 +1795,11 @@ export default function PortfolioPage() { {/* MODALS */} {showAddModal && ( - setShowAddModal(false)} onAdd={handleAddDomain} /> + setShowAddModal(false)} + onAdd={handleAddDomain} + onStartVerify={(domain) => setVerifyingDomain(domain)} + /> )} {editingDomain && (