import { ReactNode, createContext, useCallback, useContext, useMemo, useState, } from 'react' import { User } from '../../../../../types/group-management/user' import { deleteJSON, FetchError, postJSON } from '@/infrastructure/fetch-json' import { mapSeries } from '@/infrastructure/promise' import getMeta from '@/utils/meta' import { APIError } from '../components/error-alert' import useUserSelection from '../hooks/use-user-selection' import { parseEmails } from '../utils/emails' import { debugConsole } from '@/utils/debugging' export type GroupMembersContextValue = { users: User[] selectedUsers: User[] selectUser: (user: User) => void selectAllUsers: () => void unselectAllUsers: () => void selectAllNonManagedUsers: () => void unselectUser: (user: User) => void addMembers: (emailString: string) => void removeMembers: (e: any) => void removeMember: (user: User) => Promise removeMemberLoading: boolean removeMemberError?: APIError updateMemberView: (userId: string, updatedUser: User) => void inviteMemberLoading: boolean inviteError?: APIError paths: { [key: string]: string } } export const GroupMembersContext = createContext< GroupMembersContextValue | undefined >(undefined) type GroupMembersProviderProps = { children: ReactNode } export function GroupMembersProvider({ children }: GroupMembersProviderProps) { const { users, setUsers, selectedUsers, selectAllUsers, unselectAllUsers, selectAllNonManagedUsers, selectUser, unselectUser, } = useUserSelection(getMeta('ol-users') || []) const [inviteUserInflightCount, setInviteUserInflightCount] = useState(0) const [inviteError, setInviteError] = useState() const [removeMemberInflightCount, setRemoveMemberInflightCount] = useState(0) const [removeMemberError, setRemoveMemberError] = useState() const groupId = getMeta('ol-groupId') const paths = useMemo( () => ({ addMember: `/manage/groups/${groupId}/invites`, removeMember: `/manage/groups/${groupId}/user`, removeInvite: `/manage/groups/${groupId}/invites`, exportMembers: `/manage/groups/${groupId}/members/export`, }), [groupId] ) const addMembers = useCallback( emailString => { setInviteError(undefined) const emails = parseEmails(emailString) mapSeries(emails, async email => { setInviteUserInflightCount(count => count + 1) try { const data = await postJSON<{ user: User }>(paths.addMember, { body: { email, }, }) if (data.user) { const alreadyListed = users.find( user => user.email === data.user.email ) if (!alreadyListed) { setUsers(users => [...users, data.user]) } } } catch (error: unknown) { debugConsole.error(error) setInviteError((error as FetchError)?.data?.error || {}) } setInviteUserInflightCount(count => count - 1) }) }, [paths.addMember, users, setUsers] ) const removeMember = useCallback( async user => { let url if (paths.removeInvite && user.invite && user._id == null) { url = `${paths.removeInvite}/${encodeURIComponent(user.email)}` } else if (paths.removeMember && user._id) { url = `${paths.removeMember}/${user._id}` } else { return } setRemoveMemberInflightCount(count => count + 1) try { await deleteJSON(url, {}) setUsers(users => users.filter(u => u !== user)) unselectUser(user) } catch (error: unknown) { debugConsole.error(error) setRemoveMemberError((error as FetchError)?.data?.error || {}) } setRemoveMemberInflightCount(count => count - 1) }, [unselectUser, setUsers, paths.removeInvite, paths.removeMember] ) const removeMembers = useCallback( e => { e.preventDefault() setRemoveMemberError(undefined) ;(async () => { for (const user of selectedUsers) { if (user?.enrollment?.managedBy) { continue } await removeMember(user) } })() }, [selectedUsers, removeMember] ) const updateMemberView = useCallback( (userId, updatedUser) => { setUsers( users.map(u => { if (u._id === userId) { return updatedUser } else { return u } }) ) }, [setUsers, users] ) const value = useMemo( () => ({ users, selectedUsers, selectAllUsers, unselectAllUsers, selectAllNonManagedUsers, selectUser, unselectUser, updateMemberView, addMembers, removeMembers, removeMember, removeMemberLoading: removeMemberInflightCount > 0, removeMemberError, inviteMemberLoading: inviteUserInflightCount > 0, inviteError, paths, }), [ users, selectedUsers, selectAllUsers, unselectAllUsers, selectAllNonManagedUsers, selectUser, unselectUser, updateMemberView, addMembers, removeMembers, removeMember, removeMemberInflightCount, removeMemberError, inviteUserInflightCount, inviteError, paths, ] ) return ( {children} ) } export function useGroupMembersContext() { const context = useContext(GroupMembersContext) if (!context) { throw new Error( 'GroupMembersContext is only available inside GroupMembersProvider' ) } return context }