Add support for pending Group Requests
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
import React from 'react';
|
||||
import { VirtuosoMockContext } from 'react-virtuoso';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||
import { normalizeAccount, normalizeGroup, normalizeGroupRelationship, normalizeInstance } from 'soapbox/normalizers';
|
||||
|
||||
import PendingGroupsRow from '../pending-groups-row';
|
||||
|
||||
const userId = '1';
|
||||
let store: any = {
|
||||
me: userId,
|
||||
accounts: ImmutableMap({
|
||||
[userId]: normalizeAccount({
|
||||
id: userId,
|
||||
acct: 'justin-username',
|
||||
display_name: 'Justin L',
|
||||
avatar: 'test.jpg',
|
||||
chats_onboarded: false,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
const renderApp = (store: any) => (
|
||||
render(
|
||||
<VirtuosoMockContext.Provider value={{ viewportHeight: 300, itemHeight: 100 }}>
|
||||
<PendingGroupsRow />
|
||||
</VirtuosoMockContext.Provider>,
|
||||
undefined,
|
||||
store,
|
||||
)
|
||||
);
|
||||
|
||||
describe('<PendingGroupRows />', () => {
|
||||
describe('without the feature', () => {
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
...store,
|
||||
instance: normalizeInstance({
|
||||
version: '2.7.2 (compatible; Pleroma 2.3.0)',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
it('should not render', () => {
|
||||
renderApp(store);
|
||||
expect(screen.queryAllByTestId('pending-groups-row')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with the feature', () => {
|
||||
beforeEach(() => {
|
||||
store = {
|
||||
...store,
|
||||
instance: normalizeInstance({
|
||||
version: '3.4.1 (compatible; TruthSocial 1.0.0)',
|
||||
software: 'TRUTHSOCIAL',
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
describe('without pending group requests', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/groups?pending=true').reply(200, []);
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render', () => {
|
||||
renderApp(store);
|
||||
expect(screen.queryAllByTestId('pending-groups-row')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with pending group requests', () => {
|
||||
beforeEach(() => {
|
||||
__stub((mock) => {
|
||||
mock.onGet('/api/v1/groups').reply(200, [
|
||||
normalizeGroup({
|
||||
display_name: 'Group',
|
||||
id: '1',
|
||||
}),
|
||||
]);
|
||||
|
||||
mock.onGet('/api/v1/groups/relationships?id[]=1').reply(200, [
|
||||
normalizeGroupRelationship({
|
||||
id: '1',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render the row', async () => {
|
||||
renderApp(store);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryAllByTestId('pending-groups-row')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -12,7 +12,7 @@ interface IGroup {
|
||||
width?: number
|
||||
}
|
||||
|
||||
const Group = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDivElement>) => {
|
||||
const { group, width = 'auto' } = props;
|
||||
|
||||
return (
|
||||
@@ -78,4 +78,4 @@ const Group = forwardRef((props: IGroup, ref: React.ForwardedRef<HTMLDivElement>
|
||||
);
|
||||
});
|
||||
|
||||
export default Group;
|
||||
export default GroupGridItem;
|
||||
@@ -0,0 +1,80 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { Group as GroupEntity } from 'soapbox/types/entities';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
interface IGroup {
|
||||
group: GroupEntity
|
||||
withJoinAction?: boolean
|
||||
}
|
||||
|
||||
const GroupListItem = (props: IGroup) => {
|
||||
const { group, withJoinAction = true } = props;
|
||||
|
||||
return (
|
||||
<HStack
|
||||
key={group.id}
|
||||
alignItems='center'
|
||||
justifyContent='between'
|
||||
>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Avatar
|
||||
className='ring-2 ring-white dark:ring-primary-900'
|
||||
src={group.avatar}
|
||||
size={44}
|
||||
/>
|
||||
|
||||
<Stack>
|
||||
<Text
|
||||
weight='bold'
|
||||
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
|
||||
/>
|
||||
|
||||
<HStack className='text-gray-700 dark:text-gray-600' space={1} alignItems='center'>
|
||||
<Icon
|
||||
className='h-4.5 w-4.5'
|
||||
src={group.locked ? require('@tabler/icons/lock.svg') : require('@tabler/icons/world.svg')}
|
||||
/>
|
||||
|
||||
<Text theme='inherit' tag='span' size='sm' weight='medium'>
|
||||
{group.locked ? (
|
||||
<FormattedMessage id='group.privacy.locked' defaultMessage='Private' />
|
||||
) : (
|
||||
<FormattedMessage id='group.privacy.public' defaultMessage='Public' />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{typeof group.members_count !== 'undefined' && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<Text theme='inherit' tag='span' size='sm' weight='medium'>
|
||||
{shortNumberFormat(group.members_count)}
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.results.member_count'
|
||||
defaultMessage='{members, plural, one {member} other {members}}'
|
||||
values={{
|
||||
members: group.members_count,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
</HStack>
|
||||
|
||||
{withJoinAction && (
|
||||
<Button theme='primary'>
|
||||
{group.locked
|
||||
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
|
||||
: <FormattedMessage id='group.join.public' defaultMessage='Join Group' />}
|
||||
</Button>
|
||||
)}
|
||||
</HStack>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupListItem;
|
||||
@@ -5,7 +5,7 @@ import { Carousel, Stack, Text } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
|
||||
import { usePopularGroups } from 'soapbox/queries/groups';
|
||||
|
||||
import Group from './group';
|
||||
import GroupGridItem from './group-grid-item';
|
||||
|
||||
const PopularGroups = () => {
|
||||
const { groups, isFetching, isFetched, isError } = usePopularGroups();
|
||||
@@ -49,7 +49,7 @@ const PopularGroups = () => {
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
<GroupGridItem
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
|
||||
@@ -3,12 +3,12 @@ import React, { useCallback, useState } from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Components, Virtuoso, VirtuosoGrid } from 'react-virtuoso';
|
||||
|
||||
import { Avatar, Button, HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { HStack, Icon, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useGroupSearch } from 'soapbox/queries/groups/search';
|
||||
import { Group } from 'soapbox/types/entities';
|
||||
import { shortNumberFormat } from 'soapbox/utils/numbers';
|
||||
|
||||
import GroupComp from '../group';
|
||||
import GroupGridItem from '../group-grid-item';
|
||||
import GroupListItem from '../group-list-item';
|
||||
|
||||
interface Props {
|
||||
groupSearchResult: ReturnType<typeof useGroupSearch>
|
||||
@@ -38,73 +38,20 @@ export default (props: Props) => {
|
||||
};
|
||||
|
||||
const renderGroupList = useCallback((group: Group, index: number) => (
|
||||
<HStack
|
||||
alignItems='center'
|
||||
justifyContent='between'
|
||||
<div
|
||||
className={
|
||||
clsx({
|
||||
'pt-4': index !== 0,
|
||||
})
|
||||
}
|
||||
>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<Avatar
|
||||
className='ring-2 ring-white dark:ring-primary-900'
|
||||
src={group.avatar}
|
||||
size={44}
|
||||
/>
|
||||
|
||||
<Stack>
|
||||
<Text
|
||||
weight='bold'
|
||||
dangerouslySetInnerHTML={{ __html: group.display_name_html }}
|
||||
/>
|
||||
|
||||
<HStack className='text-gray-700 dark:text-gray-600' space={1} alignItems='center'>
|
||||
<Icon
|
||||
className='h-4.5 w-4.5'
|
||||
src={group.locked ? require('@tabler/icons/lock.svg') : require('@tabler/icons/world.svg')}
|
||||
/>
|
||||
|
||||
<Text theme='inherit' tag='span' size='sm' weight='medium'>
|
||||
{group.locked ? (
|
||||
<FormattedMessage id='group.privacy.locked' defaultMessage='Private' />
|
||||
) : (
|
||||
<FormattedMessage id='group.privacy.public' defaultMessage='Public' />
|
||||
)}
|
||||
</Text>
|
||||
|
||||
{typeof group.members_count !== 'undefined' && (
|
||||
<>
|
||||
<span>•</span>
|
||||
<Text theme='inherit' tag='span' size='sm' weight='medium'>
|
||||
{shortNumberFormat(group.members_count)}
|
||||
{' '}
|
||||
<FormattedMessage
|
||||
id='groups.discover.search.results.member_count'
|
||||
defaultMessage='{members, plural, one {member} other {members}}'
|
||||
values={{
|
||||
members: group.members_count,
|
||||
}}
|
||||
/>
|
||||
</Text>
|
||||
</>
|
||||
)}
|
||||
</HStack>
|
||||
</Stack>
|
||||
</HStack>
|
||||
|
||||
<Button theme='primary'>
|
||||
{group.locked
|
||||
? <FormattedMessage id='group.join.private' defaultMessage='Request Access' />
|
||||
: <FormattedMessage id='group.join.public' defaultMessage='Join Group' />}
|
||||
</Button>
|
||||
</HStack>
|
||||
<GroupListItem group={group} withJoinAction />
|
||||
</div>
|
||||
), []);
|
||||
|
||||
const renderGroupGrid = useCallback((group: Group, index: number) => (
|
||||
<div className='pb-4'>
|
||||
<GroupComp group={group} />
|
||||
<GroupGridItem group={group} />
|
||||
</div>
|
||||
), []);
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Carousel, Stack, Text } from 'soapbox/components/ui';
|
||||
import PlaceholderGroupDiscover from 'soapbox/features/placeholder/components/placeholder-group-discover';
|
||||
import { useSuggestedGroups } from 'soapbox/queries/groups';
|
||||
|
||||
import Group from './group';
|
||||
import GroupGridItem from './group-grid-item';
|
||||
|
||||
const SuggestedGroups = () => {
|
||||
const { groups, isFetching, isFetched, isError } = useSuggestedGroups();
|
||||
@@ -49,7 +49,7 @@ const SuggestedGroups = () => {
|
||||
))
|
||||
) : (
|
||||
groups.map((group) => (
|
||||
<Group
|
||||
<GroupGridItem
|
||||
key={group.id}
|
||||
group={group}
|
||||
width={width}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { Divider, HStack, Icon, Text } from 'soapbox/components/ui';
|
||||
import { useFeatures } from 'soapbox/hooks';
|
||||
import { usePendingGroups } from 'soapbox/queries/groups';
|
||||
|
||||
export default () => {
|
||||
const features = useFeatures();
|
||||
|
||||
const { groups, isFetching } = usePendingGroups();
|
||||
|
||||
if (!features.groupsPending || isFetching || groups.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Link to='/groups/pending-requests' className='group' data-testid='pending-groups-row'>
|
||||
<HStack alignItems='center' justifyContent='between'>
|
||||
<HStack alignItems='center' space={2}>
|
||||
<div className='rounded-full bg-primary-200 p-3 text-primary-500 dark:bg-primary-800 dark:text-primary-200'>
|
||||
<Icon
|
||||
src={require('@tabler/icons/exclamation-circle.svg')}
|
||||
className='h-7 w-7'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Text weight='bold' size='md'>
|
||||
<FormattedMessage
|
||||
id='groups.pending.count'
|
||||
defaultMessage='{number, plural, one {# pending request} other {# pending requests}}'
|
||||
values={{ number: groups.length }}
|
||||
/>
|
||||
</Text>
|
||||
</HStack>
|
||||
|
||||
<Icon
|
||||
src={require('@tabler/icons/chevron-right.svg')}
|
||||
className='h-5 w-5 text-gray-600 transition-colors group-hover:text-gray-700 dark:text-gray-600 dark:group-hover:text-gray-500'
|
||||
/>
|
||||
</HStack>
|
||||
</Link>
|
||||
|
||||
<Divider />
|
||||
</>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user