import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet, Record as ImmutableRecord } from 'immutable'; import { GROUP_DELETE_SUCCESS, GROUP_MEMBERSHIPS_FETCH_REQUEST, GROUP_MEMBERSHIPS_FETCH_FAIL, GROUP_MEMBERSHIPS_FETCH_SUCCESS, GROUP_MEMBERSHIPS_EXPAND_REQUEST, GROUP_MEMBERSHIPS_EXPAND_FAIL, GROUP_MEMBERSHIPS_EXPAND_SUCCESS, GROUP_PROMOTE_SUCCESS, GROUP_DEMOTE_SUCCESS, GROUP_KICK_SUCCESS, GROUP_BLOCK_SUCCESS, } from 'soapbox/actions/groups'; import type { AnyAction } from 'redux'; import type { APIEntity } from 'soapbox/types/entities'; const ListRecord = ImmutableRecord({ next: null as string | null, isLoading: false, items: ImmutableOrderedSet(), }); const ReducerRecord = ImmutableRecord({ admin: ImmutableMap({}), moderator: ImmutableMap({}), user: ImmutableMap({}), }); export type GroupRole = 'admin' | 'moderator' | 'user'; export type List = ReturnType; type State = ReturnType; const normalizeList = (state: State, path: string[], memberships: APIEntity[], next: string | null) => { return state.setIn(path, ListRecord({ next, items: ImmutableOrderedSet(memberships.map(item => item.account.id)), isLoading: false, })); }; const appendToList = (state: State, path: string[], memberships: APIEntity[], next: string | null) => { return state.updateIn(path, map => { return (map as List).set('next', next).set('isLoading', false).update('items', list => list.concat(memberships.map(item => item.account.id))); }); }; const updateLists = (state: State, groupId: string, memberships: APIEntity[]) => { const updateList = (state: State, role: string, membership: APIEntity) => { if (role === membership.role) { return state.updateIn([role, groupId], map => (map as List).update('items', set => set.add(membership.account.id))); } else { return state.updateIn([role, groupId], map => (map as List).update('items', set => set.delete(membership.account.id))); } }; memberships.forEach(membership => { state = updateList(state, 'admin', membership); state = updateList(state, 'moderator', membership); state = updateList(state, 'user', membership); }); return state; }; const removeFromList = (state: State, path: string[], accountId: string) => { return state.updateIn(path, map => { return (map as List).update('items', set => set.delete(accountId)); }); }; export default function groupMemberships(state: State = ReducerRecord(), action: AnyAction) { switch (action.type) { case GROUP_DELETE_SUCCESS: return state.deleteIn(['admin', action.id]).deleteIn(['moderator', action.id]).deleteIn(['user', action.id]); case GROUP_MEMBERSHIPS_FETCH_REQUEST: case GROUP_MEMBERSHIPS_EXPAND_REQUEST: return state.updateIn([action.role, action.id], map => (map as List || ListRecord()).set('isLoading', true)); case GROUP_MEMBERSHIPS_FETCH_FAIL: case GROUP_MEMBERSHIPS_EXPAND_FAIL: return state.updateIn([action.role, action.id], map => (map as List || ListRecord()).set('isLoading', false)); case GROUP_MEMBERSHIPS_FETCH_SUCCESS: return normalizeList(state, [action.role, action.id], action.memberships, action.next); case GROUP_MEMBERSHIPS_EXPAND_SUCCESS: return appendToList(state, [action.role, action.id], action.memberships, action.next); case GROUP_PROMOTE_SUCCESS: case GROUP_DEMOTE_SUCCESS: return updateLists(state, action.groupId, action.memberships); case GROUP_KICK_SUCCESS: case GROUP_BLOCK_SUCCESS: state = removeFromList(state, ['admin', action.groupId], action.accountId); state = removeFromList(state, ['moderator', action.groupId], action.accountId); state = removeFromList(state, ['user', action.groupId], action.accountId); return state; default: return state; } }