From 045f46bce64951315ef9c26af9b2a8accccafad9 Mon Sep 17 00:00:00 2001 From: oguz ozturk Date: Sun, 11 Jan 2026 15:17:37 +0300 Subject: [PATCH] Add step-by-step domain wizard with CF account selection and NS setup --- frontend/src/components/AddDomainWizard.jsx | 547 ++++++++++++++++++++ frontend/src/pages/Dashboard.jsx | 20 +- 2 files changed, 554 insertions(+), 13 deletions(-) create mode 100644 frontend/src/components/AddDomainWizard.jsx 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 ( + <> +
+
+ {/* Header */} +
+

Add New Domain

+ +
+ + {/* Progress Steps */} +
+
+ {steps.map((step, index) => { + const Icon = step.icon; + const isActive = currentStep === step.number; + const isCompleted = currentStep > step.number; + + return ( +
+
+
+ {isCompleted ? ( + + ) : ( + + )} +
+ + {step.title} + +
+ {index < steps.length - 1 && ( +
+ )} +
+ ); + })} +
+
+ + {/* Error Message */} + {error && ( +
+

{error}

+
+ )} + + {/* Step Content */} +
+ {/* Step 1: Domain Name */} + {currentStep === 1 && ( +
+
+

Enter Your Domain Name

+

+ Enter the domain name you want to add to your hosting platform. +

+
+ +
+ + setDomainName(e.target.value.toLowerCase().trim())} + placeholder="example.com" + className="input-field w-full" + autoFocus + /> +

+ Enter without http:// or https:// +

+
+ +
+

📋 Requirements:

+
    +
  • • You must own this domain
  • +
  • • You must have access to domain registrar settings
  • +
  • • Domain limit: {customer?.max_domains} domains
  • +
+
+
+ )} + + {/* Step 2: Cloudflare Account Selection */} + {currentStep === 2 && ( +
+
+

Select Cloudflare Account

+

+ Choose how you want to manage your domain's DNS. +

+
+ +
+ {/* Company Account Option */} +
setCfAccountType('company')} + className={`border-2 rounded-lg p-4 cursor-pointer transition-all ${ + cfAccountType === 'company' + ? 'border-primary-500 bg-primary-50' + : 'border-gray-200 hover:border-gray-300' + }`} + > +
+ setCfAccountType('company')} + className="mt-1 mr-3" + /> +
+

Use Company Cloudflare Account

+

+ We'll manage your DNS using our Cloudflare account. Easier setup, no API token needed. +

+ + {cfAccountType === 'company' && companyAccounts.length > 0 && ( +
+ + +
+ )} +
+
+
+ + {/* Own Account Option */} +
setCfAccountType('own')} + className={`border-2 rounded-lg p-4 cursor-pointer transition-all ${ + cfAccountType === 'own' + ? 'border-primary-500 bg-primary-50' + : 'border-gray-200 hover:border-gray-300' + }`} + > +
+ setCfAccountType('own')} + className="mt-1 mr-3" + /> +
+

Use My Own Cloudflare Account

+

+ Use your own Cloudflare account. You'll need to provide an API token. +

+
+
+
+
+
+ )} + + {/* Step 3: Own CF Token OR DNS Preview */} + {currentStep === 3 && cfAccountType === 'own' && ( +
+
+

Enter Cloudflare API Token

+

+ Provide your Cloudflare API token to manage DNS records. +

+
+ +
+

+ Don't have an API token? +

+ +
+ +
+ + setOwnCfEmail(e.target.value)} + placeholder="your-email@example.com" + className="input-field w-full" + /> +
+ +
+ + setOwnCfToken(e.target.value)} + placeholder="Your Cloudflare API Token" + className="input-field w-full font-mono text-sm" + /> +

+ Your token will be encrypted and stored securely +

+
+
+ )} + + {currentStep === 3 && cfAccountType === 'company' && ( +
+
+

DNS Records Preview

+

+ These DNS records will be created for your domain. +

+
+ + {dnsPreview && ( +
+ + + + + + + + + + {dnsPreview.records?.map((record, idx) => ( + + + + + + ))} + +
TypeNameValue
{record.type}{record.name}{record.value}
+
+ )} + +
+

+ ✓ DNS records will be automatically configured when you complete the setup. +

+
+
+ )} + + {/* Step 4: Nameserver Setup */} + {currentStep === 4 && ( +
+ setCurrentStep(5)} + loading={loading} + /> +
+ )} + + {/* Step 5: Verification & Completion */} + {currentStep === 5 && ( +
+
+ +
+ +
+

+ Domain Successfully Added! +

+

+ Your domain {domainName} has been configured and is ready to use. +

+
+ +
+

✓ What's Next?

+
    +
  • • DNS records have been configured
  • +
  • • SSL certificate will be issued automatically
  • +
  • • Your domain will be active within a few minutes
  • +
+
+ + +
+ )} +
+ + {/* Footer Actions */} +
+ + + {currentStep < 5 && ( + + )} +
+
+
+ + {/* Token Guide Modal */} + {showTokenGuide && setShowTokenGuide(false)} />} + + ); +}; + +export default AddDomainWizard; + diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index 1a13c4f..96624f1 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -18,6 +18,7 @@ import { ClockIcon, } from '@heroicons/react/24/outline'; import api from '../services/api'; +import AddDomainWizard from '../components/AddDomainWizard'; // Domains Content Component const DomainsContent = ({ customer }) => { @@ -131,20 +132,13 @@ const DomainsContent = ({ customer }) => {
)} - {/* Add Domain Modal - Placeholder */} + {/* Add Domain Wizard */} {showAddModal && ( -
-
-

Add New Domain

-

Domain wizard coming soon...

- -
-
+ setShowAddModal(false)} + onSuccess={fetchDomains} + customer={customer} + /> )} );