1import { useState } from 'react';
2import { apiFetch } from '../../../lib/api';
3import { notifySuccess, notifyError } from '../../../utils/notifications';
5export default function DomainRequestDetailModal({ request, onClose, onRequestProcessed }) {
6 const [processing, setProcessing] = useState(false);
7 const [approvalNotes, setApprovalNotes] = useState('');
8 const [selectedRegistrar, setSelectedRegistrar] = useState('cloudflare-registrar');
10 const handleApprove = async () => {
11 const registrarNames = {
12 cloudflare: 'Cloudflare (DNS-only)',
13 'cloudflare-registrar': 'Cloudflare Registrar',
18 if (!confirm(`Approve domain registration for ${request.domain_name}? This will attempt registration via ${registrarNames[selectedRegistrar]}.`)) {
24 await apiFetch(`/domain-requests/${request.request_id}/approve`, {
26 body: JSON.stringify({
27 approval_notes: approvalNotes,
28 registrar: selectedRegistrar
33 'Registration Approved',
34 `Domain registration for ${request.domain_name} has been approved and is being processed`
39 console.error('Error approving request:', err);
40 await notifyError('Approval Failed', err.message || 'Failed to approve registration request');
46 const handleDeny = async () => {
47 if (!approvalNotes.trim()) {
48 await notifyError('Notes Required', 'Please provide a reason for denying this request');
52 if (!confirm(`Deny domain registration for ${request.domain_name}?`)) {
58 await apiFetch(`/domain-requests/${request.request_id}/deny`, {
60 body: JSON.stringify({ approval_notes: approvalNotes })
64 'Registration Denied',
65 `Domain registration for ${request.domain_name} has been denied`
70 console.error('Error denying request:', err);
71 await notifyError('Denial Failed', err.message || 'Failed to deny registration request');
77 const formatContact = (contact) => {
78 if (!contact) return null;
80 <div style={{ fontSize: '0.9em', lineHeight: '1.6' }}>
81 <div><strong>{contact.firstName} {contact.lastName}</strong></div>
82 {contact.organization && <div>{contact.organization}</div>}
83 <div>{contact.email}</div>
84 <div>{contact.phone}</div>
85 <div>{contact.address}</div>
86 <div>{contact.city}, {contact.state} {contact.postalCode}</div>
87 <div>{contact.country}</div>
92 const formatDate = (dateString) => {
93 if (!dateString) return '-';
94 return new Date(dateString).toLocaleString('en-US', {
103 const getStatusBadge = (status) => {
104 const statusColors = {
105 pending: { bg: '#fff3e0', text: '#e65100', label: 'Pending Review' },
106 approved: { bg: '#e8f5e9', text: '#2e7d32', label: 'Approved' },
107 denied: { bg: '#ffebee', text: '#c62828', label: 'Denied' },
108 registered: { bg: '#e3f2fd', text: '#1565c0', label: 'Successfully Registered' },
109 failed: { bg: '#fce4ec', text: '#ad1457', label: 'Registration Failed' }
112 const style = statusColors[status] || statusColors.pending;
117 borderRadius: '16px',
120 background: style.bg,
128 const isPending = request.status === 'pending';
131 <div className="modal-overlay" onClick={onClose}>
132 <div className="modal-content large-modal" onClick={(e) => e.stopPropagation()}>
133 <div className="modal-header">
135 <h2>Domain Registration Request</h2>
136 <div style={{ marginTop: '8px' }}>{getStatusBadge(request.status)}</div>
138 <button className="modal-close" onClick={onClose}>
139 <span className="material-symbols-outlined">close</span>
143 <div className="modal-body">
144 {/* Domain Information */}
146 <h3>Domain Information</h3>
147 <div className="info-grid">
148 <div className="info-item">
149 <label>Domain:</label>
150 <strong style={{ fontSize: '1.1em' }}>{request.domain_name}</strong>
152 <div className="info-item">
153 <label>Registration Period:</label>
154 <span>{request.years} year{request.years !== 1 ? 's' : ''}</span>
156 <div className="info-item">
157 <label>Price:</label>
159 {request.price ? `$${request.price} ${request.currency}` : 'Not specified'}
162 <div className="info-item">
163 <label>Customer:</label>
164 <span>{request.customer_name || '-'}</span>
166 <div className="info-item">
167 <label>Contract:</label>
168 <span>{request.contract_title || 'No contract'}</span>
170 <div className="info-item">
171 <label>Tenant:</label>
172 <span>{request.tenant_subdomain || '-'}</span>
176 {request.nameservers && request.nameservers.length > 0 && (
177 <div className="info-item" style={{ marginTop: '16px' }}>
178 <label>Nameservers:</label>
179 <ul style={{ marginTop: '4px', paddingLeft: '20px' }}>
180 {request.nameservers.map((ns, idx) => (
181 <li key={idx}>{ns}</li>
188 {/* Request Details */}
190 <h3>Request Details</h3>
191 <div className="info-grid">
192 <div className="info-item">
193 <label>Requested By:</label>
194 <span>{request.requested_by_name || 'Unknown'}</span>
196 <div className="info-item">
197 <label>Requested At:</label>
198 <span>{formatDate(request.requested_at)}</span>
200 {request.reviewed_by_name && (
202 <div className="info-item">
203 <label>Reviewed By:</label>
204 <span>{request.reviewed_by_name}</span>
206 <div className="info-item">
207 <label>Reviewed At:</label>
208 <span>{formatDate(request.reviewed_at)}</span>
215 {/* Contact Information */}
217 <h3>Registrant Contact</h3>
218 {formatContact(request.registrant_contact)}
222 {request.approval_notes && (
224 <h3>Review Notes</h3>
227 background: 'var(--surface)',
228 border: '1px solid var(--border)',
230 whiteSpace: 'pre-wrap'
232 {request.approval_notes}
237 {/* Registration Error */}
238 {request.registration_error && (
240 <h3>Registration Error</h3>
243 background: '#ffebee',
244 border: '1px solid #ef5350',
247 whiteSpace: 'pre-wrap'
249 {request.registration_error}
254 {/* Approval/Denial Section (only for pending requests) */}
257 <h3>Admin Action</h3>
259 <div className="form-group">
260 <label>Registrar:</label>
262 value={selectedRegistrar}
263 onChange={(e) => setSelectedRegistrar(e.target.value)}
264 style={{ width: '100%', padding: '8px', borderRadius: '4px', border: '1px solid var(--border)' }}
266 <option value="cloudflare-registrar">Cloudflare Registrar (Full domain registration at cost)</option>
267 <option value="cloudflare">Cloudflare (DNS-only zone, no registration)</option>
268 <option value="enom">eNom (Full domain registration + DNS)</option>
269 <option value="moniker">Moniker (Full domain registration + DNS)</option>
271 <small style={{ display: 'block', marginTop: '6px', color: 'var(--text-secondary)' }}>
272 {selectedRegistrar === 'cloudflare-registrar' && '✓ Registers domain at cost price via Cloudflare. No markup, transparent pricing.'}
273 {selectedRegistrar === 'cloudflare' && '✓ Creates DNS zone only. Customer must already own domain or will register elsewhere.'}
274 {selectedRegistrar === 'enom' && '⚠️ Registers domain at eNom and sets up DNS. Requires eNom API credentials.'}
275 {selectedRegistrar === 'moniker' && '⚠️ Registers domain at Moniker and sets up DNS. Requires Moniker API credentials.'}
279 <div className="form-group">
280 <label>Notes (optional for approval, required for denial):</label>
282 value={approvalNotes}
283 onChange={(e) => setApprovalNotes(e.target.value)}
284 placeholder="Add notes about this request (e.g., 'Verified customer identity', 'Insufficient funds', etc.)"
286 style={{ width: '100%' }}
292 background: selectedRegistrar === 'cloudflare' ? '#e3f2fd' : (selectedRegistrar === 'cloudflare-registrar' ? '#e8f5e9' : '#fff3e0'),
293 border: `1px solid ${selectedRegistrar === 'cloudflare' ? '#2196f3' : (selectedRegistrar === 'cloudflare-registrar' ? '#4caf50' : '#ff9800')}`,
297 <strong>{selectedRegistrar === 'cloudflare' || selectedRegistrar === 'cloudflare-registrar' ? 'ℹ️' : '⚠️'} Important:</strong>
298 <ul style={{ marginTop: '8px', paddingLeft: '20px', marginBottom: 0 }}>
299 {selectedRegistrar === 'cloudflare-registrar' && (
301 <li>Registers domain via Cloudflare Registrar at cost (no markup)</li>
302 <li>Transparent pricing - you pay what Cloudflare pays</li>
303 <li>Automatic DNS zone creation included</li>
304 <li>Registration cannot be undone if successful</li>
307 {selectedRegistrar === 'cloudflare' && (
309 <li>Creates a free DNS zone in Cloudflare - no domain registration</li>
310 <li>Customer must update nameservers at their registrar</li>
311 <li>Domain will not resolve until nameservers are updated</li>
314 {(selectedRegistrar === 'enom' || selectedRegistrar === 'moniker') && (
316 <li>Approval will immediately attempt to register the domain via {selectedRegistrar === 'enom' ? 'eNom' : 'Moniker'} API</li>
317 <li>Ensure sufficient credits are available in the {selectedRegistrar === 'enom' ? 'eNom' : 'Moniker'} account</li>
318 <li>Registration cannot be undone if successful</li>
327 <div className="modal-footer">
332 className="btn btn-secondary"
334 disabled={processing}
338 <div style={{ display: 'flex', gap: '8px' }}>
343 disabled={processing}
344 style={{ background: '#ef5350', color: 'white' }}
346 {processing ? 'Processing...' : 'Deny Request'}
350 className="btn btn-primary"
351 onClick={handleApprove}
352 disabled={processing}
354 {processing ? 'Processing...' : 'Approve & Register'}
361 className="btn btn-primary"