EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
DomainManagementModal.jsx
Go to the documentation of this file.
1import { useState } from 'react';
2import { apiFetch } from '../../../lib/api';
3import { notifySuccess, notifyError } from '../../../utils/notifications';
4
5export default function DomainManagementModal({ domain, onClose, onUpdate }) {
6 const [loading, setLoading] = useState(false);
7 const [authCode, setAuthCode] = useState('');
8 const [showAuthCode, setShowAuthCode] = useState(false);
9 const [contacts, setContacts] = useState(null);
10 const [showContacts, setShowContacts] = useState(false);
11
12 const metadata = domain.metadata || {};
13 const domainType = metadata.domain_type || 'registered';
14 const locked = metadata.locked;
15 const autoRenew = metadata.auto_renew;
16
17 // Check if domain is DNS-only (shouldn't show management)
18 if (domainType === 'dns_only') {
19 return (
20 <div className="modal-overlay" onClick={onClose}>
21 <div className="modal-content" onClick={(e) => e.stopPropagation()}>
22 <div className="modal-header">
23 <h2>Domain Management</h2>
24 <button className="modal-close" onClick={onClose}>&times;</button>
25 </div>
26 <div className="modal-body">
27 <div className="alert alert-info">
28 <span className="material-symbols-outlined">info</span>
29 <p>This is a DNS-only domain. Registration management features are not available.</p>
30 </div>
31 </div>
32 <div className="modal-footer">
33 <button className="btn btn-secondary" onClick={onClose}>Close</button>
34 </div>
35 </div>
36 </div>
37 );
38 }
39
40 // Lock domain
41 const handleLock = async () => {
42 if (!confirm(`Lock domain ${domain.domain_name}? This will prevent unauthorized transfers.`)) {
43 return;
44 }
45
46 setLoading(true);
47 try {
48 const res = await apiFetch(`/domains/${domain.domain_id}/lock`, {
49 method: 'POST'
50 });
51
52 if (!res.ok) {
53 const error = await res.json();
54 throw new Error(error.error || 'Failed to lock domain');
55 }
56
57 await notifySuccess('Domain Locked', `${domain.domain_name} has been locked`);
58 if (onUpdate) onUpdate();
59 onClose();
60 } catch (error) {
61 await notifyError('Lock Failed', error.message);
62 } finally {
63 setLoading(false);
64 }
65 };
66
67 // Unlock domain
68 const handleUnlock = async () => {
69 if (!confirm(`Unlock domain ${domain.domain_name}? This will allow transfers but reduce security.`)) {
70 return;
71 }
72
73 setLoading(true);
74 try {
75 const res = await apiFetch(`/domains/${domain.domain_id}/unlock`, {
76 method: 'POST'
77 });
78
79 if (!res.ok) {
80 const error = await res.json();
81 throw new Error(error.error || 'Failed to unlock domain');
82 }
83
84 await notifySuccess('Domain Unlocked', `${domain.domain_name} has been unlocked`);
85 if (onUpdate) onUpdate();
86 onClose();
87 } catch (error) {
88 await notifyError('Unlock Failed', error.message);
89 } finally {
90 setLoading(false);
91 }
92 };
93
94 // Get auth/transfer code
95 const handleGetAuthCode = async () => {
96 setLoading(true);
97 setAuthCode('');
98 setShowAuthCode(true);
99
100 try {
101 const res = await apiFetch(`/domains/${domain.domain_id}/auth-code`);
102
103 if (!res.ok) {
104 const error = await res.json();
105 throw new Error(error.error || 'Failed to get auth code');
106 }
107
108 const data = await res.json();
109 setAuthCode(data.auth_code || data.authCode || 'Not available');
110 } catch (error) {
111 await notifyError('Auth Code Failed', error.message);
112 setShowAuthCode(false);
113 } finally {
114 setLoading(false);
115 }
116 };
117
118 // Copy auth code to clipboard
119 const copyAuthCode = () => {
120 navigator.clipboard.writeText(authCode);
121 notifySuccess('Copied', 'Auth code copied to clipboard');
122 };
123
124 // Toggle auto-renew
125 const handleToggleAutoRenew = async () => {
126 const newState = !autoRenew;
127 const message = newState
128 ? `Enable auto-renew for ${domain.domain_name}? The domain will renew automatically before expiration.`
129 : `Disable auto-renew for ${domain.domain_name}? You will need to manually renew the domain.`;
130
131 if (!confirm(message)) {
132 return;
133 }
134
135 setLoading(true);
136 try {
137 const res = await apiFetch(`/domains/${domain.domain_id}/auto-renew`, {
138 method: 'POST',
139 headers: { 'Content-Type': 'application/json' },
140 body: JSON.stringify({ enabled: newState })
141 });
142
143 if (!res.ok) {
144 const error = await res.json();
145 throw new Error(error.error || 'Failed to update auto-renew');
146 }
147
148 await notifySuccess(
149 'Auto-Renew Updated',
150 `Auto-renew ${newState ? 'enabled' : 'disabled'} for ${domain.domain_name}`
151 );
152 if (onUpdate) onUpdate();
153 onClose();
154 } catch (error) {
155 await notifyError('Auto-Renew Failed', error.message);
156 } finally {
157 setLoading(false);
158 }
159 };
160
161 // Get contacts
162 const handleGetContacts = async () => {
163 setLoading(true);
164 setShowContacts(true);
165
166 try {
167 const res = await apiFetch(`/domains/${domain.domain_id}/contacts`);
168
169 if (!res.ok) {
170 const error = await res.json();
171 throw new Error(error.error || 'Failed to get contacts');
172 }
173
174 const data = await res.json();
175 setContacts(data);
176 } catch (error) {
177 await notifyError('Contacts Failed', error.message);
178 setShowContacts(false);
179 } finally {
180 setLoading(false);
181 }
182 };
183
184 return (
185 <div className="modal-overlay" onClick={onClose}>
186 <div className="modal-content" onClick={(e) => e.stopPropagation()} style={{ maxWidth: '700px' }}>
187 <div className="modal-header">
188 <h2>Domain Management: {domain.domain_name}</h2>
189 <button className="modal-close" onClick={onClose}>&times;</button>
190 </div>
191
192 <div className="modal-body">
193 {/* Domain Info */}
194 <div className="card" style={{ marginBottom: '20px', background: 'var(--bg-secondary)' }}>
195 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
196 <div>
197 <strong>Registrar:</strong>
198 <div style={{ textTransform: 'capitalize', marginTop: '5px' }}>
199 {domain.registrar}
200 </div>
201 </div>
202 <div>
203 <strong>Expiration:</strong>
204 <div style={{ marginTop: '5px' }}>
205 {domain.expiration_date ? new Date(domain.expiration_date).toLocaleDateString() : 'N/A'}
206 </div>
207 </div>
208 <div>
209 <strong>Lock Status:</strong>
210 <div style={{ marginTop: '5px' }}>
211 {locked === true && <span className="badge badge-success">🔒 Locked</span>}
212 {locked === false && <span className="badge badge-warning">🔓 Unlocked</span>}
213 {locked === null && <span className="badge badge-secondary">Unknown</span>}
214 </div>
215 </div>
216 <div>
217 <strong>Auto-Renew:</strong>
218 <div style={{ marginTop: '5px' }}>
219 {autoRenew ? (
220 <span className="badge badge-success">Enabled</span>
221 ) : (
222 <span className="badge badge-secondary">Disabled</span>
223 )}
224 </div>
225 </div>
226 </div>
227 </div>
228
229 {/* Management Actions */}
230 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '15px' }}>
231 {/* Lock/Unlock */}
232 <div className="card">
233 <h3 style={{ fontSize: '16px', marginBottom: '10px' }}>Domain Lock</h3>
234 <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '15px' }}>
235 {locked ? 'Domain is locked and protected from unauthorized transfers.' : 'Unlock domain to enable transfers.'}
236 </p>
237 {locked ? (
238 <button
239 className="btn btn-warning"
240 onClick={handleUnlock}
241 disabled={loading}
242 style={{ width: '100%' }}
243 >
244 <span className="material-symbols-outlined">lock_open</span>
245 Unlock Domain
246 </button>
247 ) : (
248 <button
249 className="btn btn-success"
250 onClick={handleLock}
251 disabled={loading}
252 style={{ width: '100%' }}
253 >
254 <span className="material-symbols-outlined">lock</span>
255 Lock Domain
256 </button>
257 )}
258 </div>
259
260 {/* Auto-Renew */}
261 <div className="card">
262 <h3 style={{ fontSize: '16px', marginBottom: '10px' }}>Auto-Renew</h3>
263 <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '15px' }}>
264 {autoRenew ? 'Domain will renew automatically before expiration.' : 'Enable automatic renewal to prevent expiration.'}
265 </p>
266 <button
267 className={autoRenew ? 'btn btn-secondary' : 'btn btn-success'}
268 onClick={handleToggleAutoRenew}
269 disabled={loading}
270 style={{ width: '100%' }}
271 >
272 <span className="material-symbols-outlined">
273 {autoRenew ? 'cancel' : 'refresh'}
274 </span>
275 {autoRenew ? 'Disable' : 'Enable'} Auto-Renew
276 </button>
277 </div>
278
279 {/* Transfer Code */}
280 <div className="card">
281 <h3 style={{ fontSize: '16px', marginBottom: '10px' }}>Transfer Code</h3>
282 <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '15px' }}>
283 Get the EPP/auth code needed to transfer this domain to another registrar.
284 </p>
285 <button
286 className="btn btn-primary"
287 onClick={handleGetAuthCode}
288 disabled={loading}
289 style={{ width: '100%' }}
290 >
291 <span className="material-symbols-outlined">vpn_key</span>
292 Get Transfer Code
293 </button>
294 </div>
295
296 {/* Contacts */}
297 <div className="card">
298 <h3 style={{ fontSize: '16px', marginBottom: '10px' }}>Contact Information</h3>
299 <p style={{ fontSize: '13px', color: 'var(--text-secondary)', marginBottom: '15px' }}>
300 View registrant, admin, technical, and billing contacts.
301 </p>
302 <button
303 className="btn btn-secondary"
304 onClick={handleGetContacts}
305 disabled={loading}
306 style={{ width: '100%' }}
307 >
308 <span className="material-symbols-outlined">contacts</span>
309 View Contacts
310 </button>
311 </div>
312 </div>
313
314 {/* Auth Code Display */}
315 {showAuthCode && (
316 <div className="card" style={{ marginTop: '20px', background: 'var(--bg-tertiary)' }}>
317 <h3 style={{ fontSize: '16px', marginBottom: '10px' }}>Transfer/EPP Code</h3>
318 {loading ? (
319 <div style={{ textAlign: 'center', padding: '20px' }}>
320 <div className="spinner"></div>
321 <p>Retrieving auth code...</p>
322 </div>
323 ) : (
324 <>
325 <div style={{
326 padding: '15px',
327 background: 'var(--bg-primary)',
328 borderRadius: '8px',
329 marginBottom: '10px',
330 fontFamily: 'monospace',
331 fontSize: '16px',
332 wordBreak: 'break-all'
333 }}>
334 {authCode}
335 </div>
336 <button
337 className="btn btn-secondary btn-sm"
338 onClick={copyAuthCode}
339 style={{ width: '100%' }}
340 >
341 <span className="material-symbols-outlined">content_copy</span>
342 Copy to Clipboard
343 </button>
344 <div className="alert alert-warning" style={{ marginTop: '15px' }}>
345 <span className="material-symbols-outlined">warning</span>
346 <p><strong>Important:</strong> Keep this code secure. Anyone with this code can initiate a domain transfer. Unlock the domain before transferring.</p>
347 </div>
348 </>
349 )}
350 </div>
351 )}
352
353 {/* Contacts Display */}
354 {showContacts && (
355 <div className="card" style={{ marginTop: '20px' }}>
356 <h3 style={{ fontSize: '16px', marginBottom: '15px' }}>Domain Contacts</h3>
357 {loading ? (
358 <div style={{ textAlign: 'center', padding: '20px' }}>
359 <div className="spinner"></div>
360 <p>Loading contacts...</p>
361 </div>
362 ) : contacts ? (
363 <div style={{ display: 'grid', gap: '15px' }}>
364 {['registrant', 'admin', 'tech', 'billing'].map(type => {
365 const contact = contacts[type];
366 if (!contact) return null;
367
368 return (
369 <div key={type} style={{
370 padding: '15px',
371 background: 'var(--bg-secondary)',
372 borderRadius: '8px'
373 }}>
374 <h4 style={{
375 fontSize: '14px',
376 textTransform: 'capitalize',
377 marginBottom: '10px',
378 color: 'var(--primary-color)'
379 }}>
380 {type} Contact
381 </h4>
382 <div style={{ fontSize: '13px', lineHeight: '1.8' }}>
383 {contact.Name && <div><strong>Name:</strong> {contact.Name}</div>}
384 {contact.Organization && <div><strong>Organization:</strong> {contact.Organization}</div>}
385 {contact.Email && <div><strong>Email:</strong> {contact.Email}</div>}
386 {contact.Phone && <div><strong>Phone:</strong> {contact.Phone}</div>}
387 {contact.Address && <div><strong>Address:</strong> {contact.Address}</div>}
388 {contact.City && <div><strong>City:</strong> {contact.City}</div>}
389 {contact.State && <div><strong>State:</strong> {contact.State}</div>}
390 {contact.PostalCode && <div><strong>Postal:</strong> {contact.PostalCode}</div>}
391 {contact.Country && <div><strong>Country:</strong> {contact.Country}</div>}
392 </div>
393 </div>
394 );
395 })}
396 </div>
397 ) : null}
398 </div>
399 )}
400 </div>
401
402 <div className="modal-footer">
403 <button className="btn btn-secondary" onClick={onClose}>
404 Close
405 </button>
406 </div>
407 </div>
408 </div>
409 );
410}