v0.10.7 fixed UI settings

This commit is contained in:
2026-03-20 21:34:53 -04:00
parent 66fd4c5377
commit b224237cf7
7 changed files with 79 additions and 45 deletions

View File

@@ -2,6 +2,7 @@ import { useState, useEffect, useRef } from 'react';
import { useToast } from '../contexts/ToastContext.jsx';
import { api } from '../utils/api.js';
import Avatar from '../components/Avatar.jsx';
import UserFooter from '../components/UserFooter.jsx';
function isValidEmail(e) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(e); }
@@ -161,7 +162,7 @@ function UserRow({ u, onUpdated }) {
}
// ── Create user form ──────────────────────────────────────────────────────────
function CreateUserForm({ userPass, onCreated }) {
function CreateUserForm({ userPass, onCreated, isMobile = false }) {
const toast = useToast();
const [form, setForm] = useState({ name:'', email:'', password:'', role:'member' });
const [saving, setSaving] = useState(false);
@@ -182,8 +183,8 @@ function CreateUserForm({ userPass, onCreated }) {
};
return (
<div style={{ maxWidth:560 }}>
<div style={{ display:'grid', gridTemplateColumns:'1fr 1fr', gap:12, marginBottom:12 }}>
<div style={{ maxWidth: isMobile ? '100%' : 560 }}>
<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)} />
@@ -292,8 +293,7 @@ function BulkImportForm({ userPass, onCreated }) {
}
// ── Main page ─────────────────────────────────────────────────────────────────
export default function UserManagerPage() {
const isMobile = window.innerWidth < 768;
export default function UserManagerPage({ isMobile = false, onProfile, onHelp, onAbout }) {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState('');
@@ -321,31 +321,32 @@ export default function UserManagerPage() {
u.email?.toLowerCase().includes(search.toLowerCase())
);
const pad = isMobile ? 16 : 24;
return (
<div style={{ flex:1, display:'flex', flexDirection:'column', overflow:'hidden', background:'var(--background)' }}>
{/* Page header */}
<div style={{ background:'var(--surface)', borderBottom:'1px solid var(--border)', padding:'0 24px', flexShrink:0 }}>
<div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', height:52 }}>
<div style={{ background:'var(--surface)', borderBottom:'1px solid var(--border)', padding:`0 ${pad}px`, flexShrink:0 }}>
<div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', height:52, gap:8 }}>
<div style={{ display:'flex', alignItems:'center', gap:10 }}>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--primary)" strokeWidth="2"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<span style={{ fontWeight:700, fontSize:15 }}>User Manager</span>
{!loading && <span style={{ fontSize:12, color:'var(--text-tertiary)' }}>{users.length} user{users.length!==1?'s':''}</span>}
{!loading && <span style={{ fontSize:12, color:'var(--text-tertiary)', flexShrink:0 }}>{users.length} user{users.length!==1?'s':''}</span>}
</div>
<div style={{ display:'flex', gap:8 }}>
<button className={`btn btn-sm ${tab==='users'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('users')}>All Users</button>
<button className={`btn btn-sm ${tab==='users'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('users')}>Users</button>
<button className={`btn btn-sm ${tab==='create'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('create')}>+ Create</button>
{!isMobile && <button className={`btn btn-sm ${tab==='bulk'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('bulk')}>Bulk Import</button>}
{!isMobile && <button className={`btn btn-sm ${tab==='bulk'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('bulk')}>Bulk</button>}
</div>
</div>
</div>
{/* Content */}
<div style={{ flex:1, overflowY:'auto', padding:24 }}>
<div style={{ flex:1, overflowY:'auto', padding:pad }}>
{tab === 'users' && (
<>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)}
autoComplete="new-password" autoCorrect="off" spellCheck={false}
style={{ marginBottom:16, maxWidth:400 }} />
style={{ marginBottom:16, width:'100%', maxWidth: isMobile ? '100%' : 400 }} />
<div style={{ background:'var(--surface)', borderRadius:'var(--radius)', boxShadow:'var(--shadow-sm)', overflow:'hidden' }}>
{loading ? (
<div style={{ padding:48, textAlign:'center' }}><div className="spinner" /></div>
@@ -364,9 +365,14 @@ export default function UserManagerPage() {
</div>
</>
)}
{tab === 'create' && <CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} />}
{tab === 'create' && <CreateUserForm userPass={userPass} onCreated={() => { load(); setTab('users'); }} isMobile={isMobile} />}
{tab === 'bulk' && <BulkImportForm userPass={userPass} onCreated={load} />}
</div>
{/* User footer */}
<div className="sidebar-footer">
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} />
</div>
</div>
);
}