diff --git a/frontend/src/components/AdminUsersModal.jsx b/frontend/src/components/AdminUsersModal.jsx index 21319db..024945c 100644 --- a/frontend/src/components/AdminUsersModal.jsx +++ b/frontend/src/components/AdminUsersModal.jsx @@ -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 Modal from './Modal'; import LoadingOverlay from './LoadingOverlay'; @@ -219,11 +219,47 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa const [error, setError] = useState(''); 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( () => [...domains].map((d) => d.domain).sort(), [domains] ); + // Auto-fill email with "@" 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 next = new Set(allowedDomains); if (next.has(d)) next.delete(d); else next.add(d); @@ -297,9 +333,11 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa
setEmail(e.target.value)} + onChange={(e) => { setEmail(e.target.value); setEmailTouched(true); }} + onFocus={handleEmailFocus} className="input-field" disabled={isEdit} placeholder="admin@example.com" @@ -395,6 +433,11 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa ))}
)} + {!isEdit && allowedDomains.size === 1 && !emailTouched && ( +

+ Email pre-filled with the selected domain. You can still edit it. +

+ )} )} @@ -436,4 +479,4 @@ const AdminForm = ({ target, domains, currentUserEmail, onCancel, onSaved, onToa ); }; -export default AdminUsersModal; +export default AdminUsersModal; \ No newline at end of file diff --git a/frontend/src/components/Modal.jsx b/frontend/src/components/Modal.jsx index 910f22e..262d63f 100644 --- a/frontend/src/components/Modal.jsx +++ b/frontend/src/components/Modal.jsx @@ -21,11 +21,11 @@ const Modal = ({ open, onClose, title, subtitle, children, size = 'md' }) => { 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 ( -
{ if (e.target === e.currentTarget) onClose(); }} - > +
@@ -36,6 +36,7 @@ const Modal = ({ open, onClose, title, subtitle, children, size = 'md' }) => { onClick={onClose} className="text-gray-400 hover:text-gray-700 p-1 rounded-lg hover:bg-gray-100 transition-colors" aria-label="Close" + title="Close (Esc)" > @@ -46,4 +47,4 @@ const Modal = ({ open, onClose, title, subtitle, children, size = 'md' }) => { ); }; -export default Modal; +export default Modal; \ No newline at end of file