small fixes
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { FiPlus, FiTrash2, FiEdit2, FiUser, FiShield, FiArrowLeft } from 'react-icons/fi';
|
import { FiPlus, FiTrash2, FiEdit2, FiUser, FiShield, FiArrowLeft } from 'react-icons/fi';
|
||||||
import Modal from './Modal';
|
import Modal from './Modal';
|
||||||
import LoadingOverlay from './LoadingOverlay';
|
import LoadingOverlay from './LoadingOverlay';
|
||||||
@@ -219,11 +219,47 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa
|
|||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [busy, setBusy] = useState(false);
|
const [busy, setBusy] = useState(false);
|
||||||
|
|
||||||
|
// Tracks whether the user has manually typed in the email field.
|
||||||
|
// Once they have, we stop auto-filling to avoid clobbering their input.
|
||||||
|
const [emailTouched, setEmailTouched] = useState(isEdit);
|
||||||
|
const emailRef = useRef(null);
|
||||||
|
|
||||||
const sortedDomainNames = useMemo(
|
const sortedDomainNames = useMemo(
|
||||||
() => [...domains].map((d) => d.domain).sort(),
|
() => [...domains].map((d) => d.domain).sort(),
|
||||||
[domains]
|
[domains]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Auto-fill email with "@<domain>" when creating a NEW domain_admin
|
||||||
|
// and exactly one domain is selected, as long as the user hasn't
|
||||||
|
// manually edited the email field yet.
|
||||||
|
useEffect(() => {
|
||||||
|
if (isEdit) return;
|
||||||
|
if (role !== 'domain_admin') return;
|
||||||
|
if (emailTouched) return;
|
||||||
|
if (allowedDomains.size === 1) {
|
||||||
|
const [onlyDomain] = [...allowedDomains];
|
||||||
|
setEmail(`@${onlyDomain}`);
|
||||||
|
// Place caret before the @ so the user can type the local part directly.
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
const el = emailRef.current;
|
||||||
|
if (el && document.activeElement === el) {
|
||||||
|
try { el.setSelectionRange(0, 0); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else if (allowedDomains.size === 0) {
|
||||||
|
setEmail('');
|
||||||
|
}
|
||||||
|
}, [allowedDomains, role, isEdit, emailTouched]);
|
||||||
|
|
||||||
|
// When the user focuses the email field for the first time after
|
||||||
|
// an auto-fill, place the caret at position 0 so they can just type.
|
||||||
|
const handleEmailFocus = () => {
|
||||||
|
const el = emailRef.current;
|
||||||
|
if (el && !emailTouched && email.startsWith('@')) {
|
||||||
|
try { el.setSelectionRange(0, 0); } catch { /* ignore */ }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const toggleDomain = (d) => {
|
const toggleDomain = (d) => {
|
||||||
const next = new Set(allowedDomains);
|
const next = new Set(allowedDomains);
|
||||||
if (next.has(d)) next.delete(d); else next.add(d);
|
if (next.has(d)) next.delete(d); else next.add(d);
|
||||||
@@ -297,9 +333,11 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa
|
|||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-semibold text-gray-700 mb-2">Email</label>
|
<label className="block text-sm font-semibold text-gray-700 mb-2">Email</label>
|
||||||
<input
|
<input
|
||||||
|
ref={emailRef}
|
||||||
type="email"
|
type="email"
|
||||||
value={email}
|
value={email}
|
||||||
onChange={(e) => setEmail(e.target.value)}
|
onChange={(e) => { setEmail(e.target.value); setEmailTouched(true); }}
|
||||||
|
onFocus={handleEmailFocus}
|
||||||
className="input-field"
|
className="input-field"
|
||||||
disabled={isEdit}
|
disabled={isEdit}
|
||||||
placeholder="admin@example.com"
|
placeholder="admin@example.com"
|
||||||
@@ -395,6 +433,11 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{!isEdit && allowedDomains.size === 1 && !emailTouched && (
|
||||||
|
<p className="mt-2 text-xs text-gray-500">
|
||||||
|
Email pre-filled with the selected domain. You can still edit it.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ const Modal = ({ open, onClose, title, subtitle, children, size = 'md' }) => {
|
|||||||
lg: 'max-w-4xl',
|
lg: 'max-w-4xl',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Note: clicking the backdrop does NOT close the modal anymore.
|
||||||
|
// Use the X button or the Escape key instead. This avoids accidental
|
||||||
|
// dismissal when the user is in the middle of editing settings.
|
||||||
return (
|
return (
|
||||||
<div
|
<div className="fixed inset-0 z-30 bg-gray-900/40 backdrop-blur-sm flex items-start justify-center p-4 sm:p-8 overflow-y-auto">
|
||||||
className="fixed inset-0 z-30 bg-gray-900/40 backdrop-blur-sm flex items-start justify-center p-4 sm:p-8 overflow-y-auto"
|
|
||||||
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
||||||
>
|
|
||||||
<div className={`bg-white rounded-xl shadow-xl border border-gray-200 w-full ${widths[size]} my-8`}>
|
<div className={`bg-white rounded-xl shadow-xl border border-gray-200 w-full ${widths[size]} my-8`}>
|
||||||
<div className="flex items-start justify-between px-6 py-4 border-b border-gray-100">
|
<div className="flex items-start justify-between px-6 py-4 border-b border-gray-100">
|
||||||
<div>
|
<div>
|
||||||
@@ -36,6 +36,7 @@ const Modal = ({ open, onClose, title, subtitle, children, size = 'md' }) => {
|
|||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
className="text-gray-400 hover:text-gray-700 p-1 rounded-lg hover:bg-gray-100 transition-colors"
|
className="text-gray-400 hover:text-gray-700 p-1 rounded-lg hover:bg-gray-100 transition-colors"
|
||||||
aria-label="Close"
|
aria-label="Close"
|
||||||
|
title="Close (Esc)"
|
||||||
>
|
>
|
||||||
<FiX className="w-5 h-5" />
|
<FiX className="w-5 h-5" />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user