From 3a2b4c6efbb6f62c5b7823d751b2d751766dd22c Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Thu, 23 Mar 2023 08:43:41 -0400 Subject: [PATCH 1/2] Add ability to search my Groups --- .../features/group/group-blocked-members.tsx | 3 +- app/soapbox/features/group/group-members.tsx | 2 +- .../group/group-membership-requests.tsx | 2 +- app/soapbox/features/group/group-timeline.tsx | 3 +- app/soapbox/features/group/manage-group.tsx | 4 +-- app/soapbox/features/groups/index.tsx | 29 +++++++++++++++---- .../ui/components/panels/my-groups-panel.tsx | 2 +- .../hooks/__tests__/useGroupsPath.test.ts | 2 +- .../hooks/{ => api/groups}/useGroups.ts | 8 ++--- app/soapbox/hooks/api/index.ts | 1 + app/soapbox/hooks/api/usePopularGroups.ts | 2 +- app/soapbox/hooks/api/useSuggestedGroups.ts | 2 +- app/soapbox/hooks/index.ts | 1 - app/soapbox/hooks/useGroupsPath.ts | 3 +- app/soapbox/locales/en.json | 1 + app/soapbox/pages/group-page.tsx | 3 +- app/soapbox/utils/features.ts | 5 ++++ 17 files changed, 50 insertions(+), 23 deletions(-) rename app/soapbox/hooks/{ => api/groups}/useGroups.ts (92%) diff --git a/app/soapbox/features/group/group-blocked-members.tsx b/app/soapbox/features/group/group-blocked-members.tsx index 9c197da62..8931575ef 100644 --- a/app/soapbox/features/group/group-blocked-members.tsx +++ b/app/soapbox/features/group/group-blocked-members.tsx @@ -5,7 +5,8 @@ import { fetchGroupBlocks, groupUnblock } from 'soapbox/actions/groups'; import Account from 'soapbox/components/account'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Button, Column, HStack, Spinner } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useGroup } from 'soapbox/hooks'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import { makeGetAccount } from 'soapbox/selectors'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx index 39fbd940f..2facf912f 100644 --- a/app/soapbox/features/group/group-members.tsx +++ b/app/soapbox/features/group/group-members.tsx @@ -3,7 +3,7 @@ import React, { useMemo } from 'react'; import { PendingItemsRow } from 'soapbox/components/pending-items-row'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { useGroup } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers'; import { GroupRoles } from 'soapbox/schemas/group-member'; diff --git a/app/soapbox/features/group/group-membership-requests.tsx b/app/soapbox/features/group/group-membership-requests.tsx index d98517af8..f0f3bbd09 100644 --- a/app/soapbox/features/group/group-membership-requests.tsx +++ b/app/soapbox/features/group/group-membership-requests.tsx @@ -5,7 +5,7 @@ import Account from 'soapbox/components/account'; import { AuthorizeRejectButtons } from 'soapbox/components/authorize-reject-buttons'; import ScrollableList from 'soapbox/components/scrollable-list'; import { Column, HStack, Spinner } from 'soapbox/components/ui'; -import { useGroup } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index f549d5605..9494b033e 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -7,7 +7,8 @@ import { connectGroupStream } from 'soapbox/actions/streaming'; import { expandGroupTimeline } from 'soapbox/actions/timelines'; import { Avatar, HStack, Icon, Stack, Text } from 'soapbox/components/ui'; import ComposeForm from 'soapbox/features/compose/components/compose-form'; -import { useAppDispatch, useGroup, useOwnAccount } from 'soapbox/hooks'; +import { useAppDispatch, useOwnAccount } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import Timeline from '../ui/components/timeline'; diff --git a/app/soapbox/features/group/manage-group.tsx b/app/soapbox/features/group/manage-group.tsx index 460bc0906..1eb94f379 100644 --- a/app/soapbox/features/group/manage-group.tsx +++ b/app/soapbox/features/group/manage-group.tsx @@ -6,8 +6,8 @@ import { editGroup } from 'soapbox/actions/groups'; import { openModal } from 'soapbox/actions/modals'; import List, { ListItem } from 'soapbox/components/list'; import { CardBody, CardHeader, CardTitle, Column, Spinner, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useGroup, useGroupsPath } from 'soapbox/hooks'; -import { useDeleteGroup } from 'soapbox/hooks/api'; +import { useAppDispatch, useGroupsPath } from 'soapbox/hooks'; +import { useDeleteGroup, useGroup } from 'soapbox/hooks/api'; import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; diff --git a/app/soapbox/features/groups/index.tsx b/app/soapbox/features/groups/index.tsx index c84ddbd53..335588794 100644 --- a/app/soapbox/features/groups/index.tsx +++ b/app/soapbox/features/groups/index.tsx @@ -1,12 +1,13 @@ -import React from 'react'; -import { FormattedMessage } from 'react-intl'; +import React, { useState } from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; import { Link } from 'react-router-dom'; import { openModal } from 'soapbox/actions/modals'; import GroupCard from 'soapbox/components/group-card'; import ScrollableList from 'soapbox/components/scrollable-list'; -import { Button, Stack, Text } from 'soapbox/components/ui'; -import { useAppDispatch, useAppSelector, useGroups, useFeatures } from 'soapbox/hooks'; +import { Button, Input, Stack, Text } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector, useDebounce, useFeatures } from 'soapbox/hooks'; +import { useGroups } from 'soapbox/hooks/api'; import { PERMISSION_CREATE_GROUPS, hasPermission } from 'soapbox/utils/permissions'; import PlaceholderGroupCard from '../placeholder/components/placeholder-group-card'; @@ -16,13 +17,22 @@ import TabBar, { TabItems } from './components/tab-bar'; import type { Group as GroupEntity } from 'soapbox/types/entities'; +const messages = defineMessages({ + placeholder: { id: 'groups.search.placeholder', defaultMessage: 'Search My Groups' }, +}); + const Groups: React.FC = () => { + const debounce = useDebounce; const dispatch = useAppDispatch(); const features = useFeatures(); + const intl = useIntl(); const canCreateGroup = useAppSelector((state) => hasPermission(state, PERMISSION_CREATE_GROUPS)); - const { groups, isLoading } = useGroups(); + const [searchValue, setSearchValue] = useState(''); + const debouncedValue = debounce(searchValue, 300); + + const { groups, isLoading } = useGroups(debouncedValue); const createGroup = () => { dispatch(openModal('MANAGE_GROUP')); @@ -76,6 +86,15 @@ const Groups: React.FC = () => { )} + {features.groupsSearch ? ( + setSearchValue(event.target.value)} + placeholder={intl.formatMessage(messages.placeholder)} + theme='search' + value={searchValue} + /> + ) : null} + { const { groups, isFetching, isFetched, isError } = useGroups(); diff --git a/app/soapbox/hooks/__tests__/useGroupsPath.test.ts b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts index 7596acd9a..d102fe412 100644 --- a/app/soapbox/hooks/__tests__/useGroupsPath.test.ts +++ b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts @@ -53,7 +53,7 @@ describe('useGroupsPath()', () => { describe('when the user has groups', () => { beforeEach(() => { __stub((mock) => { - mock.onGet('/api/v1/groups').reply(200, [ + mock.onGet('/api/v1/groups?q=').reply(200, [ buildGroup({ display_name: 'Group', id: '1', diff --git a/app/soapbox/hooks/useGroups.ts b/app/soapbox/hooks/api/groups/useGroups.ts similarity index 92% rename from app/soapbox/hooks/useGroups.ts rename to app/soapbox/hooks/api/groups/useGroups.ts index 9bd0e99ca..a99dd43c6 100644 --- a/app/soapbox/hooks/useGroups.ts +++ b/app/soapbox/hooks/api/groups/useGroups.ts @@ -6,15 +6,15 @@ import { useApi } from 'soapbox/hooks'; import { groupSchema, Group } from 'soapbox/schemas/group'; import { groupRelationshipSchema, GroupRelationship } from 'soapbox/schemas/group-relationship'; -import { useFeatures } from './useFeatures'; +import { useFeatures } from '../../useFeatures'; -function useGroups() { +function useGroups(q: string = '') { const api = useApi(); const features = useFeatures(); const { entities, ...result } = useEntities( - [Entities.GROUPS], - () => api.get('/api/v1/groups'), + [Entities.GROUPS, 'search', q], + () => api.get('/api/v1/groups', { params: { q } }), { enabled: features.groups, schema: groupSchema }, ); const { relationships } = useGroupRelationships(entities.map(entity => entity.id)); diff --git a/app/soapbox/hooks/api/index.ts b/app/soapbox/hooks/api/index.ts index 3fa29718b..d1c8ec09f 100644 --- a/app/soapbox/hooks/api/index.ts +++ b/app/soapbox/hooks/api/index.ts @@ -5,6 +5,7 @@ export { useBlockGroupMember } from './groups/useBlockGroupMember'; export { useCancelMembershipRequest } from './groups/useCancelMembershipRequest'; export { useDeleteGroup } from './groups/useDeleteGroup'; export { useDemoteGroupMember } from './groups/useDemoteGroupMember'; +export { useGroup, useGroups } from './groups/useGroups'; export { useJoinGroup } from './groups/useJoinGroup'; export { useLeaveGroup } from './groups/useLeaveGroup'; export { usePromoteGroupMember } from './groups/usePromoteGroupMember'; diff --git a/app/soapbox/hooks/api/usePopularGroups.ts b/app/soapbox/hooks/api/usePopularGroups.ts index 97d375174..b7c683970 100644 --- a/app/soapbox/hooks/api/usePopularGroups.ts +++ b/app/soapbox/hooks/api/usePopularGroups.ts @@ -2,9 +2,9 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { Group, groupSchema } from 'soapbox/schemas'; +import { useGroupRelationships } from '../api/groups/useGroups'; import { useApi } from '../useApi'; import { useFeatures } from '../useFeatures'; -import { useGroupRelationships } from '../useGroups'; function usePopularGroups() { const api = useApi(); diff --git a/app/soapbox/hooks/api/useSuggestedGroups.ts b/app/soapbox/hooks/api/useSuggestedGroups.ts index 9d5e20ace..0ac06b4ce 100644 --- a/app/soapbox/hooks/api/useSuggestedGroups.ts +++ b/app/soapbox/hooks/api/useSuggestedGroups.ts @@ -2,9 +2,9 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { Group, groupSchema } from 'soapbox/schemas'; +import { useGroupRelationships } from '../api/groups/useGroups'; import { useApi } from '../useApi'; import { useFeatures } from '../useFeatures'; -import { useGroupRelationships } from '../useGroups'; function useSuggestedGroups() { const api = useApi(); diff --git a/app/soapbox/hooks/index.ts b/app/soapbox/hooks/index.ts index 3f66a8147..ef8b6462f 100644 --- a/app/soapbox/hooks/index.ts +++ b/app/soapbox/hooks/index.ts @@ -6,7 +6,6 @@ export { useClickOutside } from './useClickOutside'; export { useCompose } from './useCompose'; export { useDebounce } from './useDebounce'; export { useGetState } from './useGetState'; -export { useGroup, useGroups } from './useGroups'; export { useGroupsPath } from './useGroupsPath'; export { useDimensions } from './useDimensions'; export { useFeatures } from './useFeatures'; diff --git a/app/soapbox/hooks/useGroupsPath.ts b/app/soapbox/hooks/useGroupsPath.ts index b855ec0a6..a4c1efa19 100644 --- a/app/soapbox/hooks/useGroupsPath.ts +++ b/app/soapbox/hooks/useGroupsPath.ts @@ -1,5 +1,4 @@ -import { useGroups } from 'soapbox/hooks'; - +import { useGroups } from './api'; import { useFeatures } from './useFeatures'; /** diff --git a/app/soapbox/locales/en.json b/app/soapbox/locales/en.json index 1d4d12a31..6866cb0a2 100644 --- a/app/soapbox/locales/en.json +++ b/app/soapbox/locales/en.json @@ -825,6 +825,7 @@ "groups.pending.empty.title": "No pending requests", "groups.pending.label": "Pending Requests", "groups.popular.label": "Suggested Groups", + "groups.search.placeholder": "Search My Groups", "hashtag.column_header.tag_mode.all": "and {additional}", "hashtag.column_header.tag_mode.any": "or {additional}", "hashtag.column_header.tag_mode.none": "without {additional}", diff --git a/app/soapbox/pages/group-page.tsx b/app/soapbox/pages/group-page.tsx index 2dd80510f..ee6f46cb6 100644 --- a/app/soapbox/pages/group-page.tsx +++ b/app/soapbox/pages/group-page.tsx @@ -12,7 +12,8 @@ import { SignUpPanel, SuggestedGroupsPanel, } from 'soapbox/features/ui/util/async-components'; -import { useGroup, useOwnAccount } from 'soapbox/hooks'; +import { useOwnAccount } from 'soapbox/hooks'; +import { useGroup } from 'soapbox/hooks/api'; import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; import { Group } from 'soapbox/schemas'; diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index b9d0e2210..631a49ac3 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -542,6 +542,11 @@ const getInstanceFeatures = (instance: Instance) => { */ groupsPromoteToAdmin: v.software !== TRUTHSOCIAL, + /** + * Can search my own groups. + */ + groupsSearch: v.software === TRUTHSOCIAL, + /** * Can hide follows/followers lists and counts. * @see PATCH /api/v1/accounts/update_credentials From e6252070a66cbf4030c9fc160040e06892d8d6f0 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 28 Mar 2023 09:46:55 -0400 Subject: [PATCH 2/2] Fix test --- app/soapbox/hooks/__tests__/useGroupsPath.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/soapbox/hooks/__tests__/useGroupsPath.test.ts b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts index d102fe412..7596acd9a 100644 --- a/app/soapbox/hooks/__tests__/useGroupsPath.test.ts +++ b/app/soapbox/hooks/__tests__/useGroupsPath.test.ts @@ -53,7 +53,7 @@ describe('useGroupsPath()', () => { describe('when the user has groups', () => { beforeEach(() => { __stub((mock) => { - mock.onGet('/api/v1/groups?q=').reply(200, [ + mock.onGet('/api/v1/groups').reply(200, [ buildGroup({ display_name: 'Group', id: '1',