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

@@ -1,4 +1,5 @@
import { useState, useEffect, useCallback } from 'react';
import UserFooter from '../components/UserFooter.jsx';
import { api } from '../utils/api.js';
import { useToast } from '../contexts/ToastContext.jsx';
import Avatar from '../components/Avatar.jsx';
@@ -45,7 +46,7 @@ function GroupCheckList({ allGroups, selectedIds, onChange }) {
}
// ── All Groups tab ────────────────────────────────────────────────────────────
function AllGroupsTab({ allUsers, onRefresh }) {
function AllGroupsTab({ allUsers, onRefresh, isMobile = false }) {
const toast = useToast();
const [groups, setGroups] = useState([]);
const [selected, setSelected] = useState(null);
@@ -100,9 +101,9 @@ function AllGroupsTab({ allUsers, onRefresh }) {
const isCreating = !selected;
return (
<div style={{ display:'flex', gap:0, height:'100%', minHeight:0 }}>
<div style={{ display:'flex', flexDirection: isMobile ? 'column' : 'row', gap:0, height:'100%', minHeight:0, overflow: isMobile ? 'auto' : 'hidden' }}>
{/* Sidebar list */}
<div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', overflowY:'auto', padding:'12px 8px' }}>
<div style={{ width: isMobile ? '100%' : 220, flexShrink:0, borderRight: isMobile ? 'none' : '1px solid var(--border)', borderBottom: isMobile ? '1px solid var(--border)' : 'none', overflowY: isMobile ? 'visible' : 'auto', padding:'12px 8px' }}>
<div style={{ fontSize:11, fontWeight:700, letterSpacing:'0.8px', textTransform:'uppercase', color:'var(--text-tertiary)', marginBottom:8, paddingLeft:4 }}>User Groups</div>
<button onClick={clearSelection} style={{ display:'block', width:'100%', textAlign:'left', padding:'8px 10px', borderRadius:'var(--radius)', border:'none',
background:isCreating?'var(--primary-light)':'transparent', color:isCreating?'var(--primary)':'var(--text-secondary)',
@@ -121,8 +122,8 @@ function AllGroupsTab({ allUsers, onRefresh }) {
</div>
{/* Form */}
<div style={{ flex:1, overflowY:'auto', padding:'16px 24px' }}>
<div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth:520 }}>
<div style={{ flex:1, overflowY: isMobile ? 'visible' : 'auto', padding: isMobile ? '16px 12px' : '16px 24px' }}>
<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)}
@@ -159,7 +160,7 @@ function AllGroupsTab({ allUsers, onRefresh }) {
}
// ── Direct Messages tab ───────────────────────────────────────────────────────
function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) {
function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey, isMobile = false }) {
const toast = useToast();
const [dms, setDms] = useState([]);
const [selected, setSelected] = useState(null);
@@ -212,8 +213,8 @@ function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) {
const isCreating = !selected;
return (
<div style={{ display:'flex', gap:0, height:'100%', minHeight:0 }}>
<div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', overflowY:'auto', padding:'12px 8px' }}>
<div style={{ display:'flex', flexDirection: isMobile ? 'column' : 'row', gap:0, height:'100%', minHeight:0, overflow: isMobile ? 'auto' : 'hidden' }}>
<div style={{ width: isMobile ? '100%' : 220, flexShrink:0, borderRight: isMobile ? 'none' : '1px solid var(--border)', borderBottom: isMobile ? '1px solid var(--border)' : 'none', overflowY: isMobile ? 'visible' : 'auto', padding:'12px 8px' }}>
<div style={{ fontSize:11, fontWeight:700, letterSpacing:'0.8px', textTransform:'uppercase', color:'var(--text-tertiary)', marginBottom:8, paddingLeft:4 }}>Multi-Group DMs</div>
<button onClick={clearSelection} style={{ display:'block', width:'100%', textAlign:'left', padding:'8px 10px', borderRadius:'var(--radius)', border:'none',
background:isCreating?'var(--primary-light)':'transparent', color:isCreating?'var(--primary)':'var(--text-secondary)',
@@ -230,8 +231,8 @@ function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) {
))}
{dms.length===0 && <div style={{ fontSize:13, color:'var(--text-tertiary)', padding:'8px 4px' }}>No multi-group DMs yet</div>}
</div>
<div style={{ flex:1, overflowY:'auto', padding:'16px 24px' }}>
<div style={{ display:'flex', flexDirection:'column', gap:18, maxWidth:520 }}>
<div style={{ flex:1, overflowY: isMobile ? 'visible' : 'auto', padding: isMobile ? '16px 12px' : '16px 24px' }}>
<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" />
@@ -268,7 +269,7 @@ function DirectMessagesTab({ allUserGroups, onRefresh, refreshKey }) {
// ── U2U Restrictions tab ──────────────────────────────────────────────────────
function U2URestrictionsTab({ allUserGroups }) {
function U2URestrictionsTab({ allUserGroups, isMobile = false }) {
const toast = useToast();
const [selectedGroup, setSelectedGroup] = useState(null);
const [blockedIds, setBlockedIds] = useState(new Set());
@@ -276,6 +277,22 @@ function U2URestrictionsTab({ allUserGroups }) {
const [loading, setLoading] = useState(false);
const [saving, setSaving] = useState(false);
const [search, setSearch] = useState('');
// Map of groupId → number of restrictions (for showing dots in sidebar)
const [restrictionCounts, setRestrictionCounts] = useState({});
// Load restriction counts for all groups on mount and after saves
const loadAllCounts = useCallback(async () => {
const counts = {};
for (const g of allUserGroups) {
try {
const { blockedGroupIds } = await api.getGroupRestrictions(g.id);
counts[g.id] = blockedGroupIds.length;
} catch { counts[g.id] = 0; }
}
setRestrictionCounts(counts);
}, [allUserGroups]);
useEffect(() => { if (allUserGroups.length > 0) loadAllCounts(); }, [allUserGroups]);
const loadRestrictions = async (group) => {
setLoading(true);
@@ -314,6 +331,7 @@ function U2URestrictionsTab({ allUserGroups }) {
await api.setGroupRestrictions(selectedGroup.id, [...blockedIds]);
setSavedBlockedIds(new Set(blockedIds));
toast('Restrictions saved', 'success');
loadAllCounts();
} catch (e) { toast(e.message, 'error'); }
finally { setSaving(false); }
};
@@ -328,14 +346,14 @@ function U2URestrictionsTab({ allUserGroups }) {
: otherGroups;
return (
<div style={{ display:'flex', gap:0, height:'100%', minHeight:0 }}>
<div style={{ display:'flex', flexDirection: isMobile ? 'column' : 'row', gap:0, height:'100%', minHeight:0, overflow: isMobile ? 'auto' : 'hidden' }}>
{/* Group selector sidebar */}
<div style={{ width:220, flexShrink:0, borderRight:'1px solid var(--border)', overflowY:'auto', padding:'12px 8px' }}>
<div style={{ width: isMobile ? '100%' : 220, flexShrink:0, borderRight: isMobile ? 'none' : '1px solid var(--border)', borderBottom: isMobile ? '1px solid var(--border)' : 'none', overflowY: isMobile ? 'visible' : 'auto', padding:'12px 8px' }}>
<div style={{ fontSize:11, fontWeight:700, letterSpacing:'0.8px', textTransform:'uppercase', color:'var(--text-tertiary)', marginBottom:8, paddingLeft:4 }}>
Select Group
</div>
{allUserGroups.map(g => {
const hasRestrictions = g.id === selectedGroup?.id ? blockedIds.size > 0 : false;
const hasRestrictions = g.id === selectedGroup?.id ? blockedIds.size > 0 : (restrictionCounts[g.id] || 0) > 0;
return (
<button key={g.id} onClick={() => selectGroup(g)} style={{
display:'block', width:'100%', textAlign:'left', padding:'8px 10px',
@@ -351,7 +369,7 @@ function U2URestrictionsTab({ allUserGroups }) {
<div style={{ fontSize:11, color:'var(--text-tertiary)' }}>{g.member_count} member{g.member_count!==1?'s':''}</div>
</div>
{hasRestrictions && (
<span style={{ width:8, height:8, borderRadius:'50%', background:'var(--warning)', flexShrink:0 }} title="Has restrictions" />
<span style={{ width:8, height:8, borderRadius:'50%', background:'var(--error)', flexShrink:0 }} title="Has U2U restrictions" />
)}
</div>
</button>
@@ -363,7 +381,7 @@ function U2URestrictionsTab({ allUserGroups }) {
</div>
{/* Restriction editor */}
<div style={{ flex:1, overflowY:'auto', padding:'16px 24px' }}>
<div style={{ flex:1, overflowY: isMobile ? 'visible' : 'auto', padding: isMobile ? '16px 12px' : '16px 24px' }}>
{!selectedGroup ? (
<div style={{ display:'flex', flexDirection:'column', alignItems:'center', justifyContent:'center',
height:'100%', color:'var(--text-tertiary)', gap:12, textAlign:'center' }}>
@@ -376,7 +394,7 @@ function U2URestrictionsTab({ allUserGroups }) {
</div>
</div>
) : (
<div style={{ maxWidth:540 }}>
<div style={{ maxWidth: isMobile ? '100%' : 540 }}>
{/* Header */}
<div style={{ marginBottom:20 }}>
<h3 style={{ fontSize:16, fontWeight:700, margin:'0 0 6px' }}>{selectedGroup.name}</h3>
@@ -477,7 +495,7 @@ function U2URestrictionsTab({ allUserGroups }) {
}
// ── Main page ─────────────────────────────────────────────────────────────────
export default function GroupManagerPage() {
export default function GroupManagerPage({ isMobile = false, onProfile, onHelp, onAbout }) {
const [tab, setTab] = useState('all');
const [allUsers, setAllUsers] = useState([]);
const [allUserGroups, setAllUserGroups] = useState([]);
@@ -499,18 +517,22 @@ export default function GroupManagerPage() {
<span style={{ fontWeight:700, fontSize:15 }}>Group Manager</span>
</div>
<div style={{ display:'flex', gap:8 }}>
<button className={`btn btn-sm ${tab==='all'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('all')}>User Groups</button>
<button className={`btn btn-sm ${tab==='dm'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('dm')}>Multi-Group DMs</button>
<button className={`btn btn-sm ${tab==='u2u'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('u2u')}>U2U Restrictions</button>
<button className={`btn btn-sm ${tab==='all'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('all')}>{isMobile ? 'Groups' : 'User Groups'}</button>
<button className={`btn btn-sm ${tab==='dm'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('dm')}>{isMobile ? 'Multi-DMs' : 'Multi-Group DMs'}</button>
<button className={`btn btn-sm ${tab==='u2u'?'btn-primary':'btn-secondary'}`} onClick={() => setTab('u2u')}>U2U</button>
</div>
</div>
</div>
{/* Content */}
<div style={{ flex:1, display:'flex', overflow:'hidden' }}>
{tab==='all' && <AllGroupsTab allUsers={allUsers} onRefresh={onRefresh} />}
{tab==='dm' && <DirectMessagesTab allUserGroups={allUserGroups} onRefresh={onRefresh} refreshKey={refreshKey} />}
{tab==='u2u' && <U2URestrictionsTab allUserGroups={allUserGroups} />}
<div style={{ flex:1, display:'flex', overflow: isMobile ? 'auto' : 'hidden' }}>
{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} />}
</div>
{/* User footer */}
<div className="sidebar-footer">
<UserFooter onProfile={onProfile} onHelp={onHelp} onAbout={onAbout} />
</div>
</div>
);