diff --git a/frontend/src/components/AddDomainWizard.jsx b/frontend/src/components/AddDomainWizard.jsx new file mode 100644 index 0000000..1614377 --- /dev/null +++ b/frontend/src/components/AddDomainWizard.jsx @@ -0,0 +1,547 @@ +/** + * Add Domain Wizard - Step-by-step domain addition process + */ +import { useState, useEffect } from 'react'; +import { + XMarkIcon, + CheckCircleIcon, + ArrowRightIcon, + ArrowLeftIcon, + GlobeAltIcon, + CloudIcon, + DocumentTextIcon, + ServerIcon, + ShieldCheckIcon, +} from '@heroicons/react/24/outline'; +import api from '../services/api'; +import CFTokenGuide from './CFTokenGuide'; +import NSInstructions from './NSInstructions'; + +const AddDomainWizard = ({ onClose, onSuccess, customer }) => { + const [currentStep, setCurrentStep] = useState(1); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + // Form data + const [domainName, setDomainName] = useState(''); + const [cfAccountType, setCfAccountType] = useState(''); // 'company' or 'own' + const [selectedCompanyAccount, setSelectedCompanyAccount] = useState(null); + const [ownCfToken, setOwnCfToken] = useState(''); + const [ownCfEmail, setOwnCfEmail] = useState(''); + + // Company CF accounts + const [companyAccounts, setCompanyAccounts] = useState([]); + + // Domain setup data + const [domainId, setDomainId] = useState(null); + const [dnsPreview, setDnsPreview] = useState(null); + const [nsInstructions, setNsInstructions] = useState(null); + const [nsStatus, setNsStatus] = useState(null); + + // UI helpers + const [showTokenGuide, setShowTokenGuide] = useState(false); + + const steps = [ + { number: 1, title: 'Domain Name', icon: GlobeAltIcon }, + { number: 2, title: 'Cloudflare Account', icon: CloudIcon }, + { number: 3, title: cfAccountType === 'own' ? 'API Token' : 'DNS Preview', icon: DocumentTextIcon }, + { number: 4, title: 'Nameserver Setup', icon: ServerIcon }, + { number: 5, title: 'Verification', icon: ShieldCheckIcon }, + ]; + + // Fetch company CF accounts + useEffect(() => { + if (currentStep === 2) { + fetchCompanyAccounts(); + } + }, [currentStep]); + + const fetchCompanyAccounts = async () => { + try { + const response = await api.get('/api/customer/cloudflare-accounts'); + setCompanyAccounts(response.data.accounts || []); + } catch (err) { + console.error('Failed to fetch CF accounts:', err); + } + }; + + // Validate domain name + const validateDomain = (domain) => { + const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; + return domainRegex.test(domain); + }; + + // Step 1: Submit domain name + const handleStep1Next = async () => { + if (!domainName.trim()) { + setError('Please enter a domain name'); + return; + } + + if (!validateDomain(domainName)) { + setError('Please enter a valid domain name (e.g., example.com)'); + return; + } + + setError(null); + setCurrentStep(2); + }; + + // Step 2: Select CF account type + const handleStep2Next = () => { + if (!cfAccountType) { + setError('Please select a Cloudflare account option'); + return; + } + + if (cfAccountType === 'company' && !selectedCompanyAccount) { + setError('Please select a company Cloudflare account'); + return; + } + + setError(null); + setCurrentStep(3); + }; + + // Step 3: Handle based on account type + const handleStep3Next = async () => { + setLoading(true); + setError(null); + + try { + if (cfAccountType === 'own') { + // Validate own CF token + if (!ownCfToken.trim() || !ownCfEmail.trim()) { + setError('Please enter both Cloudflare email and API token'); + setLoading(false); + return; + } + + // Create domain with own CF account + const response = await api.post('/api/customer/domains', { + domain_name: domainName, + cf_account_type: 'own', + cf_email: ownCfEmail, + cf_api_token: ownCfToken, + }); + + setDomainId(response.data.domain.id); + setNsInstructions(response.data.ns_instructions); + setCurrentStep(4); + } else { + // Create domain with company CF account + const response = await api.post('/api/customer/domains', { + domain_name: domainName, + cf_account_type: 'company', + cf_account_id: selectedCompanyAccount.id, + }); + + setDomainId(response.data.domain.id); + setDnsPreview(response.data.dns_preview); + setNsInstructions(response.data.ns_instructions); + setCurrentStep(4); + } + } catch (err) { + setError(err.response?.data?.error || 'Failed to create domain'); + } finally { + setLoading(false); + } + }; + + // Step 4: Check nameserver status + const checkNsStatus = async () => { + setLoading(true); + try { + const response = await api.get(`/api/customer/domains/${domainId}/ns-status`); + setNsStatus(response.data); + + if (response.data.is_cloudflare) { + setCurrentStep(5); + } + } catch (err) { + console.error('Failed to check NS status:', err); + } finally { + setLoading(false); + } + }; + + return ( + <> +
{error}
++ Enter the domain name you want to add to your hosting platform. +
++ Enter without http:// or https:// +
++ Choose how you want to manage your domain's DNS. +
++ We'll manage your DNS using our Cloudflare account. Easier setup, no API token needed. +
+ + {cfAccountType === 'company' && companyAccounts.length > 0 && ( ++ Use your own Cloudflare account. You'll need to provide an API token. +
++ Provide your Cloudflare API token to manage DNS records. +
++ Don't have an API token? +
+ ++ Your token will be encrypted and stored securely +
++ These DNS records will be created for your domain. +
+| Type | +Name | +Value | +
|---|---|---|
| {record.type} | +{record.name} | +{record.value} | +
+ ✓ DNS records will be automatically configured when you complete the setup. +
++ Your domain {domainName} has been configured and is ready to use. +
+Domain wizard coming soon...
- -