| // components/GroupManagementModal.tsx | |
| "use client"; | |
| import { useState, useEffect } from "react"; | |
| import api from "@/lib/api"; | |
| import { Group, GroupMember } from "@/types"; | |
| interface GroupManagementModalProps { | |
| isOpen: boolean; | |
| onClose: () => void; | |
| group: Group; | |
| currentUserId: number; | |
| onUpdate: () => void; | |
| } | |
| export function GroupManagementModal({ isOpen, onClose, group, currentUserId, onUpdate }: GroupManagementModalProps) { | |
| const [name, setName] = useState(group.name); | |
| const [members, setMembers] = useState<GroupMember[]>([]); | |
| const [search, setSearch] = useState(""); | |
| const [searchResults, setSearchResults] = useState<any[]>([]); | |
| const [loading, setLoading] = useState(false); | |
| useEffect(() => { | |
| if (isOpen) fetchMembers(); | |
| }, [isOpen, group]); | |
| const fetchMembers = async () => { | |
| try { | |
| const res = await api.get(`/groups/${group.id}/members`); | |
| setMembers(res.data); | |
| } catch (err) { | |
| console.error(err); | |
| } | |
| }; | |
| const searchUsers = async () => { | |
| if (!search.trim()) return; | |
| try { | |
| const res = await api.get(`/users/search?q=${encodeURIComponent(search)}`); | |
| const data = Array.isArray(res.data) ? res.data : res.data?.users || res.data?.result || []; | |
| setSearchResults(data); | |
| } catch (err) { console.error(err); } | |
| }; | |
| const addMember = async (userId: number) => { | |
| await api.post(`/groups/${group.id}/members`, { userId }); | |
| fetchMembers(); | |
| setSearch(""); setSearchResults([]); | |
| }; | |
| const removeMember = async (userId: number) => { | |
| await api.delete(`/groups/${group.id}/members/${userId}`); | |
| fetchMembers(); | |
| }; | |
| const makeAdmin = async (userId: number) => { | |
| await api.post(`/groups/${group.id}/admins/${userId}`); | |
| fetchMembers(); | |
| }; | |
| const updateName = async () => { | |
| await api.put(`/groups/${group.id}`, { name }); | |
| onUpdate(); | |
| }; | |
| const isAdmin = members.some(m => m.userId === currentUserId && m.isAdmin); | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50"> | |
| <div className="bg-white dark:bg-gray-800 rounded-2xl w-full max-w-md p-6 shadow-xl max-h-[90vh] overflow-y-auto"> | |
| <div className="flex justify-between items-center mb-4"> | |
| <h2 className="text-xl font-bold">Manage Group: {group.name}</h2> | |
| <button onClick={onClose} className="text-gray-500 hover:text-gray-700">✖</button> | |
| </div> | |
| <div className="space-y-4"> | |
| {isAdmin && ( | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" value={name} onChange={e => setName(e.target.value)} | |
| className="flex-1 px-3 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-primary" | |
| placeholder="Group name" | |
| /> | |
| <button onClick={updateName} className="btn btn-primary btn-sm">Rename</button> | |
| </div> | |
| )} | |
| <div> | |
| <h4 className="font-semibold mb-2">Members</h4> | |
| <div className="space-y-2 max-h-60 overflow-y-auto"> | |
| {members.map(m => ( | |
| <div key={m.userId} className="flex justify-between items-center p-2 border-b"> | |
| <span>{m.username} {m.isAdmin && "(Admin)"}</span> | |
| {isAdmin && m.userId !== currentUserId && ( | |
| <div className="flex gap-1"> | |
| {!m.isAdmin && ( | |
| <button onClick={() => makeAdmin(m.userId)} className="btn btn-secondary btn-sm">Make Admin</button> | |
| )} | |
| <button onClick={() => removeMember(m.userId)} className="btn btn-danger btn-sm">Remove</button> | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| {isAdmin && ( | |
| <div> | |
| <h4 className="font-semibold mb-2">Add Member</h4> | |
| <div className="flex gap-2"> | |
| <input | |
| type="text" value={search} onChange={e => setSearch(e.target.value)} | |
| className="flex-1 px-3 py-2 border rounded-lg" | |
| placeholder="Search users..." | |
| /> | |
| <button onClick={searchUsers} className="btn btn-secondary btn-sm">Search</button> | |
| </div> | |
| <div className="mt-2 space-y-1"> | |
| {searchResults.map(u => ( | |
| <div key={u.id} className="flex justify-between items-center p-2 border-b"> | |
| <span>{u.username}</span> | |
| <button onClick={() => addMember(u.id)} className="btn btn-primary btn-sm">Add</button> | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| } |
Xet Storage Details
- Size:
- 4.83 kB
- Xet hash:
- e2355b9736cd41c7a0fc786fdfb3dbfcd07595b815755ee2ac794a7bef7d98bc
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.