v0.11.1 various UI bug fixes

This commit is contained in:
2026-03-20 23:01:26 -04:00
parent 50e7adf246
commit 0a048271c6
21 changed files with 91 additions and 93 deletions

View File

@@ -108,7 +108,7 @@ function UserRow({ u, onUpdated }) {
<input className="input" style={{ flex:1, fontSize:13, padding:'5px 8px' }}
value={nameVal} onChange={e => setNameVal(e.target.value)}
onKeyDown={e => { if(e.key==='Enter') handleSaveName(); if(e.key==='Escape'){setEditName(false);setNameVal(u.name);} }}
autoComplete="new-password" />
autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
<button className="btn btn-primary btn-sm" onClick={handleSaveName}>Save</button>
<button className="btn btn-secondary btn-sm" onClick={() => { setEditName(false); setNameVal(u.name); }}></button>
</div>
@@ -134,7 +134,7 @@ function UserRow({ u, onUpdated }) {
type="text" placeholder="New password (min 6)" value={resetPw}
onChange={e => setResetPw(e.target.value)}
onKeyDown={e => { if(e.key==='Enter') handleResetPw(); if(e.key==='Escape'){setShowReset(false);setResetPw('');} }}
autoComplete="new-password" />
autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
<button className="btn btn-primary btn-sm" onClick={handleResetPw}>Set</button>
<button className="btn btn-secondary btn-sm" onClick={() => { setShowReset(false); setResetPw(''); }}></button>
</div>
@@ -159,7 +159,7 @@ function UserRow({ u, onUpdated }) {
);
}
function CreateUserForm({ userPass, onCreated, isMobile }) {
function CreateUserForm({ userPass, onCreated, isMobile, onIF, onIB }) {
const toast = useToast();
const [form, setForm] = useState({ name:'', email:'', password:'', role:'member' });
const [saving, setSaving] = useState(false);
@@ -178,15 +178,15 @@ function CreateUserForm({ userPass, onCreated, isMobile }) {
<div style={{ display:'grid', gridTemplateColumns: isMobile ? '1fr' : '1fr 1fr', gap:12, marginBottom:12 }}>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color:'var(--text-secondary)' }}>Full Name <span style={{ fontWeight:400, color:'var(--text-tertiary)' }}>(First Last)</span></label>
<input className="input" placeholder="Jane Smith" autoComplete="new-password" autoCorrect="off" autoCapitalize="words" value={form.name} onChange={e => set('name')(e.target.value)} />
<input className="input" placeholder="Jane Smith" autoComplete="new-password" autoCorrect="off" autoCapitalize="words" value={form.name} onChange={e => set('name')(e.target.value)} onFocus={onIF} onBlur={onIB} />
</div>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color:'var(--text-secondary)' }}>Email</label>
<input className="input" type="email" placeholder="jane@example.com" autoComplete="new-password" value={form.email} onChange={e => set('email')(e.target.value)} />
<input className="input" type="email" placeholder="jane@example.com" autoComplete="new-password" value={form.email} onChange={e => set('email')(e.target.value)} onFocus={onIF} onBlur={onIB} />
</div>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color:'var(--text-secondary)' }}>Temp Password <span style={{ fontWeight:400, color:'var(--text-tertiary)' }}>(blank = {userPass})</span></label>
<input className="input" type="text" autoComplete="new-password" value={form.password} onChange={e => set('password')(e.target.value)} />
<input className="input" type="text" autoComplete="new-password" value={form.password} onChange={e => set('password')(e.target.value)} onFocus={onIF} onBlur={onIB} />
</div>
<div className="flex-col gap-1">
<label className="text-sm font-medium" style={{ color:'var(--text-secondary)' }}>Role</label>
@@ -272,19 +272,6 @@ function BulkImportForm({ userPass, onCreated }) {
);
}
// ── useKeyboardOpen — true when software keyboard is visible ─────────────────
function useKeyboardOpen() {
const [open, setOpen] = useState(false);
useEffect(() => {
const vv = window.visualViewport;
if (!vv) return;
const handler = () => setOpen(vv.height < window.innerHeight * 0.75);
vv.addEventListener('resize', handler);
return () => vv.removeEventListener('resize', handler);
}, []);
return open;
}
// ── Main page ─────────────────────────────────────────────────────────────────
export default function UserManagerPage({ isMobile = false, onProfile, onHelp, onAbout }) {
const [users, setUsers] = useState([]);
@@ -293,7 +280,9 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
const [search, setSearch] = useState('');
const [tab, setTab] = useState('users');
const [userPass, setUserPass] = useState('user@1234');
const keyboardOpen = useKeyboardOpen();
const [inputFocused, setInputFocused] = useState(false);
const onIF = () => setInputFocused(true);
const onIB = () => setInputFocused(false);
const load = useCallback(async () => {
setLoadError(''); setLoading(true);
@@ -349,7 +338,7 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
)}
{/* ── Right panel ── */}
<div style={{ flex:1, display:'flex', flexDirection:'column', overflow:'hidden', minWidth:0, background:'var(--background)', height:'100%' }}>
<div style={{ flex:1, display:'flex', flexDirection:'column', overflow:'hidden', minWidth:0, background:'var(--background)' }}>
{/* Mobile tab bar — only on mobile since desktop uses left panel */}
{isMobile && (
@@ -365,6 +354,7 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
{tab === 'users' && (
<>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)}
onFocus={onIF} onBlur={onIB}
autoComplete="new-password" autoCorrect="off" spellCheck={false}
style={{ marginBottom:16, width:'100%', maxWidth: isMobile ? '100%' : 400 }} />
<div style={{ background:'var(--surface)', borderRadius:'var(--radius)', boxShadow:'var(--shadow-sm)', overflow:'hidden' }}>
@@ -385,13 +375,15 @@ export default function UserManagerPage({ isMobile = false, onProfile, onHelp, o
</div>
</>
)}
{tab === 'create' && <CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} isMobile={isMobile} />}
{tab === 'create' && <CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} isMobile={isMobile} onIF={onIF} onIB={onIB} />}
{tab === 'bulk' && <BulkImportForm userPass={userPass} onCreated={load} />}
</div>
{/* Mobile footer — in-flow so it stays at bottom and never floats with keyboard */}
{isMobile && (
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} />
{/* Mobile footer — fixed, hidden when any input is focused (keyboard open) */}
{isMobile && !inputFocused && (
<div style={{ position:'fixed', bottom:0, left:0, right:0, zIndex:20, background:'var(--surface)', borderTop:'1px solid var(--border)' }}>
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} />
</div>
)}
</div>
</div>