EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
DomainDNSModal.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../../lib/api';
3import { notifySuccess, notifyError } from '../../../utils/notifications';
4
5export default function DomainDNSModal({ showModal, domain, onClose, onUpdate }) {
6 const [nameservers, setNameservers] = useState([]);
7 const [dnsRecords, setDnsRecords] = useState([]);
8 const [saving, setSaving] = useState(false);
9 const [activeTab, setActiveTab] = useState('nameservers'); // 'nameservers' or 'dns'
10
11 useEffect(() => {
12 if (domain) {
13 // Parse nameservers
14 if (domain.nameservers) {
15 try {
16 const ns = typeof domain.nameservers === 'string'
17 ? JSON.parse(domain.nameservers)
18 : domain.nameservers;
19 setNameservers(Array.isArray(ns) ? ns : []);
20 } catch {
21 setNameservers([]);
22 }
23 }
24
25 // Parse DNS records
26 if (domain.dns_records) {
27 try {
28 const records = typeof domain.dns_records === 'string'
29 ? JSON.parse(domain.dns_records)
30 : domain.dns_records;
31 setDnsRecords(Array.isArray(records) ? records : []);
32 } catch {
33 setDnsRecords([]);
34 }
35 }
36 }
37 }, [domain]);
38
39 if (!showModal || !domain) return null;
40
41 const addNameserver = () => {
42 setNameservers([...nameservers, '']);
43 };
44
45 const updateNameserver = (index, value) => {
46 const updated = [...nameservers];
47 updated[index] = value;
48 setNameservers(updated);
49 };
50
51 const removeNameserver = (index) => {
52 setNameservers(nameservers.filter((_, i) => i !== index));
53 };
54
55 const addDnsRecord = () => {
56 setDnsRecords([
57 ...dnsRecords,
58 { type: 'A', name: '@', value: '', ttl: 3600, priority: null }
59 ]);
60 };
61
62 const updateDnsRecord = (index, field, value) => {
63 const updated = [...dnsRecords];
64 updated[index] = { ...updated[index], [field]: value };
65 setDnsRecords(updated);
66 };
67
68 const removeDnsRecord = (index) => {
69 setDnsRecords(dnsRecords.filter((_, i) => i !== index));
70 };
71
72 const handleSave = async () => {
73 setSaving(true);
74 try {
75 // Filter out empty nameservers
76 const cleanNameservers = nameservers.filter(ns => ns.trim());
77
78 // Validate DNS records
79 const validRecords = dnsRecords.filter(r => r.name && r.value);
80
81 // Update domain with DNS records - will automatically sync to Cloudflare
82 await apiFetch(`/domains/${domain.domain_id}`, {
83 method: 'PUT',
84 body: JSON.stringify({
85 nameservers: JSON.stringify(cleanNameservers),
86 dns_records: JSON.stringify(validRecords),
87 sync_to_cloudflare: domain.registrar === 'cloudflare' // Sync if Cloudflare domain
88 })
89 });
90
91 await notifySuccess(
92 'DNS Updated',
93 domain.registrar === 'cloudflare'
94 ? 'DNS records updated and synced to Cloudflare successfully'
95 : 'DNS records updated successfully'
96 );
97 onUpdate();
98 onClose();
99 } catch (err) {
100 await notifyError('Update Failed', err.message || 'Failed to update DNS settings');
101 } finally {
102 setSaving(false);
103 }
104 };
105
106 return (
107 <div classNameName="modal-overlay" onClick={onClose}>
108 <div className="modal-content" style={{ maxWidth: '800px' }} onClick={(e) => e.stopPropagation()}>
109 <div className="modal-header">
110 <div>
111 <h2>DNS Management - {domain.domain_name}</h2>
112 {domain.registrar === 'cloudflare' && (
113 <small style={{ color: '#1DA1F2', display: 'block', marginTop: '4px' }}>
114 <span className="material-symbols-outlined" style={{ fontSize: '14px', verticalAlign: 'middle' }}>cloud</span>
115 {' '}Changes will sync to Cloudflare automatically
116 </small>
117 )}
118 </div>
119 <button className="modal-close" onClick={onClose}>
120 <span className="material-symbols-outlined">close</span>
121 </button>
122 </div>
123
124 <div style={{ borderBottom: '1px solid #e0e0e0', display: 'flex', gap: '4px', padding: '0 24px' }}>
125 <button
126 className={`tab-button ${activeTab === 'nameservers' ? 'active' : ''}`}
127 onClick={() => setActiveTab('nameservers')}
128 style={{
129 padding: '12px 20px',
130 background: activeTab === 'nameservers' ? '#fff' : 'transparent',
131 border: 'none',
132 borderBottom: activeTab === 'nameservers' ? '2px solid var(--primary-color)' : '2px solid transparent',
133 cursor: 'pointer',
134 fontWeight: activeTab === 'nameservers' ? '600' : '400'
135 }}
136 >
137 Nameservers
138 </button>
139 <button
140 className={`tab-button ${activeTab === 'dns' ? 'active' : ''}`}
141 onClick={() => setActiveTab('dns')}
142 style={{
143 padding: '12px 20px',
144 background: activeTab === 'dns' ? '#fff' : 'transparent',
145 border: 'none',
146 borderBottom: activeTab === 'dns' ? '2px solid var(--primary-color)' : '2px solid transparent',
147 cursor: 'pointer',
148 fontWeight: activeTab === 'dns' ? '600' : '400'
149 }}
150 >
151 DNS Records
152 </button>
153 </div>
154
155 <div className="modal-body" style={{ maxHeight: '500px', overflowY: 'auto' }}>
156 {activeTab === 'nameservers' && (
157 <div>
158 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
159 <div>
160 <h3 style={{ margin: 0 }}>Nameservers</h3>
161 <p style={{ margin: '4px 0 0 0', fontSize: '0.9em', color: '#666' }}>
162 Configure the authoritative nameservers for this domain
163 </p>
164 </div>
165 <button className="btn btn-sm" onClick={addNameserver}>
166 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>add</span>
167 Add NS
168 </button>
169 </div>
170
171 {nameservers.length === 0 ? (
172 <div style={{ padding: '24px', textAlign: 'center', color: '#999', background: '#f5f5f5', borderRadius: '8px' }}>
173 No nameservers configured. Click "Add NS" to add one.
174 </div>
175 ) : (
176 <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
177 {nameservers.map((ns, index) => (
178 <div key={index} style={{ display: 'flex', gap: '8px', alignItems: 'center' }}>
179 <input
180 type="text"
181 value={ns}
182 onChange={(e) => updateNameserver(index, e.target.value)}
183 placeholder="ns1.example.com"
184 style={{ flex: 1, padding: '8px 12px' }}
185 />
186 <button
187 className="btn btn-danger btn-sm"
188 onClick={() => removeNameserver(index)}
189 style={{ padding: '8px 12px' }}
190 >
191 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>delete</span>
192 </button>
193 </div>
194 ))}
195 </div>
196 )}
197 </div>
198 )}
199
200 {activeTab === 'dns' && (
201 <div>
202 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
203 <div>
204 <h3 style={{ margin: 0 }}>DNS Records</h3>
205 <p style={{ margin: '4px 0 0 0', fontSize: '0.9em', color: '#666' }}>
206 Manage A, AAAA, CNAME, MX, TXT, and other DNS records
207 </p>
208 </div>
209 <button className="btn btn-sm" onClick={addDnsRecord}>
210 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>add</span>
211 Add Record
212 </button>
213 </div>
214
215 {dnsRecords.length === 0 ? (
216 <div style={{ padding: '24px', textAlign: 'center', color: '#999', background: '#f5f5f5', borderRadius: '8px' }}>
217 No DNS records configured. Click "Add Record" to add one.
218 </div>
219 ) : (
220 <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
221 {dnsRecords.map((record, index) => (
222 <div key={index} style={{
223 display: 'grid',
224 gridTemplateColumns: '100px 1fr 2fr 100px 80px 40px',
225 gap: '8px',
226 alignItems: 'center',
227 padding: '12px',
228 background: '#f9f9f9',
229 borderRadius: '6px'
230 }}>
231 <select
232 value={record.type}
233 onChange={(e) => updateDnsRecord(index, 'type', e.target.value)}
234 style={{ padding: '8px' }}
235 >
236 <option value="A">A</option>
237 <option value="AAAA">AAAA</option>
238 <option value="CNAME">CNAME</option>
239 <option value="MX">MX</option>
240 <option value="TXT">TXT</option>
241 <option value="NS">NS</option>
242 <option value="SRV">SRV</option>
243 <option value="CAA">CAA</option>
244 </select>
245 <input
246 type="text"
247 value={record.name}
248 onChange={(e) => updateDnsRecord(index, 'name', e.target.value)}
249 placeholder="@ or subdomain"
250 style={{ padding: '8px' }}
251 />
252 <input
253 type="text"
254 value={record.value}
255 onChange={(e) => updateDnsRecord(index, 'value', e.target.value)}
256 placeholder="Value"
257 style={{ padding: '8px' }}
258 />
259 <input
260 type="number"
261 value={record.ttl}
262 onChange={(e) => updateDnsRecord(index, 'ttl', e.target.value)}
263 placeholder="TTL"
264 style={{ padding: '8px' }}
265 />
266 {(record.type === 'MX' || record.type === 'SRV') && (
267 <input
268 type="number"
269 value={record.priority || ''}
270 onChange={(e) => updateDnsRecord(index, 'priority', e.target.value)}
271 placeholder="Priority"
272 style={{ padding: '8px' }}
273 />
274 )}
275 <button
276 className="btn btn-danger btn-sm"
277 onClick={() => removeDnsRecord(index)}
278 style={{ padding: '8px 12px' }}
279 >
280 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>delete</span>
281 </button>
282 </div>
283 ))}
284 </div>
285 )}
286 </div>
287 )}
288 </div>
289
290 <div className="modal-footer">
291 <button type="button" className="btn btn-secondary" onClick={onClose}>
292 Cancel
293 </button>
294 <button
295 type="button"
296 className="btn btn-primary"
297 onClick={handleSave}
298 disabled={saving}
299 >
300 {saving ? 'Saving...' : 'Save Changes'}
301 </button>
302 </div>
303 </div>
304 </div>
305 );
306}