Merge branch 'group-actions' into 'develop'

Group actions

See merge request soapbox-pub/soapbox!2324
This commit is contained in:
Chewbacca 2023-03-13 16:01:34 +00:00
commit 55bacbbf05
14 changed files with 281 additions and 244 deletions

View file

@ -40,14 +40,6 @@ const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST';
const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS';
const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL';
const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST';
const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS';
const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL';
const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST';
const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS';
const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL';
const GROUP_DELETE_STATUS_REQUEST = 'GROUP_DELETE_STATUS_REQUEST';
const GROUP_DELETE_STATUS_SUCCESS = 'GROUP_DELETE_STATUS_SUCCESS';
const GROUP_DELETE_STATUS_FAIL = 'GROUP_DELETE_STATUS_FAIL';
@ -312,70 +304,6 @@ const fetchGroupRelationshipsFail = (error: AxiosError) => ({
skipNotFound: true,
});
const joinGroup = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const locked = (getState().groups.items.get(id) as any).locked || false;
dispatch(joinGroupRequest(id, locked));
return api(getState).post(`/api/v1/groups/${id}/join`).then(response => {
dispatch(joinGroupSuccess(response.data));
toast.success(locked ? messages.joinRequestSuccess : messages.joinSuccess);
}).catch(error => {
dispatch(joinGroupFail(error, locked));
});
};
const leaveGroup = (id: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(leaveGroupRequest(id));
return api(getState).post(`/api/v1/groups/${id}/leave`).then(response => {
dispatch(leaveGroupSuccess(response.data));
toast.success(messages.leaveSuccess);
}).catch(error => {
dispatch(leaveGroupFail(error));
});
};
const joinGroupRequest = (id: string, locked: boolean) => ({
type: GROUP_JOIN_REQUEST,
id,
locked,
skipLoading: true,
});
const joinGroupSuccess = (relationship: APIEntity) => ({
type: GROUP_JOIN_SUCCESS,
relationship,
skipLoading: true,
});
const joinGroupFail = (error: AxiosError, locked: boolean) => ({
type: GROUP_JOIN_FAIL,
error,
locked,
skipLoading: true,
});
const leaveGroupRequest = (id: string) => ({
type: GROUP_LEAVE_REQUEST,
id,
skipLoading: true,
});
const leaveGroupSuccess = (relationship: APIEntity) => ({
type: GROUP_LEAVE_SUCCESS,
relationship,
skipLoading: true,
});
const leaveGroupFail = (error: AxiosError) => ({
type: GROUP_LEAVE_FAIL,
error,
skipLoading: true,
});
const groupDeleteStatus = (groupId: string, statusId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(groupDeleteStatusRequest(groupId, statusId));
@ -895,12 +823,6 @@ export {
GROUP_RELATIONSHIPS_FETCH_REQUEST,
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
GROUP_RELATIONSHIPS_FETCH_FAIL,
GROUP_JOIN_REQUEST,
GROUP_JOIN_SUCCESS,
GROUP_JOIN_FAIL,
GROUP_LEAVE_REQUEST,
GROUP_LEAVE_SUCCESS,
GROUP_LEAVE_FAIL,
GROUP_DELETE_STATUS_REQUEST,
GROUP_DELETE_STATUS_SUCCESS,
GROUP_DELETE_STATUS_FAIL,
@ -973,14 +895,6 @@ export {
fetchGroupRelationshipsRequest,
fetchGroupRelationshipsSuccess,
fetchGroupRelationshipsFail,
joinGroup,
leaveGroup,
joinGroupRequest,
joinGroupSuccess,
joinGroupFail,
leaveGroupRequest,
leaveGroupSuccess,
leaveGroupFail,
groupDeleteStatus,
groupDeleteStatusRequest,
groupDeleteStatusSuccess,

View file

@ -1,10 +1,10 @@
import React from 'react';
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { joinGroup, leaveGroup } from 'soapbox/actions/groups';
import { openModal } from 'soapbox/actions/modals';
import { Button } from 'soapbox/components/ui';
import { useAppDispatch } from 'soapbox/hooks';
import { useCancelMembershipRequest, useJoinGroup, useLeaveGroup } from 'soapbox/queries/groups';
import { Group } from 'soapbox/types/entities';
interface IGroupActionButton {
@ -21,25 +21,32 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
const dispatch = useAppDispatch();
const intl = useIntl();
const isNonMember = !group.relationship || !group.relationship.member;
const joinGroup = useJoinGroup();
const leaveGroup = useLeaveGroup();
const cancelRequest = useCancelMembershipRequest();
const isRequested = group.relationship?.requested;
const isNonMember = !group.relationship?.member && !isRequested;
const isAdmin = group.relationship?.role === 'admin';
const onJoinGroup = () => dispatch(joinGroup(group.id));
const onJoinGroup = () => joinGroup.mutate(group);
const onLeaveGroup = () =>
dispatch(openModal('CONFIRM', {
heading: intl.formatMessage(messages.confirmationHeading),
message: intl.formatMessage(messages.confirmationMessage),
confirm: intl.formatMessage(messages.confirmationConfirm),
onConfirm: () => dispatch(leaveGroup(group.id)),
onConfirm: () => leaveGroup.mutate(group),
}));
const onCancelRequest = () => cancelRequest.mutate(group);
if (isNonMember) {
return (
<Button
theme='primary'
onClick={onJoinGroup}
disabled={joinGroup.isLoading}
>
{group.locked
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
@ -52,7 +59,8 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
return (
<Button
theme='secondary'
onClick={onLeaveGroup}
onClick={onCancelRequest}
disabled={cancelRequest.isLoading}
>
<FormattedMessage id='group.cancel_request' defaultMessage='Cancel Request' />
</Button>
@ -74,6 +82,7 @@ const GroupActionButton = ({ group }: IGroupActionButton) => {
<Button
theme='secondary'
onClick={onLeaveGroup}
disabled={leaveGroup.isLoading}
>
<FormattedMessage id='group.leave' defaultMessage='Leave Group' />
</Button>

View file

@ -125,7 +125,7 @@ const GroupHeader: React.FC<IGroupHeader> = ({ group }) => {
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
/>
<Stack space={1}>
<Stack space={1} alignItems='center'>
<HStack className='text-gray-700 dark:text-gray-600' space={2} wrap>
<GroupRelationship group={group} />
<GroupPrivacy group={group} />

View file

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Carousel, Stack, Text } from 'soapbox/components/ui';
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
@ -7,46 +8,59 @@ import { usePopularGroups } from 'soapbox/queries/groups';
import Group from './group';
const PopularGroups = () => {
const { groups, isFetching } = usePopularGroups();
const { groups, isFetching, isFetched, isError } = usePopularGroups();
const isEmpty = (isFetched && groups.length === 0) || isError;
const [groupCover, setGroupCover] = useState<HTMLDivElement | null>(null);
return (
<Stack space={4}>
<Text size='xl' weight='bold'>
Popular Groups
<FormattedMessage
id='groups.discover.popular.title'
defaultMessage='Popular Groups'
/>
</Text>
<Carousel
itemWidth={250}
itemCount={groups.length}
controlsHeight={groupCover?.clientHeight}
>
{({ width }: { width: number }) => (
<>
{isFetching ? (
new Array(20).fill(0).map((_, idx) => (
<div
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
style={{ width: width || 'auto' }}
key={idx}
>
<PlaceholderGroupDiscover />
</div>
))
) : (
groups.map((group) => (
<Group
key={group.id}
group={group}
width={width}
ref={setGroupCover}
/>
))
)}
</>
)}
</Carousel>
{isEmpty ? (
<Text theme='muted'>
<FormattedMessage
id='groups.discover.popular.empty'
defaultMessage='Unable to fetch popular groups at this time. Please check back later.'
/>
</Text>
) : (
<Carousel
itemWidth={250}
itemCount={groups.length}
controlsHeight={groupCover?.clientHeight}
>
{({ width }: { width: number }) => (
<>
{isFetching ? (
new Array(20).fill(0).map((_, idx) => (
<div
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
style={{ width: width || 'auto' }}
key={idx}
>
<PlaceholderGroupDiscover />
</div>
))
) : (
groups.map((group) => (
<Group
key={group.id}
group={group}
width={width}
ref={setGroupCover}
/>
))
)}
</>
)}
</Carousel>
)}
</Stack>
);
};

View file

@ -0,0 +1,20 @@
import React from 'react';
import { Stack, Text } from 'soapbox/components/ui';
interface Props {
title: React.ReactNode | string
subtitle: React.ReactNode | string
}
export default ({ title, subtitle }: Props) => (
<Stack space={2} className='px-4 py-2' data-testid='no-results'>
<Text weight='bold' size='lg'>
{title}
</Text>
<Text theme='muted'>
{subtitle}
</Text>
</Stack>
);

View file

@ -1,22 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import { Stack, Text } from 'soapbox/components/ui';
export default () => (
<Stack space={2} className='px-4 py-2' data-testid='no-results'>
<Text weight='bold' size='lg'>
<FormattedMessage
id='groups.discover.search.no_results.title'
defaultMessage='No matches found'
/>
</Text>
<Text theme='muted'>
<FormattedMessage
id='groups.discover.search.no_results.subtitle'
defaultMessage='Try searching for another group.'
/>
</Text>
</Stack>
);

View file

@ -1,4 +1,5 @@
import React, { useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { Stack } from 'soapbox/components/ui';
import PlaceholderGroupSearch from 'soapbox/features/placeholder/components/placeholder-group-search';
@ -6,7 +7,7 @@ import { useDebounce, useOwnAccount } from 'soapbox/hooks';
import { useGroupSearch } from 'soapbox/queries/groups/search';
import { saveGroupSearch } from 'soapbox/utils/groups';
import NoResultsBlankslate from './no-results-blankslate';
import Blankslate from './blankslate';
import RecentSearches from './recent-searches';
import Results from './results';
@ -25,7 +26,7 @@ export default (props: Props) => {
const debouncedValueToSave = debounce(searchValue as string, 1000);
const groupSearchResult = useGroupSearch(debouncedValue);
const { groups, isFetching, isFetched } = groupSearchResult;
const { groups, isFetching, isFetched, isError } = groupSearchResult;
const hasSearchResults = isFetched && groups.length > 0;
const hasNoSearchResults = isFetched && groups.length === 0;
@ -46,8 +47,42 @@ export default (props: Props) => {
);
}
if (isError) {
return (
<Blankslate
title={
<FormattedMessage
id='groups.discover.search.error.title'
defaultMessage='An error occurred'
/>
}
subtitle={
<FormattedMessage
id='groups.discover.search.error.subtitle'
defaultMessage='Please try again later.'
/>
}
/>
);
}
if (hasNoSearchResults) {
return <NoResultsBlankslate />;
return (
<Blankslate
title={
<FormattedMessage
id='groups.discover.search.no_results.title'
defaultMessage='No matches found'
/>
}
subtitle={
<FormattedMessage
id='groups.discover.search.no_results.subtitle'
defaultMessage='Try searching for another group.'
/>
}
/>
);
}
if (hasSearchResults) {

View file

@ -1,4 +1,5 @@
import React, { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import { Carousel, Stack, Text } from 'soapbox/components/ui';
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
@ -7,46 +8,59 @@ import { useSuggestedGroups } from 'soapbox/queries/groups';
import Group from './group';
const SuggestedGroups = () => {
const { groups, isFetching } = useSuggestedGroups();
const { groups, isFetching, isFetched, isError } = useSuggestedGroups();
const isEmpty = (isFetched && groups.length === 0) || isError;
const [groupCover, setGroupCover] = useState<HTMLDivElement | null>(null);
return (
<Stack space={4}>
<Text size='xl' weight='bold'>
Suggested For You
<FormattedMessage
id='groups.discover.suggested.title'
defaultMessage='Suggested For You'
/>
</Text>
<Carousel
itemWidth={250}
itemCount={groups.length}
controlsHeight={groupCover?.clientHeight}
>
{({ width }: { width: number }) => (
<>
{isFetching ? (
new Array(20).fill(0).map((_, idx) => (
<div
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
style={{ width: width || 'auto' }}
key={idx}
>
<PlaceholderGroupDiscover />
</div>
))
) : (
groups.map((group) => (
<Group
key={group.id}
group={group}
width={width}
ref={setGroupCover}
/>
))
)}
</>
)}
</Carousel>
{isEmpty ? (
<Text theme='muted'>
<FormattedMessage
id='groups.discover.suggested.empty'
defaultMessage='Unable to fetch suggested groups at this time. Please check back later.'
/>
</Text>
) : (
<Carousel
itemWidth={250}
itemCount={groups.length}
controlsHeight={groupCover?.clientHeight}
>
{({ width }: { width: number }) => (
<>
{isFetching ? (
new Array(20).fill(0).map((_, idx) => (
<div
className='relative flex shrink-0 flex-col space-y-2 px-0.5'
style={{ width: width || 'auto' }}
key={idx}
>
<PlaceholderGroupDiscover />
</div>
))
) : (
groups.map((group) => (
<Group
key={group.id}
group={group}
width={width}
ref={setGroupCover}
/>
))
)}
</>
)}
</Carousel>
)}
</Stack>
);
};

View file

@ -2,7 +2,7 @@ import { Map as ImmutableMap } from 'immutable';
import { __stub } from 'soapbox/api';
import { renderHook, waitFor } from 'soapbox/jest/test-helpers';
import { normalizeAccount, normalizeGroup, normalizeInstance } from 'soapbox/normalizers';
import { normalizeAccount, normalizeGroup, normalizeGroupRelationship, normalizeInstance } from 'soapbox/normalizers';
import { useGroupsPath } from '../useGroupsPath';
@ -58,23 +58,16 @@ describe('useGroupsPath()', () => {
id: '1',
}),
]);
mock.onGet('/api/v1/groups/relationships?id[]=1').reply(200, [
normalizeGroupRelationship({
id: '1',
}),
]);
});
});
test('should default to the discovery page', async () => {
const store = {
entities: {
Groups: {
store: {
'1': normalizeGroup({}),
},
lists: {
'': new Set(['1']),
},
},
},
};
test('should default to the "My Groups" page', async () => {
const { result } = renderHook(useGroupsPath, undefined, store);
await waitFor(() => {

View file

@ -789,7 +789,7 @@
"group.join.private": "Request Access",
"group.join.public": "Join Group",
"group.join.request_success": "Requested to join the group",
"group.join.success": "Joined the group",
"group.join.success": "Group joined successfully!",
"group.leave": "Leave Group",
"group.leave.success": "Left the group",
"group.manage": "Manage Group",
@ -801,6 +801,10 @@
"group.tabs.all": "All",
"group.tabs.members": "Members",
"group.user_subheading": "Users",
"groups.discover.popular.empty": "Unable to fetch popular groups at this time. Please check back later.",
"groups.discover.popular.title": "Popular Groups",
"groups.discover.search.error.subtitle": "Please try again later.",
"groups.discover.search.error.title": "An error occurred",
"groups.discover.search.no_results.subtitle": "Try searching for another group.",
"groups.discover.search.no_results.title": "No matches found",
"groups.discover.search.placeholder": "Search",
@ -810,6 +814,8 @@
"groups.discover.search.recent_searches.title": "Recent searches",
"groups.discover.search.results.groups": "Groups",
"groups.discover.search.results.member_count": "{members, plural, one {member} other {members}}",
"groups.discover.suggested.empty": "Unable to fetch suggested groups at this time. Please check back later.",
"groups.discover.suggested.title": "Suggested For You",
"groups.empty.subtitle": "Start discovering groups to join or create your own.",
"groups.empty.title": "No Groups yet",
"hashtag.column_header.tag_mode.all": "and {additional}",

View file

@ -10,7 +10,9 @@ import {
export const GroupRelationshipRecord = ImmutableRecord({
id: '',
blocked_by: false,
member: false,
notifying: null,
requested: false,
role: null as 'admin' | 'moderator' | 'user' | null,
});

View file

@ -37,7 +37,7 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
const { group } = useGroup(id);
const isNonMember = !group?.relationship || !group.relationship.member;
const isNonMember = !group?.relationship?.member;
const isPrivate = group?.locked;
// if ((group as any) === false) {
@ -80,7 +80,6 @@ const GroupPage: React.FC<IGroupPage> = ({ params, children }) => {
Content is only visible to group members
</Text>
</Stack>
) : children}
</Column>

View file

@ -1,13 +1,21 @@
import { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { useInfiniteQuery, useMutation, useQuery } from '@tanstack/react-query';
import { defineMessages, useIntl } from 'react-intl';
import { fetchGroupRelationships } from 'soapbox/actions/groups';
import { importFetchedGroups } from 'soapbox/actions/importer';
import { getNextLink } from 'soapbox/api';
import { useApi, useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { normalizeGroup } from 'soapbox/normalizers';
import { Group } from 'soapbox/types/entities';
import { useApi, useFeatures, useOwnAccount } from 'soapbox/hooks';
import { normalizeGroup, normalizeGroupRelationship } from 'soapbox/normalizers';
import toast from 'soapbox/toast';
import { Group, GroupRelationship } from 'soapbox/types/entities';
import { flattenPages, PaginatedResult } from 'soapbox/utils/queries';
import { queryClient } from './client';
const messages = defineMessages({
joinSuccess: { id: 'group.join.success', defaultMessage: 'Group joined successfully!' },
joinRequestSuccess: { id: 'group.join.request_success', defaultMessage: 'Requested to join the group' },
leaveSuccess: { id: 'group.leave.success', defaultMessage: 'Left the group' },
});
const GroupKeys = {
group: (id: string) => ['groups', 'group', id] as const,
myGroups: (userId: string) => ['groups', userId] as const,
@ -15,29 +23,54 @@ const GroupKeys = {
suggestedGroups: ['groups', 'suggested'] as const,
};
const useGroups = () => {
const useGroupsApi = () => {
const api = useApi();
const getGroupRelationships = async (ids: string[]) => {
const queryString = ids.map((id) => `id[]=${id}`).join('&');
const { data } = await api.get<GroupRelationship[]>(`/api/v1/groups/relationships?${queryString}`);
return data;
};
const fetchGroups = async (endpoint: string) => {
const response = await api.get<Group[]>(endpoint);
const groups = [response.data].flat();
const relationships = await getGroupRelationships(groups.map((group) => group.id));
const result = groups.map((group) => {
const relationship = relationships.find((relationship) => relationship.id === group.id);
return normalizeGroup({
...group,
relationship: relationship ? normalizeGroupRelationship(relationship) : null,
});
});
return {
response,
groups: result,
};
};
return { fetchGroups };
};
const useGroups = () => {
const account = useOwnAccount();
const dispatch = useAppDispatch();
const features = useFeatures();
const { fetchGroups } = useGroupsApi();
const getGroups = async (pageParam?: any): Promise<PaginatedResult<Group>> => {
const endpoint = '/api/v1/groups';
const nextPageLink = pageParam?.link;
const uri = nextPageLink || endpoint;
const response = await api.get<Group[]>(uri);
const { data } = response;
const { response, groups } = await fetchGroups(uri);
const link = getNextLink(response);
const hasMore = !!link;
const result = data.map(normalizeGroup);
// Note: Temporary while part of Groups is using Redux
dispatch(importFetchedGroups(result));
dispatch(fetchGroupRelationships(result.map((item) => item.id)));
return {
result,
result: groups,
hasMore,
link,
};
@ -67,14 +100,13 @@ const useGroups = () => {
};
const usePopularGroups = () => {
const api = useApi();
const features = useFeatures();
const { fetchGroups } = useGroupsApi();
const getQuery = async () => {
const { data } = await api.get<Group[]>('/api/v1/groups/search?q=group'); // '/api/v1/truth/trends/groups'
const result = data.map(normalizeGroup);
const { groups } = await fetchGroups('/api/v1/truth/trends/groups');
return result;
return groups;
};
const queryInfo = useQuery<Group[]>(GroupKeys.popularGroups, getQuery, {
@ -89,14 +121,13 @@ const usePopularGroups = () => {
};
const useSuggestedGroups = () => {
const api = useApi();
const features = useFeatures();
const { fetchGroups } = useGroupsApi();
const getQuery = async () => {
const { data } = await api.get<Group[]>('/api/mock/groups'); // /api/v1/truth/suggestions/groups
const result = data.map(normalizeGroup);
const { groups } = await fetchGroups('/api/v1/truth/suggestions/groups');
return result;
return groups;
};
const queryInfo = useQuery<Group[]>(GroupKeys.suggestedGroups, getQuery, {
@ -111,12 +142,12 @@ const useSuggestedGroups = () => {
};
const useGroup = (id: string) => {
const api = useApi();
const features = useFeatures();
const { fetchGroups } = useGroupsApi();
const getGroup = async () => {
const { data } = await api.get(`/api/v1/groups/${id}`);
return normalizeGroup(data);
const { groups } = await fetchGroups(`/api/v1/groups/${id}`);
return groups[0];
};
const queryInfo = useQuery(GroupKeys.group(id), getGroup, {
@ -129,4 +160,43 @@ const useGroup = (id: string) => {
};
};
export { useGroups, useGroup, usePopularGroups, useSuggestedGroups };
const useJoinGroup = () => {
const api = useApi();
const intl = useIntl();
return useMutation((group: Group) => api.post<GroupRelationship>(`/api/v1/groups/${group.id}/join`), {
onSuccess(_response, group) {
queryClient.invalidateQueries(['groups']);
toast.success(
group.locked
? intl.formatMessage(messages.joinRequestSuccess)
: intl.formatMessage(messages.joinSuccess),
);
},
});
};
const useLeaveGroup = () => {
const api = useApi();
const intl = useIntl();
return useMutation((group: Group) => api.post<GroupRelationship>(`/api/v1/groups/${group.id}/leave`), {
onSuccess() {
queryClient.invalidateQueries({ queryKey: ['groups'] });
toast.success(intl.formatMessage(messages.leaveSuccess));
},
});
};
const useCancelMembershipRequest = () => {
const api = useApi();
const me = useOwnAccount();
return useMutation((group: Group) => api.post(`/api/v1/groups/${group.id}/membership_requests/${me?.id}/reject`), {
onSuccess() {
queryClient.invalidateQueries({ queryKey: ['groups'] });
},
});
};
export { useGroups, useGroup, usePopularGroups, useSuggestedGroups, useJoinGroup, useLeaveGroup, useCancelMembershipRequest };

View file

@ -5,12 +5,6 @@ import {
GROUP_UPDATE_SUCCESS,
GROUP_DELETE_SUCCESS,
GROUP_RELATIONSHIPS_FETCH_SUCCESS,
GROUP_JOIN_REQUEST,
GROUP_JOIN_SUCCESS,
GROUP_JOIN_FAIL,
GROUP_LEAVE_REQUEST,
GROUP_LEAVE_SUCCESS,
GROUP_LEAVE_FAIL,
} from 'soapbox/actions/groups';
import { normalizeGroupRelationship } from 'soapbox/normalizers';
@ -37,17 +31,6 @@ export default function groupRelationships(state: State = ImmutableMap(), action
return state.set(action.group.id, normalizeGroupRelationship({ id: action.group.id, member: true, requested: false, role: 'admin' }));
case GROUP_DELETE_SUCCESS:
return state.delete(action.id);
case GROUP_JOIN_REQUEST:
return state.getIn([action.id, 'member']) ? state : state.setIn([action.id, action.locked ? 'requested' : 'member'], true);
case GROUP_JOIN_FAIL:
return state.setIn([action.id, action.locked ? 'requested' : 'member'], false);
case GROUP_LEAVE_REQUEST:
return state.setIn([action.id, 'member'], false);
case GROUP_LEAVE_FAIL:
return state.setIn([action.id, 'member'], true);
case GROUP_JOIN_SUCCESS:
case GROUP_LEAVE_SUCCESS:
return normalizeRelationships(state, [action.relationship]);
case GROUP_RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships);
default: