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

@@ -19,13 +19,13 @@ import Avatar from '../components/Avatar.jsx';
// ── Shared sub-components (identical logic to modal versions) ─────────────────
function UserCheckList({ allUsers, selectedIds, onChange }) {
function UserCheckList({ allUsers, selectedIds, onChange, onIF, onIB }) {
const [search, setSearch] = useState('');
const filtered = allUsers.filter(u => (u.display_name||u.name).toLowerCase().includes(search.toLowerCase()));
return (
<div>
<input className="input" placeholder="Search users…" value={search} onChange={e => setSearch(e.target.value)}
style={{ marginBottom:8 }} autoComplete="new-password" />
<input className="input" placeholder="Search users…" value={search} onChange={e = autoComplete="new-password"> setSearch(e.target.value)}
style={{ marginBottom:8 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
<div style={{ maxHeight:220, overflowY:'auto', border:'1px solid var(--border)', borderRadius:'var(--radius)' }}>
{filtered.map(u => (
<label key={u.id} style={{ display:'flex', alignItems:'center', gap:10, padding:'8px 12px', borderBottom:'1px solid var(--border)', cursor:'pointer' }}>
@@ -59,7 +59,7 @@ function GroupCheckList({ allGroups, selectedIds, onChange }) {
}
// ── All Groups tab ────────────────────────────────────────────────────────────
function AllGroupsTab({ allUsers, onRefresh, isMobile = false }) {
function AllGroupsTab({ allUsers, onRefresh, isMobile = false, onIF, onIB }) {
const toast = useToast();
const [groups, setGroups] = useState([]);
const [selected, setSelected] = useState(null);
@@ -139,13 +139,13 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false }) {
<div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth: isMobile ? '100%' : 520 }}>
<div>
<label className="settings-section-label">Group Name</label>
<input className="input" value={editName} onChange={e => setEditName(e.target.value)}
placeholder="e.g. Coaches" style={{ marginTop:6 }} autoComplete="new-password" />
<input className="input" value={editName} onChange={e = autoComplete="new-password"> setEditName(e.target.value)}
placeholder="e.g. Coaches" style={{ marginTop:6 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
{isCreating && <p style={{ fontSize:12, color:'var(--text-tertiary)', marginTop:5 }}>A matching Direct Message group will be created automatically.</p>}
</div>
<div>
<label className="settings-section-label">Members</label>
<div style={{ marginTop:6 }}><UserCheckList allUsers={allUsers} selectedIds={members} onChange={setMembers} /></div>
<div style={{ marginTop:6 }}><UserCheckList allUsers={allUsers} selectedIds={members} onChange={setMembers} onIF={onIF} onIB={onIB} /></div>
<p style={{ fontSize:12, color:'var(--text-tertiary)', marginTop:5 }}>{members.size} selected</p>
</div>
<div style={{ display:'flex', gap:8, alignItems:'center', flexWrap:'wrap' }}>
@@ -173,7 +173,7 @@ function AllGroupsTab({ allUsers, onRefresh, isMobile = false }) {
}
// ── Direct Messages tab ───────────────────────────────────────────────────────
function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey, isMobile = false }) {
function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey, isMobile = false, onIF, onIB }) {
const toast = useToast();
const [dms, setDms] = useState([]);
const [selected, setSelected] = useState(null);
@@ -248,7 +248,7 @@ function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey, isMobile = fa
<div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth: isMobile ? '100%' : 520 }}>
<div>
<label className="settings-section-label">DM Name</label>
<input className="input" value={dmName} onChange={e => setDmName(e.target.value)} placeholder="e.g. Coaches + Players" style={{ marginTop:6 }} autoComplete="new-password" />
<input className="input" value={dmName} onChange={e = autoComplete="new-password"> setDmName(e.target.value)} placeholder="e.g. Coaches + Players" style={{ marginTop:6 }} autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
</div>
<div>
<label className="settings-section-label">Member Groups</label>
@@ -282,7 +282,7 @@ function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey, isMobile = fa
// ── U2U Restrictions tab ──────────────────────────────────────────────────────
function U2URestrictionsTab({ allUserGroups, isMobile = false }) {
function U2URestrictionsTab({ allUserGroups, isMobile = false, onIF, onIB }) {
const toast = useToast();
const [selectedGroup, setSelectedGroup] = useState(null);
const [blockedIds, setBlockedIds] = useState(new Set());
@@ -434,8 +434,8 @@ function U2URestrictionsTab({ allUserGroups, isMobile = false }) {
Allowed Groups <span style={{ fontWeight:400, color:'var(--text-tertiary)' }}>({otherGroups.length - blockedIds.size} of {otherGroups.length} allowed)</span>
</label>
<input className="input" placeholder="Search groups…" value={search}
onChange={e => setSearch(e.target.value)} style={{ marginBottom:8 }}
autoComplete="new-password" />
onChange={e = autoComplete="new-password"> setSearch(e.target.value)} style={{ marginBottom:8 }}
autoComplete="new-password" onFocus={onIF} onBlur={onIB} />
</div>
{loading ? (
@@ -515,7 +515,9 @@ export default function GroupManagerPage({ isMobile = false, onProfile, onHelp,
const [allUsers, setAllUsers] = useState([]);
const [allUserGroups, setAllUserGroups] = useState([]);
const [refreshKey, setRefreshKey] = useState(0);
const keyboardOpen = useKeyboardOpen();
const [inputFocused, setInputFocused] = useState(false);
const onIF = () => setInputFocused(true);
const onIB = () => setInputFocused(false);
const onRefresh = () => setRefreshKey(k => k+1);
useEffect(() => {
@@ -559,7 +561,7 @@ export default function GroupManagerPage({ isMobile = false, onProfile, onHelp,
)}
{/* ── 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 shown on mobile */}
{isMobile && (
@@ -573,14 +575,16 @@ export default function GroupManagerPage({ isMobile = false, onProfile, onHelp,
{/* Content */}
<div style={{ flex:1, display:'flex', overflow: isMobile ? 'auto' : 'hidden', paddingBottom: isMobile ? 70 : 0 }}>
{tab==='all' && <AllGroupsTab allUsers={allUsers} onRefresh={onRefresh} isMobile={isMobile} />}
{tab==='dm' && <DirectMessagesTab allUserGroups={allUserGroups} onRefresh={onRefresh} refreshKey={refreshKey} isMobile={isMobile} />}
{tab==='u2u' && <U2URestrictionsTab allUserGroups={allUserGroups} isMobile={isMobile} />}
{tab==='all' && <AllGroupsTab allUsers={allUsers} onRefresh={onRefresh} isMobile={isMobile} onIF={onIF} onIB={onIB} />}
{tab==='dm' && <DirectMessagesTab allUserGroups={allUserGroups} onRefresh={onRefresh} refreshKey={refreshKey} isMobile={isMobile} onIF={onIF} onIB={onIB} />}
{tab==='u2u' && <U2URestrictionsTab allUserGroups={allUserGroups} isMobile={isMobile} onIF={onIF} onIB={onIB} />}
</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>