313 lines
9.5 KiB
JavaScript
313 lines
9.5 KiB
JavaScript
import React, { useState, useEffect } from 'react';
|
||
import './DomainVerification.css';
|
||
|
||
/**
|
||
* Domain verification UI component
|
||
* Allows users to verify domain ownership via DNS TXT records or blockchain
|
||
*/
|
||
export default function DomainVerification({ apiBaseUrl = 'https://api.aethex.cloud/api/passport/domain' }) {
|
||
const [domain, setDomain] = useState('');
|
||
const [walletAddress, setWalletAddress] = useState('');
|
||
const [verificationInstructions, setVerificationInstructions] = useState(null);
|
||
const [verificationStatus, setVerificationStatus] = useState(null);
|
||
const [loading, setLoading] = useState(false);
|
||
const [error, setError] = useState(null);
|
||
const [currentStatus, setCurrentStatus] = useState(null);
|
||
|
||
// Load current verification status on mount
|
||
useEffect(() => {
|
||
loadCurrentStatus();
|
||
}, []);
|
||
|
||
/**
|
||
* Load current verification status
|
||
*/
|
||
async function loadCurrentStatus() {
|
||
try {
|
||
const token = localStorage.getItem('authToken');
|
||
const response = await fetch(`${apiBaseUrl}/status`, {
|
||
headers: {
|
||
'Authorization': `Bearer ${token}`
|
||
}
|
||
});
|
||
|
||
if (response.ok) {
|
||
const data = await response.json();
|
||
setCurrentStatus(data);
|
||
}
|
||
} catch (err) {
|
||
console.error('Failed to load status:', err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Request verification token from backend
|
||
*/
|
||
async function requestVerification() {
|
||
if (!domain) {
|
||
setError('Please enter a domain');
|
||
return;
|
||
}
|
||
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
const token = localStorage.getItem('authToken');
|
||
const response = await fetch(`${apiBaseUrl}/request-verification`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
body: JSON.stringify({ domain })
|
||
});
|
||
|
||
const data = await response.json();
|
||
|
||
if (data.success) {
|
||
setVerificationInstructions(data.verification);
|
||
} else {
|
||
setError(data.error || 'Failed to request verification');
|
||
}
|
||
} catch (err) {
|
||
setError('Network error. Please try again.');
|
||
console.error('Failed to request verification:', err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Verify domain ownership by checking DNS or blockchain
|
||
*/
|
||
async function verifyDomain() {
|
||
setLoading(true);
|
||
setError(null);
|
||
|
||
try {
|
||
const token = localStorage.getItem('authToken');
|
||
const body = { domain };
|
||
|
||
// Add wallet address if verifying .aethex domain
|
||
if (domain.endsWith('.aethex')) {
|
||
body.walletAddress = walletAddress;
|
||
}
|
||
|
||
const response = await fetch(`${apiBaseUrl}/verify`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Authorization': `Bearer ${token}`
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
const data = await response.json();
|
||
setVerificationStatus(data);
|
||
|
||
if (data.verified) {
|
||
// Refresh status and reload after short delay
|
||
setTimeout(() => {
|
||
loadCurrentStatus();
|
||
window.location.reload();
|
||
}, 1500);
|
||
} else {
|
||
setError(data.error || 'Verification failed');
|
||
}
|
||
} catch (err) {
|
||
setError('Network error. Please try again.');
|
||
console.error('Verification failed:', err);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Copy text to clipboard
|
||
*/
|
||
function copyToClipboard(text) {
|
||
navigator.clipboard.writeText(text);
|
||
// You could add a toast notification here
|
||
alert('Copied to clipboard!');
|
||
}
|
||
|
||
/**
|
||
* Reset form
|
||
*/
|
||
function resetForm() {
|
||
setVerificationInstructions(null);
|
||
setVerificationStatus(null);
|
||
setDomain('');
|
||
setWalletAddress('');
|
||
setError(null);
|
||
}
|
||
|
||
// Show current verified domain if exists
|
||
if (currentStatus?.hasVerifiedDomain) {
|
||
return (
|
||
<div className="domain-verification verified-container">
|
||
<h3>✓ Domain Verified</h3>
|
||
<div className="verified-domain-display">
|
||
<strong>{currentStatus.domain}</strong>
|
||
<span className="verification-type">
|
||
{currentStatus.verificationType === 'blockchain' ? 'Blockchain' : 'DNS'}
|
||
</span>
|
||
</div>
|
||
<p className="verified-date">
|
||
Verified on {new Date(currentStatus.verifiedAt).toLocaleDateString()}
|
||
</p>
|
||
<button
|
||
className="secondary-button"
|
||
onClick={() => setCurrentStatus(null)}
|
||
>
|
||
Verify Another Domain
|
||
</button>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
return (
|
||
<div className="domain-verification">
|
||
<h3>Verify Your Domain</h3>
|
||
<p className="description">
|
||
Prove ownership of a domain to display it on your profile
|
||
</p>
|
||
|
||
{error && (
|
||
<div className="error-message">
|
||
<span>⚠️ {error}</span>
|
||
</div>
|
||
)}
|
||
|
||
{!verificationInstructions ? (
|
||
<div className="input-section">
|
||
<div className="form-group">
|
||
<label htmlFor="domain">Domain Name</label>
|
||
<input
|
||
id="domain"
|
||
type="text"
|
||
placeholder="yourdomain.com or anderson.aethex"
|
||
value={domain}
|
||
onChange={(e) => setDomain(e.target.value.toLowerCase().trim())}
|
||
disabled={loading}
|
||
className="domain-input"
|
||
/>
|
||
<small className="help-text">
|
||
Enter a traditional domain (e.g., dev.aethex.dev) or a .aethex blockchain domain
|
||
</small>
|
||
</div>
|
||
|
||
<button
|
||
onClick={requestVerification}
|
||
disabled={!domain || loading}
|
||
className="primary-button"
|
||
>
|
||
{loading ? 'Generating...' : 'Request Verification'}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
<div className="instructions-section">
|
||
<h4>
|
||
{domain.endsWith('.aethex')
|
||
? 'Connect Your Wallet'
|
||
: `Add this DNS record to ${verificationInstructions.domain}:`
|
||
}
|
||
</h4>
|
||
|
||
{domain.endsWith('.aethex') ? (
|
||
// Blockchain verification
|
||
<div className="blockchain-verification">
|
||
<p>Connect the wallet that owns <strong>{domain}</strong></p>
|
||
<div className="form-group">
|
||
<label htmlFor="wallet">Wallet Address</label>
|
||
<input
|
||
id="wallet"
|
||
type="text"
|
||
placeholder="0x..."
|
||
value={walletAddress}
|
||
onChange={(e) => setWalletAddress(e.target.value.trim())}
|
||
disabled={loading}
|
||
className="wallet-input"
|
||
/>
|
||
</div>
|
||
<button
|
||
onClick={verifyDomain}
|
||
disabled={!walletAddress || loading}
|
||
className="primary-button verify-button"
|
||
>
|
||
{loading ? 'Verifying...' : 'Verify Ownership'}
|
||
</button>
|
||
</div>
|
||
) : (
|
||
// DNS verification
|
||
<div className="dns-verification">
|
||
<div className="dns-record">
|
||
<div className="record-field">
|
||
<strong>Type:</strong>
|
||
<span>{verificationInstructions.recordType}</span>
|
||
</div>
|
||
<div className="record-field">
|
||
<strong>Name:</strong>
|
||
<span>{verificationInstructions.recordName}</span>
|
||
</div>
|
||
<div className="record-field">
|
||
<strong>Value:</strong>
|
||
<div className="value-container">
|
||
<code>{verificationInstructions.recordValue}</code>
|
||
<button
|
||
onClick={() => copyToClipboard(verificationInstructions.recordValue)}
|
||
className="copy-button"
|
||
title="Copy to clipboard"
|
||
>
|
||
📋 Copy
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="help-section">
|
||
<p><strong>How to add DNS records:</strong></p>
|
||
<ol>
|
||
<li>Go to your domain's DNS settings (Google Domains, Cloudflare, etc.)</li>
|
||
<li>Add a new TXT record with the values above</li>
|
||
<li>Wait 5-10 minutes for DNS to propagate</li>
|
||
<li>Click "Verify Domain" below</li>
|
||
</ol>
|
||
<p className="expires-note">
|
||
⏱️ This verification expires on {new Date(verificationInstructions.expiresAt).toLocaleDateString()}
|
||
</p>
|
||
</div>
|
||
|
||
<button
|
||
onClick={verifyDomain}
|
||
disabled={loading}
|
||
className="primary-button verify-button"
|
||
>
|
||
{loading ? 'Verifying...' : 'Verify Domain'}
|
||
</button>
|
||
|
||
{verificationStatus && (
|
||
<div className={`status-message ${verificationStatus.verified ? 'success' : 'error'}`}>
|
||
{verificationStatus.verified ? (
|
||
<span>✓ Domain verified successfully!</span>
|
||
) : (
|
||
<span>✗ {verificationStatus.error}</span>
|
||
)}
|
||
</div>
|
||
)}
|
||
</div>
|
||
)}
|
||
|
||
<button
|
||
onClick={resetForm}
|
||
className="secondary-button cancel-button"
|
||
disabled={loading}
|
||
>
|
||
Cancel
|
||
</button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|