1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../../lib/api';
3import { notifySuccess, notifyError } from '../../../utils/notifications';
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'
14 if (domain.nameservers) {
16 const ns = typeof domain.nameservers === 'string'
17 ? JSON.parse(domain.nameservers)
19 setNameservers(Array.isArray(ns) ? ns : []);
26 if (domain.dns_records) {
28 const records = typeof domain.dns_records === 'string'
29 ? JSON.parse(domain.dns_records)
31 setDnsRecords(Array.isArray(records) ? records : []);
39 if (!showModal || !domain) return null;
41 const addNameserver = () => {
42 setNameservers([...nameservers, '']);
45 const updateNameserver = (index, value) => {
46 const updated = [...nameservers];
47 updated[index] = value;
48 setNameservers(updated);
51 const removeNameserver = (index) => {
52 setNameservers(nameservers.filter((_, i) => i !== index));
55 const addDnsRecord = () => {
58 { type: 'A', name: '@', value: '', ttl: 3600, priority: null }
62 const updateDnsRecord = (index, field, value) => {
63 const updated = [...dnsRecords];
64 updated[index] = { ...updated[index], [field]: value };
65 setDnsRecords(updated);
68 const removeDnsRecord = (index) => {
69 setDnsRecords(dnsRecords.filter((_, i) => i !== index));
72 const handleSave = async () => {
75 // Filter out empty nameservers
76 const cleanNameservers = nameservers.filter(ns => ns.trim());
78 // Validate DNS records
79 const validRecords = dnsRecords.filter(r => r.name && r.value);
81 // Update domain with DNS records - will automatically sync to Cloudflare
82 await apiFetch(`/domains/${domain.domain_id}`, {
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
93 domain.registrar === 'cloudflare'
94 ? 'DNS records updated and synced to Cloudflare successfully'
95 : 'DNS records updated successfully'
100 await notifyError('Update Failed', err.message || 'Failed to update DNS settings');
107 <div classNameName="modal-overlay" onClick={onClose}>
108 <div className="modal-content" style={{ maxWidth: '800px' }} onClick={(e) => e.stopPropagation()}>
109 <div className="modal-header">
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
119 <button className="modal-close" onClick={onClose}>
120 <span className="material-symbols-outlined">close</span>
124 <div style={{ borderBottom: '1px solid #e0e0e0', display: 'flex', gap: '4px', padding: '0 24px' }}>
126 className={`tab-button ${activeTab === 'nameservers' ? 'active' : ''}`}
127 onClick={() => setActiveTab('nameservers')}
129 padding: '12px 20px',
130 background: activeTab === 'nameservers' ? '#fff' : 'transparent',
132 borderBottom: activeTab === 'nameservers' ? '2px solid var(--primary-color)' : '2px solid transparent',
134 fontWeight: activeTab === 'nameservers' ? '600' : '400'
140 className={`tab-button ${activeTab === 'dns' ? 'active' : ''}`}
141 onClick={() => setActiveTab('dns')}
143 padding: '12px 20px',
144 background: activeTab === 'dns' ? '#fff' : 'transparent',
146 borderBottom: activeTab === 'dns' ? '2px solid var(--primary-color)' : '2px solid transparent',
148 fontWeight: activeTab === 'dns' ? '600' : '400'
155 <div className="modal-body" style={{ maxHeight: '500px', overflowY: 'auto' }}>
156 {activeTab === 'nameservers' && (
158 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
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
165 <button className="btn btn-sm" onClick={addNameserver}>
166 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>add</span>
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.
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' }}>
182 onChange={(e) => updateNameserver(index, e.target.value)}
183 placeholder="ns1.example.com"
184 style={{ flex: 1, padding: '8px 12px' }}
187 className="btn btn-danger btn-sm"
188 onClick={() => removeNameserver(index)}
189 style={{ padding: '8px 12px' }}
191 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>delete</span>
200 {activeTab === 'dns' && (
202 <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '16px' }}>
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
209 <button className="btn btn-sm" onClick={addDnsRecord}>
210 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>add</span>
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.
220 <div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
221 {dnsRecords.map((record, index) => (
222 <div key={index} style={{
224 gridTemplateColumns: '100px 1fr 2fr 100px 80px 40px',
226 alignItems: 'center',
228 background: '#f9f9f9',
233 onChange={(e) => updateDnsRecord(index, 'type', e.target.value)}
234 style={{ padding: '8px' }}
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>
248 onChange={(e) => updateDnsRecord(index, 'name', e.target.value)}
249 placeholder="@ or subdomain"
250 style={{ padding: '8px' }}
255 onChange={(e) => updateDnsRecord(index, 'value', e.target.value)}
257 style={{ padding: '8px' }}
262 onChange={(e) => updateDnsRecord(index, 'ttl', e.target.value)}
264 style={{ padding: '8px' }}
266 {(record.type === 'MX' || record.type === 'SRV') && (
269 value={record.priority || ''}
270 onChange={(e) => updateDnsRecord(index, 'priority', e.target.value)}
271 placeholder="Priority"
272 style={{ padding: '8px' }}
276 className="btn btn-danger btn-sm"
277 onClick={() => removeDnsRecord(index)}
278 style={{ padding: '8px 12px' }}
280 <span className="material-symbols-outlined" style={{ fontSize: '18px' }}>delete</span>
290 <div className="modal-footer">
291 <button type="button" className="btn btn-secondary" onClick={onClose}>
296 className="btn btn-primary"
300 {saving ? 'Saving...' : 'Save Changes'}