new features
This commit is contained in:
@@ -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)}
|
||||
|
||||
Reference in New Issue
Block a user