EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
DomainRegistrationModal.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../../lib/api';
3
4export default function DomainRegistrationModal({
5 showModal,
6 formData,
7 setFormData,
8 customers,
9 contracts,
10 onSubmit,
11 onClose
12}) {
13 const [showContactDetails, setShowContactDetails] = useState(false);
14
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('');
20
21 // Search for customers (debounced)
22 useEffect(() => {
23 if (customerSelected) return;
24
25 const searchCustomers = async () => {
26 if (!customerQuery || !customerQuery.trim()) {
27 setCustomerSuggestions([]);
28 return;
29 }
30 try {
31 const res = await apiFetch(`/customers?search=${encodeURIComponent(customerQuery)}`, {
32 method: 'GET',
33 credentials: 'include'
34 });
35 if (!res.ok) return;
36 const data = await res.json();
37 setCustomerSuggestions(data.customers || []);
38 } catch (err) {
39 console.error(err);
40 }
41 };
42 const timer = setTimeout(searchCustomers, 300);
43 return () => clearTimeout(timer);
44 }, [customerQuery, customerSelected]);
45
46 if (!showModal) return null;
47
48 const handleSubmit = (e) => {
49 e.preventDefault();
50 onSubmit(formData);
51 };
52
53 // Toggle between simple and detailed contact forms
54 const toggleContactForm = () => {
55 setShowContactDetails(!showContactDetails);
56 };
57
58 return (
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>
65 </button>
66 </div>
67 <form onSubmit={handleSubmit}>
68 <div className="modal-body">
69 <div className="info-banner">
70 <span className="material-symbols-outlined">info</span>
71 <div>
72 Domain registration requests require admin approval before processing.
73 You'll be notified once your request is reviewed.
74 </div>
75 </div>
76
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' }}>
84 <input
85 type="radio"
86 name="domain_type"
87 value="registration"
88 checked={formData.domain_type === 'registration' || !formData.domain_type}
89 onChange={(e) => setFormData({ ...formData, domain_type: e.target.value })}
90 />
91 <div>
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)
95 </div>
96 </div>
97 </label>
98 <label style={{ display: 'flex', alignItems: 'center', gap: '8px', cursor: 'pointer' }}>
99 <input
100 type="radio"
101 name="domain_type"
102 value="dns-only"
103 checked={formData.domain_type === 'dns-only'}
104 onChange={(e) => setFormData({ ...formData, domain_type: e.target.value })}
105 />
106 <div>
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)
110 </div>
111 </div>
112 </label>
113 </div>
114 </div>
115 </div>
116
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>
120 <div>
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.
124 </div>
125 </div>
126 )}
127
128 <div className="form-row">
129 <div className="form-group">
130 <label>Domain Name *</label>
131 <input
132 type="text"
133 value={formData.domain_name}
134 onChange={(e) => setFormData({ ...formData, domain_name: e.target.value })}
135 placeholder="example.com"
136 required
137 />
138 </div>
139 {formData.domain_type !== 'dns-only' && (
140 <div className="form-group">
141 <label>Registration Period *</label>
142 <select
143 value={formData.years}
144 onChange={(e) => setFormData({ ...formData, years: parseInt(e.target.value) })}
145 required
146 >
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>
152 </select>
153 </div>
154 )}
155 </div>
156
157 <div className="form-row">
158 <div className="form-group">
159 <label>Customer (Optional)</label>
160 <div className="search-input" style={{ position: 'relative' }}>
161 <input
162 type="text"
163 placeholder="Search customer (or leave blank for none)"
164 value={customerQuery}
165 onChange={(e) => {
166 setCustomerQuery(e.target.value);
167 setCustomerName('');
168 setCustomerSelected(false);
169 setFormData({ ...formData, customer_id: null });
170 }}
171 style={{
172 width: '100%',
173 ...(customerQuery && !customerSelected ? { borderColor: '#f0ad4e' } : {})
174 }}
175 />
176 {customerSuggestions.length > 0 && !customerSelected && (
177 <div className="suggestions-dropdown" style={{
178 position: 'absolute',
179 top: '100%',
180 left: 0,
181 right: 0,
182 background: 'white',
183 border: '1px solid #ddd',
184 borderRadius: '4px',
185 maxHeight: '200px',
186 overflowY: 'auto',
187 zIndex: 1000,
188 boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
189 }}>
190 {customerSuggestions.map(c => (
191 <div
192 key={c.customer_id}
193 className="suggestion-item"
194 onClick={() => {
195 setCustomerName(c.name);
196 setCustomerQuery(c.name);
197 setCustomerSuggestions([]);
198 setCustomerSelected(true);
199 setFormData({ ...formData, customer_id: c.customer_id });
200 }}
201 style={{
202 padding: '8px 12px',
203 cursor: 'pointer',
204 borderBottom: '1px solid #eee'
205 }}
206 onMouseOver={(e) => e.currentTarget.style.background = '#f5f5f5'}
207 onMouseOut={(e) => e.currentTarget.style.background = 'white'}
208 >
209 {c.name} <span style={{ color: '#999', fontSize: '0.9em' }}>({c.email || 'No email'})</span>
210 </div>
211 ))}
212 </div>
213 )}
214 {customerQuery && !customerSelected && (
215 <small style={{ display: 'block', marginTop: '4px', color: '#f0ad4e' }}>
216 Please select a customer from the dropdown or clear to continue
217 </small>
218 )}
219 {!customerQuery && (
220 <small style={{ display: 'block', marginTop: '4px', color: '#666' }}>
221 Leave blank if not for a specific customer
222 </small>
223 )}
224 </div>
225 </div>
226 <div className="form-group">
227 <label>Contract (Optional)</label>
228 <select
229 value={formData.contract_id}
230 onChange={(e) => setFormData({ ...formData, contract_id: e.target.value })}
231 >
232 <option value="">No contract</option>
233 {contracts.map((c) => (
234 <option key={c.contract_id} value={c.contract_id}>
235 {c.title}
236 </option>
237 ))}
238 </select>
239 <small>Link this domain to a contract for automated billing</small>
240 </div>
241 </div>
242
243 {/* Nameservers */}
244 <div className="form-group">
245 <label>Nameservers (optional, one per line)</label>
246 <textarea
247 value={formData.nameservers}
248 onChange={(e) => setFormData({ ...formData, nameservers: e.target.value })}
249 placeholder="ns1.example.com&#10;ns2.example.com&#10;Leave blank to use registrar defaults"
250 rows="3"
251 />
252 <small>If left blank, registrar default nameservers will be used</small>
253 </div>
254
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>
260 <input
261 type="text"
262 value={formData.registrant_firstName}
263 onChange={(e) => setFormData({ ...formData, registrant_firstName: e.target.value })}
264 required
265 />
266 </div>
267 <div className="form-group">
268 <label>Last Name *</label>
269 <input
270 type="text"
271 value={formData.registrant_lastName}
272 onChange={(e) => setFormData({ ...formData, registrant_lastName: e.target.value })}
273 required
274 />
275 </div>
276 </div>
277
278 <div className="form-row">
279 <div className="form-group">
280 <label>Organization</label>
281 <input
282 type="text"
283 value={formData.registrant_organization}
284 onChange={(e) => setFormData({ ...formData, registrant_organization: e.target.value })}
285 placeholder="Optional"
286 />
287 </div>
288 <div className="form-group">
289 <label>Email *</label>
290 <input
291 type="email"
292 value={formData.registrant_email}
293 onChange={(e) => setFormData({ ...formData, registrant_email: e.target.value })}
294 required
295 />
296 </div>
297 </div>
298
299 <div className="form-row">
300 <div className="form-group">
301 <label>Phone *</label>
302 <input
303 type="tel"
304 value={formData.registrant_phone}
305 onChange={(e) => setFormData({ ...formData, registrant_phone: e.target.value })}
306 placeholder="+1.5551234567"
307 required
308 />
309 <small>Format: +CountryCode.Phone (e.g., +1.5551234567)</small>
310 </div>
311 <div className="form-group">
312 <label>Country *</label>
313 <input
314 type="text"
315 value={formData.registrant_country}
316 onChange={(e) => setFormData({ ...formData, registrant_country: e.target.value })}
317 placeholder="US, AU, GB, etc."
318 maxLength="2"
319 required
320 />
321 <small>2-letter country code</small>
322 </div>
323 </div>
324
325 <div className="form-group">
326 <label>Address *</label>
327 <input
328 type="text"
329 value={formData.registrant_address}
330 onChange={(e) => setFormData({ ...formData, registrant_address: e.target.value })}
331 placeholder="Street address"
332 required
333 />
334 </div>
335
336 <div className="form-row">
337 <div className="form-group">
338 <label>City *</label>
339 <input
340 type="text"
341 value={formData.registrant_city}
342 onChange={(e) => setFormData({ ...formData, registrant_city: e.target.value })}
343 required
344 />
345 </div>
346 <div className="form-group">
347 <label>State/Province *</label>
348 <input
349 type="text"
350 value={formData.registrant_state}
351 onChange={(e) => setFormData({ ...formData, registrant_state: e.target.value })}
352 placeholder="CA, NSW, etc."
353 required
354 />
355 </div>
356 <div className="form-group">
357 <label>Postal Code *</label>
358 <input
359 type="text"
360 value={formData.registrant_postalCode}
361 onChange={(e) => setFormData({ ...formData, registrant_postalCode: e.target.value })}
362 required
363 />
364 </div>
365 </div>
366
367 {/* Optional: Advanced Contact Details Toggle */}
368 <div className="form-group">
369 <button
370 type="button"
371 className="btn btn-link"
372 onClick={toggleContactForm}
373 style={{ padding: 0, textDecoration: 'underline' }}
374 >
375 {showContactDetails ? '− Hide Advanced Contact Details' : '+ Specify Different Admin/Tech/Billing Contacts'}
376 </button>
377 <small style={{ display: 'block', marginTop: '4px', color: '#666' }}>
378 By default, all contact types will use the registrant information above
379 </small>
380 </div>
381
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.
387 </p>
388 </div>
389 )}
390
391 {/* Price Display (if available) */}
392 {formData.price && (
393 <div className="price-summary" style={{
394 marginTop: '20px',
395 padding: '15px',
396 background: '#f5f5f5',
397 borderRadius: '4px',
398 display: 'flex',
399 justifyContent: 'space-between',
400 alignItems: 'center'
401 }}>
402 <span style={{ fontWeight: 'bold' }}>Estimated Price:</span>
403 <span style={{ fontSize: '1.2em', fontWeight: 'bold' }}>
404 ${formData.price} {formData.currency || 'USD'}
405 </span>
406 </div>
407 )}
408 </div>
409 <div className="modal-footer">
410 <button type="button" className="btn btn-secondary" onClick={onClose}>
411 Cancel
412 </button>
413 <button type="submit" className="btn btn-primary">
414 Submit Registration Request
415 </button>
416 </div>
417 </form>
418 </div>
419 </div>
420 );
421}