new features

This commit is contained in:
2026-04-28 17:18:09 -05:00
parent 830e52a4fa
commit ffe2204597
7 changed files with 291 additions and 94 deletions

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import {
FiRefreshCw, FiList, FiLogOut, FiSettings, FiKey, FiTrash2, FiPlus, FiInbox,
FiUsers, FiUser,
FiUsers, FiUser, FiHardDrive,
} from 'react-icons/fi';
import Login from './components/Login';
@@ -15,6 +15,7 @@ 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 { authAPI, domainsAPI, mailboxesAPI } from './services/api';
@@ -36,16 +37,15 @@ function App() {
const [showAudit, setShowAudit] = useState(false);
const [showAdmins, setShowAdmins] = useState(false);
const [showChangePw, setShowChangePw] = useState(false);
const [showDomainQuota, setShowDomainQuota] = useState(false);
const showToast = useCallback((message, type = 'success') => {
setToast({ message, type });
}, []);
const isSuperAdmin = user?.role === 'super_admin';
// Hide left column entirely if a domain admin only has a single domain.
const hideDomainList = !isSuperAdmin && domains.length <= 1;
// ---- data loading helpers ----
const loadDomains = useCallback(async (resync = false) => {
const list = await domainsAPI.list(resync);
setDomains(list);
@@ -58,7 +58,6 @@ function App() {
setMailboxes(list);
}, []);
// ---- initial boot ----
useEffect(() => {
(async () => {
try {
@@ -77,7 +76,6 @@ function App() {
(async () => {
setBusyMessage('Loading domains...');
try {
// Only super admin should trigger an upstream DMS resync on login.
const list = await loadDomains(isSuperAdmin);
const first = list[0]?.domain || null;
setSelectedDomain(first);
@@ -158,7 +156,15 @@ function App() {
}
};
// ---- render ----
const handleQuotaApplied = async () => {
setBusyMessage('Refreshing quotas...');
try {
await loadMailboxes(selectedDomain, true);
} finally {
setBusyMessage('');
}
};
if (!bootChecked) {
return <div className="min-h-screen flex items-center justify-center text-gray-400">Loading...</div>;
}
@@ -174,7 +180,6 @@ function App() {
return (
<div className="min-h-screen">
{/* Header */}
<header className="bg-white border-b border-gray-200 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-6 py-4 flex items-center justify-between gap-4">
<div className="min-w-0">
@@ -188,12 +193,13 @@ function App() {
<FiUser className="w-4 h-4 mr-2" />
My Password
</button>
<button onClick={() => setShowAudit(true)} className="btn-secondary">
<FiList className="w-4 h-4 mr-2" />
Audit Log
</button>
{/* Audit log: super_admin only. */}
{isSuperAdmin && (
<>
<button onClick={() => setShowAudit(true)} className="btn-secondary">
<FiList className="w-4 h-4 mr-2" />
Audit Log
</button>
<button onClick={() => setShowAdmins(true)} className="btn-secondary">
<FiUsers className="w-4 h-4 mr-2" />
Admins
@@ -212,10 +218,8 @@ function App() {
</div>
</header>
{/* Main */}
<main className="max-w-7xl mx-auto px-6 py-6">
<div className={`grid grid-cols-1 ${hideDomainList ? '' : 'lg:grid-cols-[320px_1fr]'} gap-6 items-start`}>
{/* Domains - hidden when single-domain user */}
{!hideDomainList && (
<section className="card">
<h2 className="text-base font-semibold text-gray-900">Domains on this node</h2>
@@ -259,9 +263,8 @@ function App() {
</section>
)}
{/* Mailboxes */}
<section className="card relative">
<div className="flex items-start justify-between gap-4 mb-5">
<div className="flex items-start justify-between gap-4 mb-5 flex-wrap">
<div>
<h2 className="text-base font-semibold text-gray-900">
{selectedDomain || 'Mailboxes'}
@@ -270,14 +273,28 @@ function App() {
Create/delete mailboxes, reset passwords, edit rules. Quotas are refreshed when you open a domain.
</p>
</div>
<button
onClick={() => setShowNew(true)}
disabled={!selectedDomain}
className="btn-primary"
>
<FiPlus className="w-4 h-4 mr-2" />
New mailbox
</button>
<div className="flex flex-row flex-nowrap gap-2">
{/* Set domain-wide quota: super_admin only. */}
{isSuperAdmin && (
<button
onClick={() => setShowDomainQuota(true)}
disabled={!selectedDomain || mailboxes.length === 0}
className="btn-secondary"
title="Set quota for all mailboxes in this domain"
>
<FiHardDrive className="w-4 h-4 mr-2" />
Set quota
</button>
)}
<button
onClick={() => setShowNew(true)}
disabled={!selectedDomain}
className="btn-primary"
>
<FiPlus className="w-4 h-4 mr-2" />
New mailbox
</button>
</div>
</div>
{mailboxes.length === 0 ? (
@@ -354,7 +371,6 @@ function App() {
</div>
</main>
{/* Modals */}
<MailboxSettingsModal
open={!!settingsTarget}
email={settingsTarget?.email}
@@ -388,11 +404,13 @@ function App() {
onConfirm={handleDelete}
onClose={() => setDeleteTarget(null)}
/>
<AuditLogModal
open={showAudit}
onClose={() => setShowAudit(false)}
onToast={showToast}
/>
{isSuperAdmin && (
<AuditLogModal
open={showAudit}
onClose={() => setShowAudit(false)}
onToast={showToast}
/>
)}
{isSuperAdmin && (
<AdminUsersModal
open={showAdmins}
@@ -401,6 +419,16 @@ function App() {
onToast={showToast}
/>
)}
{isSuperAdmin && (
<DomainQuotaModal
open={showDomainQuota}
domain={selectedDomain}
mailboxes={mailboxes}
onClose={() => setShowDomainQuota(false)}
onApplied={handleQuotaApplied}
onToast={showToast}
/>
)}
<ChangeMyPasswordModal
open={showChangePw}
onClose={() => setShowChangePw(false)}