AeThex-Connect/integration-package/frontend/components/DomainVerification.jsx

313 lines
9.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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>
);
}