import React, { useCallback, useEffect, useState } from 'react';
import {
FiRefreshCw, FiList, FiLogOut, FiSettings, FiKey, FiTrash2, FiPlus, FiInbox,
FiUsers, FiUser, FiHardDrive, FiDollarSign, FiActivity,
} from 'react-icons/fi';
import Login from './components/Login';
import Toast from './components/Toast';
import LoadingOverlay from './components/LoadingOverlay';
import UsageBar, { formatBytes } from './components/UsageBar';
import MailboxSettingsModal from './components/MailboxSettingsModal';
import NewMailboxModal from './components/NewMailboxModal';
import PasswordResetModal from './components/PasswordResetModal';
import ConfirmDialog from './components/ConfirmDialog';
import AuditLogModal from './components/AuditLogModal';
import AdminUsersModal from './components/AdminUsersModal';
import ChangeMyPasswordModal from './components/ChangeMyPasswordModal';
import DomainQuotaModal from './components/DomainQuotaModal';
import BillingModal from './components/BillingModal';
import HealthModal from './components/HealthModal';
import HealthBanner from './components/HealthBanner';
import { authAPI, domainsAPI, mailboxesAPI, healthAPI } from './services/api';
function App() {
const [user, setUser] = useState(null);
const [bootChecked, setBootChecked] = useState(false);
const [domains, setDomains] = useState([]);
const [selectedDomain, setSelectedDomain] = useState(null);
const [mailboxes, setMailboxes] = useState([]);
const [busyMessage, setBusyMessage] = useState('');
const [toast, setToast] = useState(null);
const [settingsTarget, setSettingsTarget] = useState(null);
const [pwTarget, setPwTarget] = useState(null);
const [deleteTarget, setDeleteTarget] = useState(null);
const [showNew, setShowNew] = useState(false);
const [showAudit, setShowAudit] = useState(false);
const [showAdmins, setShowAdmins] = useState(false);
const [showChangePw, setShowChangePw] = useState(false);
const [showDomainQuota, setShowDomainQuota] = useState(false);
const [showBilling, setShowBilling] = useState(false);
const [showHealth, setShowHealth] = useState(false);
// Persisted health status for the currently selected domain (drives the banner).
const [healthStatus, setHealthStatus] = useState(null);
const showToast = useCallback((message, type = 'success') => {
setToast({ message, type });
}, []);
const isSuperAdmin = user?.role === 'super_admin';
const hideDomainList = !isSuperAdmin && domains.length <= 1;
const loadDomains = useCallback(async (resync = false) => {
const list = await domainsAPI.list(resync);
setDomains(list);
return list;
}, []);
const loadMailboxes = useCallback(async (domain, refreshQuota = false) => {
if (!domain) { setMailboxes([]); return; }
const list = await mailboxesAPI.list(domain, refreshQuota);
setMailboxes(list);
}, []);
// Load (or re-load) the persisted health status for a domain.
// Cheap call — just reads from PostgreSQL, no checks are run.
const loadHealthStatus = useCallback(async (domain) => {
if (!domain) { setHealthStatus(null); return; }
try {
const status = await healthAPI.getStatus(domain);
setHealthStatus(status); // null if never checked, that's fine
} catch (err) {
// Silent: don't block the UI just because health status load failed.
console.warn('Failed to load health status:', err);
setHealthStatus(null);
}
}, []);
useEffect(() => {
(async () => {
try {
const me = await authAPI.me();
setUser(me);
} catch {
setUser(null);
} finally {
setBootChecked(true);
}
})();
}, []);
useEffect(() => {
if (!user) return;
(async () => {
setBusyMessage('Loading domains...');
try {
const list = await loadDomains(isSuperAdmin);
const first = list[0]?.domain || null;
setSelectedDomain(first);
if (first) {
setBusyMessage('Refreshing quotas...');
await loadMailboxes(first, true);
await loadHealthStatus(first);
}
} catch (err) {
showToast(`Failed to load: ${err.message}`, 'error');
} finally {
setBusyMessage('');
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [user]);
const selectDomain = async (domain) => {
if (domain === selectedDomain) return;
setSelectedDomain(domain);
setBusyMessage('Loading mailboxes...');
try {
await loadMailboxes(domain, true);
await loadHealthStatus(domain);
} catch (err) {
showToast(`Failed to load mailboxes: ${err.message}`, 'error');
} finally {
setBusyMessage('');
}
};
const handleResync = async () => {
setBusyMessage('Re-syncing from DMS...');
try {
await domainsAPI.resync();
const list = await loadDomains(false);
if (selectedDomain && !list.find((d) => d.domain === selectedDomain)) {
const first = list[0]?.domain || null;
setSelectedDomain(first);
if (first) {
await loadMailboxes(first, false);
await loadHealthStatus(first);
}
} else if (selectedDomain) {
await loadMailboxes(selectedDomain, false);
}
showToast('DMS sync complete', 'success');
} catch (err) {
showToast(`Sync failed: ${err.message}`, 'error');
} finally {
setBusyMessage('');
}
};
const handleLogout = async () => {
try { await authAPI.logout(); } catch { /* ignore */ }
setUser(null);
setDomains([]);
setMailboxes([]);
setSelectedDomain(null);
setHealthStatus(null);
};
const handleDelete = async () => {
if (!deleteTarget) return;
try {
await mailboxesAPI.remove(deleteTarget);
showToast(`Deleted ${deleteTarget}`, 'success');
setDeleteTarget(null);
await loadMailboxes(selectedDomain, false);
await loadDomains(false);
} catch (err) {
showToast(`Delete failed: ${err.message}`, 'error');
}
};
const handleMailboxCreated = async () => {
setBusyMessage('Refreshing...');
try {
await loadDomains(false);
await loadMailboxes(selectedDomain, true);
} finally {
setBusyMessage('');
}
};
const handleQuotaApplied = async () => {
setBusyMessage('Refreshing quotas...');
try {
await loadMailboxes(selectedDomain, true);
} finally {
setBusyMessage('');
}
};
// Called by HealthModal after a fresh check completes — re-load the
// persisted summary so the banner reflects the new state.
const handleHealthChecked = async () => {
if (selectedDomain) await loadHealthStatus(selectedDomain);
};
if (!bootChecked) {
return
Loading...
;
}
if (!user) {
return (
<>
{toast && setToast(null)} />}
>
);
}
return (
{!hideDomainList && (
Domains on this node
{isSuperAdmin
? 'Domains are discovered dynamically from DMS accounts.'
: 'Your assigned domains.'}
{domains.length === 0 ? (
No domains available.
) : (
{domains.map((d) => {
const active = d.domain === selectedDomain;
return (
);
})}
)}
)}
{/* Health banner (shows only when last check found problems) */}
setShowHealth(true)} />
{selectedDomain || 'Mailboxes'}
Create/delete mailboxes, reset passwords, edit rules. Quotas are refreshed when you open a domain.
{isSuperAdmin && (
)}
{mailboxes.length === 0 ? (
No mailboxes for this domain
{selectedDomain ? 'Click "New mailbox" to create the first one.' : 'Select a domain first.'}
) : (
| Email |
Status |
Usage |
Updated |
Actions |
{mailboxes.map((m) => (
|
{m.email_address}
{m.node_name}
|
{m.status}
|
|
{new Date(m.updated_at).toLocaleString()}
|
|
))}
)}
setSettingsTarget(null)}
onToast={showToast}
/>
setShowNew(false)}
onCreated={handleMailboxCreated}
onToast={showToast}
/>
setPwTarget(null)}
onToast={showToast}
/>
setDeleteTarget(null)}
/>
{isSuperAdmin && (
setShowAudit(false)}
onToast={showToast}
/>
)}
{isSuperAdmin && (
setShowAdmins(false)}
onToast={showToast}
/>
)}
{isSuperAdmin && (
setShowDomainQuota(false)}
onApplied={handleQuotaApplied}
onToast={showToast}
/>
)}
{isSuperAdmin && (
setShowBilling(false)}
onToast={showToast}
/>
)}
setShowHealth(false)}
onCheckedReport={handleHealthChecked}
onToast={showToast}
/>
setShowChangePw(false)}
onToast={showToast}
/>
{busyMessage && }
{toast && setToast(null)} />}
);
}
export default App;