import { useState, useEffect } from 'react'; import { api } from '../utils/api.js'; import { useToast } from '../contexts/ToastContext.jsx'; import Avatar from './Avatar.jsx'; // ── Shared back header ──────────────────────────────────────────────────────── function Header({ title, onBack, right }) { return (
{onBack && ( )} {title} {right}
); } // ── Members screen ──────────────────────────────────────────────────────────── function MembersScreen({ group, allUsers, onBack }) { const toast = useToast(); const [members, setMembers] = useState([]); const [loading, setLoading] = useState(true); const loadMembers = async () => { try { const r = await api.getUserGroup(group.id); setMembers(r.members || []); } catch(e) { toast(e.message, 'error'); } finally { setLoading(false); } }; useEffect(() => { loadMembers(); }, [group.id]); const [search, setSearch] = useState(''); const memberIds = new Set(members.map(m => m.id)); const filteredUsers = search.trim() ? allUsers.filter(u => (u.display_name||u.name).toLowerCase().includes(search.toLowerCase())) : allUsers; const toggle = async (user) => { const nowMember = memberIds.has(user.id); // Optimistic update if(nowMember) setMembers(prev => prev.filter(m => m.id !== user.id)); else setMembers(prev => [...prev, user]); try { const newIds = nowMember ? members.filter(m => m.id !== user.id).map(m => m.id) : [...members.map(m => m.id), user.id]; await api.updateUserGroupMembers(group.id, newIds); } catch(e) { toast(e.message, 'error'); loadMembers(); // revert on error } }; return (
{members.length} member{members.length!==1?'s':''}} /> {loading ? (
Loading…
) : (
setSearch(e.target.value)} autoComplete="new-password" placeholder="Search users…" autoComplete="new-password" autoCorrect="off" autoCapitalize="off" spellCheck={false} style={{width:'100%',padding:'8px 10px 8px 32px',border:'1px solid var(--border)',borderRadius:'var(--radius)',background:'var(--background)',color:'var(--text-primary)',fontSize:14,boxSizing:'border-box'}} />
{search ? `${filteredUsers.length} result${filteredUsers.length!==1?'s':''}` : 'All Users'}
{filteredUsers.map(u => { const isMember = memberIds.has(u.id); return (
{u.display_name||u.name}
{u.role}
); })}
)}
); } // ── Multi-Group DM screen ───────────────────────────────────────────────────── function MultiGroupDmsScreen({ userGroups, onBack }) { const toast = useToast(); const [dms, setDms] = useState([]); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const [selectedGroups, setSelectedGroups] = useState(new Set()); const [saving, setSaving] = useState(false); const load = async () => { try { const r = await api.getMultiGroupDms(); setDms(r.dms || []); } catch(e) { toast(e.message,'error'); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const create = async () => { if(!newName.trim() || selectedGroups.size < 2) return toast('Name and at least 2 groups required','error'); setSaving(true); try { await api.createMultiGroupDm({ name: newName.trim(), userGroupIds: [...selectedGroups] }); setNewName(''); setSelectedGroups(new Set()); setCreating(false); load(); } catch(e) { toast(e.message,'error'); } finally { setSaving(false); } }; const deleteDm = async (dm) => { if(!confirm(`Delete "${dm.name}"?`)) return; try { await api.deleteMultiGroupDm(dm.id); load(); } catch(e) { toast(e.message,'error'); } }; const toggleGrp = id => setSelectedGroups(prev => { const n=new Set(prev); n.has(id)?n.delete(id):n.add(id); return n; }); return (
setCreating(v=>!v)} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--primary)',fontSize:24,lineHeight:1,padding:0 }}>+} /> {creating && (
setNewName(e.target.value)} autoComplete="new-password" placeholder="DM name…" autoComplete="new-password" autoCorrect="off" autoCapitalize="off" spellCheck={false} style={{ width:'100%',padding:'9px 12px',border:'1px solid var(--border)',borderRadius:'var(--radius)',background:'var(--background)',color:'var(--text-primary)',fontSize:15,marginBottom:10,boxSizing:'border-box' }}/>
Select groups (min 2):
{userGroups.map(g=>( ))}
)}
{loading &&
Loading…
} {!loading && dms.length===0 &&
No multi-group DMs yet. Tap + to create one.
} {dms.map(dm=>(
MG
{dm.name}
{dm.group_count} group{dm.group_count!==1?'s':''}
))}
); } // ── Group list screen ───────────────────────────────────────────────────────── export default function MobileGroupManager({ onClose }) { const toast = useToast(); const [groups, setGroups] = useState([]); const [allUsers, setAllUsers] = useState([]); const [loading, setLoading] = useState(true); const [creating, setCreating] = useState(false); const [newName, setNewName] = useState(''); const [saving, setSaving] = useState(false); const [screen, setScreen] = useState('list'); // list | members | mgdms const [activeGroup, setActiveGroup] = useState(null); const [tab, setTab] = useState('groups'); // groups | mgdms const load = async () => { try { const [ug, us] = await Promise.all([api.getUserGroups(), api.getUsers()]); setGroups(ug.groups || []); setAllUsers(us.users || []); } catch(e) { toast(e.message,'error'); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); if(screen === 'members' && activeGroup) return {setScreen('list');load();}}/>; if(screen === 'mgdms') return setScreen('list')}/>; const createGroup = async () => { if(!newName.trim()) return; setSaving(true); try { await api.createUserGroup({ name: newName.trim() }); setNewName(''); setCreating(false); load(); } catch(e) { toast(e.message,'error'); } finally { setSaving(false); } }; const deleteGroup = async (g, e) => { e.stopPropagation(); if(!confirm(`Delete "${g.name}"?`)) return; try { await api.deleteUserGroup(g.id); load(); } catch(e2) { toast(e2.message,'error'); } }; return (
setCreating(v=>!v)} style={{ background:'none',border:'none',cursor:'pointer',color:'var(--primary)',fontSize:24,lineHeight:1,padding:0 }}>+} /> {/* Tab bar */}
{[['groups','All Groups'],['mgdms','Multi-Group DMs']].map(([key,label])=>( ))}
{tab === 'mgdms' && setTab('groups')}/>} {tab === 'groups' && ( <> {creating && (
setNewName(e.target.value)} autoComplete="new-password" onKeyDown={e=>e.key==='Enter'&&createGroup()} placeholder="Group name…" autoComplete="new-password" autoCorrect="off" autoCapitalize="off" spellCheck={false} style={{ flex:1,padding:'8px 12px',border:'1px solid var(--border)',borderRadius:'var(--radius)',background:'var(--background)',color:'var(--text-primary)',fontSize:15 }}/>
)}
{loading &&
Loading…
} {!loading && groups.length===0 &&
No groups yet. Tap + to create one.
} {groups.map(g=>(
{setActiveGroup(g);setScreen('members');}}>
{g.name.substring(0,2).toUpperCase()}
{g.name}
{g.member_count||0} member{g.member_count!==1?'s':''}
))}
)}
); }