import { useState, useEffect, useRef } from "react"; import { Link } from "react-router-dom"; import { Trash, CheckLg, PlusLg, PersonPlusFill } from "react-bootstrap-icons"; import { authenticatedEndpoint, ProfilePicture } from "../../util"; import { useAuth } from "../../useAuth"; import { Button, Modal } from "react-bootstrap"; import { AsyncTypeahead } from "react-bootstrap-typeahead"; interface Member { uuid: string; permissions: string[]; display_name: string; picture_url?: string; } export default function Members({ members, possiblePermissions, saveMemberPermissions, deleteMember, }: { members: Member[]; possiblePermissions?: string[]; saveMemberPermissions: ( prospectiveMember: boolean, uuid: string, selectedPermissions: string[] ) => Promise; deleteMember: (uuid: string) => Promise; }) { const [prospectiveMembers, setProspectiveMembers] = useState([]); useEffect(() => { setProspectiveMembers( prospectiveMembers.filter((prospectiveMember) => { for (const member of members) { if (member.uuid === prospectiveMember.uuid) { return false; } } return true; }) ); }, [members]); return ( {members.map((member, index) => ( ))} {prospectiveMembers.map((member, index) => ( ))} {possiblePermissions ? ( setProspectiveMembers([ ...prospectiveMembers, { uuid: userUuid, display_name: displayName, picture_url: pictureUrl, permissions: ["VISIBLE"], }, ]) } existingMembers={members} /> ) : ( <> )}
); } function MemberListItem({ member, prospectiveMember, possiblePermissions, saveMemberPermissions, deleteMember, }: { member: Member; prospectiveMember: boolean; possiblePermissions?: string[]; saveMemberPermissions: ( prospectiveMember: boolean, uuid: string, selectedPermissions: string[] ) => Promise; deleteMember: (uuid: string) => Promise; }) { const [selectedPermissions, setSelectedPermissions] = useState( member.permissions || [] ); const auth = useAuth(); const [deleting, setDeleting] = useState(false); const [saving, setSaving] = useState(false); const [error, setError] = useState(undefined); let itemAction = <>; const doSaveMemberPermissions = async () => { setSaving(true); try { await saveMemberPermissions( prospectiveMember, member.uuid, selectedPermissions ); } catch (e) { setError(error); } finally { setSaving(false); } }; const doDelete = async () => { setSaving(true); try { await deleteMember(member.uuid); } catch (e) { setError(error); } finally { setSaving(false); } }; if (!possiblePermissions) { // the current user can't perform any actions } else if (saving) { itemAction = ( ); } else if ( !prospectiveMember && selectedPermissions.indexOf("VISIBLE") === -1 ) { itemAction = ( ); } else if ( prospectiveMember || selectedPermissions.sort().join(",") != member.permissions.sort().join(",") ) { itemAction = ( ); } return ( <> setDeleting(false)} onConfirm={() => doDelete()} username={member.display_name} /> setError(undefined)} /> {member.display_name} {auth?.getUserUuid() === member.uuid ? ( <>
(that's you!) ) : ( <> )} {possiblePermissions && member.permissions ? ( <> {itemAction} ) : ( <> )} ); } interface MemberListInserterProps { existingMembers: Member[]; onInsert: ( username: string, user_uuid: string, picture_url: string | null ) => any; } interface SearchOption { user_uuid: string; display_name: string; picture_url: string | null; } function MemberListInserter({ onInsert, existingMembers, }: MemberListInserterProps) { const auth = useAuth(); const searchRef = useRef(null); const [loading, setLoading] = useState(false); const [options, setOptions] = useState([]); const [error, setError] = useState(""); if (!auth) { return <>; } const handleSearch = async (query: string) => { setLoading(true); setError(""); try { let res = await fetch( authenticatedEndpoint( auth, `users/search?q=${encodeURIComponent(query)}` ) ); let json = await res.json(); if (json.error) { throw new Error(json.error); } setOptions(json.users || []); } catch (e: any) { setError(e.message); } finally { setLoading(false); } }; const handleChange = (selected: SearchOption[]) => { onInsert( selected[0].display_name, selected[0].picture_url, selected[0].user_uuid ); searchRef.current?.clear(); }; return (
{ for (const existing of existingMembers) { if (option.user_uuid === existing.uuid) { return false; } } return true; }} labelKey="display_name" options={options} isLoading={loading} placeholder="Search for User" onChange={handleChange} ref={searchRef} renderMenuItemChildren={(option: SearchOption) => ( <> {option.display_name} )} />
{error}
); } function RenderPermissions({ possiblePermissions, selectedPermissions, userUuid, onChange, }: { possiblePermissions: string[]; selectedPermissions: string[]; userUuid: string; onChange: (permissions: string[]) => any; }) { return (
{possiblePermissions.map((permission) => (
-1} onChange={(e) => { let newUserPermissions = new Set(selectedPermissions); if (e.target.checked) { newUserPermissions.add(permission); } else { newUserPermissions.delete(permission); } onChange(Array.from(newUserPermissions)); }} />
))}
); } function DeleteModal(props: { show: boolean; onCancel: () => void; onConfirm: () => void; username: string; }) { return ( Are you sure you wish to remove this member from the crate?

Are you sure you wish to remove {props.username} from the crate?

); } function ErrorModal(props: { error?: string; onClose: () => void }) { return ( Error

{props.error}

); }