1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../../lib/api';
4export default function DomainRegistrationModal({
13 const [showContactDetails, setShowContactDetails] = useState(false);
15 // Customer search state
16 const [customerQuery, setCustomerQuery] = useState('');
17 const [customerSuggestions, setCustomerSuggestions] = useState([]);
18 const [customerSelected, setCustomerSelected] = useState(false);
19 const [customerName, setCustomerName] = useState('');
21 // Search for customers (debounced)
23 if (customerSelected) return;
25 const searchCustomers = async () => {
26 if (!customerQuery || !customerQuery.trim()) {
27 setCustomerSuggestions([]);
31 const res = await apiFetch(`/customers?search=${encodeURIComponent(customerQuery)}`, {
33 credentials: 'include'
36 const data = await res.json();
37 setCustomerSuggestions(data.customers || []);
42 const timer = setTimeout(searchCustomers, 300);
43 return () => clearTimeout(timer);
44 }, [customerQuery, customerSelected]);
46 if (!showModal) return null;
48 const handleSubmit = (e) => {
53 // Toggle between simple and detailed contact forms
54 const toggleContactForm = () => {
55 setShowContactDetails(!showContactDetails);
59 <div className="modal-overlay" onClick={onClose}>
60 <div className="modal-content large-modal" onClick={(e) => e.stopPropagation()}>
61 <div className="modal-header">
62 <h2>Request Domain Registration</h2>
63 <button className="modal-close" onClick={onClose}>
64 <span className="material-symbols-outlined">close</span>
67 <form onSubmit={handleSubmit}>
68 <div className="modal-body">
69 <div className="info-banner">
70 <span className="material-symbols-outlined">info</span>
72 Domain registration requests require admin approval before processing.
73 You'll be notified once your request is reviewed.
77 {/* Basic Domain Information */}
78 <h3>Domain Information</h3>
79 <div className="form-row">
80 <div className="form-group" style={{ gridColumn: '1 / -1' }}>
81 <label>Domain Type *</label>
82 <div style={{ display: 'flex', gap: '20px', marginTop: '8px' }}>
83 <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
88 checked={formData.domain_type === 'registration' || !formData.domain_type}
89 onChange={(e) => setFormData({ ...formData, domain_type: e.target.value })}
92 <strong>New Domain Registration</strong>
93 <div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>
94 Register a new domain through our registrar (requires payment)
98 <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
103 checked={formData.domain_type === 'dns-only'}
104 onChange={(e) => setFormData({ ...formData, domain_type: e.target.value })}
107 <strong>DNS Management Only</strong>
108 <div style={{ fontSize: '0.875rem', color: 'var(--text-secondary)' }}>
109 Manage DNS for an existing domain registered elsewhere (free)
117 {formData.domain_type === 'dns-only' && (
118 <div className="info-banner" style={{ background: '#e3f2fd', borderColor: '#2196F3' }}>
119 <span className="material-symbols-outlined" style={{ color: '#2196F3' }}>info</span>
121 <strong>DNS-Only Domain Setup:</strong><br/>
122 After approval, a Cloudflare DNS zone will be created. You'll receive nameservers to update at your current registrar.
123 No domain registration or payment is required.
128 <div className="form-row">
129 <div className="form-group">
130 <label>Domain Name *</label>
133 value={formData.domain_name}
134 onChange={(e) => setFormData({ ...formData, domain_name: e.target.value })}
135 placeholder="example.com"
139 {formData.domain_type !== 'dns-only' && (
140 <div className="form-group">
141 <label>Registration Period *</label>
143 value={formData.years}
144 onChange={(e) => setFormData({ ...formData, years: parseInt(e.target.value) })}
147 <option value="1">1 Year</option>
148 <option value="2">2 Years</option>
149 <option value="3">3 Years</option>
150 <option value="5">5 Years</option>
151 <option value="10">10 Years</option>
157 <div className="form-row">
158 <div className="form-group">
159 <label>Customer (Optional)</label>
160 <div className="search-input" style={{ position: 'relative' }}>
163 placeholder="Search customer (or leave blank for none)"
164 value={customerQuery}
166 setCustomerQuery(e.target.value);
168 setCustomerSelected(false);
169 setFormData({ ...formData, customer_id: null });
173 ...(customerQuery && !customerSelected ? { borderColor: '#f0ad4e' } : {})
176 {customerSuggestions.length > 0 && !customerSelected && (
177 <div className="suggestions-dropdown" style={{
178 position: 'absolute',
183 border: '1px solid #ddd',
188 boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
190 {customerSuggestions.map(c => (
193 className="suggestion-item"
195 setCustomerName(c.name);
196 setCustomerQuery(c.name);
197 setCustomerSuggestions([]);
198 setCustomerSelected(true);
199 setFormData({ ...formData, customer_id: c.customer_id });
204 borderBottom: '1px solid #eee'
206 onMouseOver={(e) => e.currentTarget.style.background = '#f5f5f5'}
207 onMouseOut={(e) => e.currentTarget.style.background = 'white'}
209 {c.name} <span style={{ color: '#999', fontSize: '0.9em' }}>({c.email || 'No email'})</span>
214 {customerQuery && !customerSelected && (
215 <small style={{ display: 'block', marginTop: '4px', color: '#f0ad4e' }}>
216 Please select a customer from the dropdown or clear to continue
220 <small style={{ display: 'block', marginTop: '4px', color: '#666' }}>
221 Leave blank if not for a specific customer
226 <div className="form-group">
227 <label>Contract (Optional)</label>
229 value={formData.contract_id}
230 onChange={(e) => setFormData({ ...formData, contract_id: e.target.value })}
232 <option value="">No contract</option>
233 {contracts.map((c) => (
234 <option key={c.contract_id} value={c.contract_id}>
239 <small>Link this domain to a contract for automated billing</small>
244 <div className="form-group">
245 <label>Nameservers (optional, one per line)</label>
247 value={formData.nameservers}
248 onChange={(e) => setFormData({ ...formData, nameservers: e.target.value })}
249 placeholder="ns1.example.com ns2.example.com Leave blank to use registrar defaults"
252 <small>If left blank, registrar default nameservers will be used</small>
255 {/* Registrant Contact Information */}
256 <h3>Registrant Contact Information *</h3>
257 <div className="form-row">
258 <div className="form-group">
259 <label>First Name *</label>
262 value={formData.registrant_firstName}
263 onChange={(e) => setFormData({ ...formData, registrant_firstName: e.target.value })}
267 <div className="form-group">
268 <label>Last Name *</label>
271 value={formData.registrant_lastName}
272 onChange={(e) => setFormData({ ...formData, registrant_lastName: e.target.value })}
278 <div className="form-row">
279 <div className="form-group">
280 <label>Organization</label>
283 value={formData.registrant_organization}
284 onChange={(e) => setFormData({ ...formData, registrant_organization: e.target.value })}
285 placeholder="Optional"
288 <div className="form-group">
289 <label>Email *</label>
292 value={formData.registrant_email}
293 onChange={(e) => setFormData({ ...formData, registrant_email: e.target.value })}
299 <div className="form-row">
300 <div className="form-group">
301 <label>Phone *</label>
304 value={formData.registrant_phone}
305 onChange={(e) => setFormData({ ...formData, registrant_phone: e.target.value })}
306 placeholder="+1.5551234567"
309 <small>Format: +CountryCode.Phone (e.g., +1.5551234567)</small>
311 <div className="form-group">
312 <label>Country *</label>
315 value={formData.registrant_country}
316 onChange={(e) => setFormData({ ...formData, registrant_country: e.target.value })}
317 placeholder="US, AU, GB, etc."
321 <small>2-letter country code</small>
325 <div className="form-group">
326 <label>Address *</label>
329 value={formData.registrant_address}
330 onChange={(e) => setFormData({ ...formData, registrant_address: e.target.value })}
331 placeholder="Street address"
336 <div className="form-row">
337 <div className="form-group">
338 <label>City *</label>
341 value={formData.registrant_city}
342 onChange={(e) => setFormData({ ...formData, registrant_city: e.target.value })}
346 <div className="form-group">
347 <label>State/Province *</label>
350 value={formData.registrant_state}
351 onChange={(e) => setFormData({ ...formData, registrant_state: e.target.value })}
352 placeholder="CA, NSW, etc."
356 <div className="form-group">
357 <label>Postal Code *</label>
360 value={formData.registrant_postalCode}
361 onChange={(e) => setFormData({ ...formData, registrant_postalCode: e.target.value })}
367 {/* Optional: Advanced Contact Details Toggle */}
368 <div className="form-group">
371 className="btn btn-link"
372 onClick={toggleContactForm}
373 style={{ padding: 0, textDecoration: 'underline' }}
375 {showContactDetails ? '− Hide Advanced Contact Details' : '+ Specify Different Admin/Tech/Billing Contacts'}
377 <small style={{ display: 'block', marginTop: '4px', color: '#666' }}>
378 By default, all contact types will use the registrant information above
382 {/* TODO: Add admin, tech, billing contact sections if showContactDetails is true */}
383 {showContactDetails && (
384 <div className="advanced-contacts">
385 <p style={{ color: '#666', fontStyle: 'italic' }}>
386 Advanced contact management coming soon. All contacts will use registrant information for now.
391 {/* Price Display (if available) */}
393 <div className="price-summary" style={{
396 background: '#f5f5f5',
399 justifyContent: 'space-between',
402 <span style={{ fontWeight: 'bold' }}>Estimated Price:</span>
403 <span style={{ fontSize: '1.2em', fontWeight: 'bold' }}>
404 ${formData.price} {formData.currency || 'USD'}
409 <div className="modal-footer">
410 <button type="button" className="btn btn-secondary" onClick={onClose}>
413 <button type="submit" className="btn btn-primary">
414 Submit Registration Request