From 8cb05403619ead8bbf31196bb660cd0daa6d0c12 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 4 Apr 2023 11:45:27 -0400 Subject: [PATCH 01/55] Fix infinite scroll bug with Group Search --- .../components/discover/search/search.tsx | 4 +- app/soapbox/queries/groups/search.ts | 67 ------------------- 2 files changed, 2 insertions(+), 69 deletions(-) delete mode 100644 app/soapbox/queries/groups/search.ts diff --git a/app/soapbox/features/groups/components/discover/search/search.tsx b/app/soapbox/features/groups/components/discover/search/search.tsx index 4e3308353..647f10fb5 100644 --- a/app/soapbox/features/groups/components/discover/search/search.tsx +++ b/app/soapbox/features/groups/components/discover/search/search.tsx @@ -26,7 +26,7 @@ export default (props: Props) => { const debouncedValueToSave = debounce(searchValue as string, 1000); const groupSearchResult = useGroupSearch(debouncedValue); - const { groups, isFetching, isFetched, isError } = groupSearchResult; + const { groups, isLoading, isFetched, isError } = groupSearchResult; const hasSearchResults = isFetched && groups.length > 0; const hasNoSearchResults = isFetched && groups.length === 0; @@ -37,7 +37,7 @@ export default (props: Props) => { } }, [debouncedValueToSave]); - if (isFetching) { + if (isLoading) { return ( diff --git a/app/soapbox/queries/groups/search.ts b/app/soapbox/queries/groups/search.ts deleted file mode 100644 index 3da166cc2..000000000 --- a/app/soapbox/queries/groups/search.ts +++ /dev/null @@ -1,67 +0,0 @@ -import { useInfiniteQuery } from '@tanstack/react-query'; - -import { getNextLink } from 'soapbox/api'; -import { useApi, useFeatures } from 'soapbox/hooks'; -import { normalizeGroup } from 'soapbox/normalizers'; -import { Group } from 'soapbox/types/entities'; -import { flattenPages, PaginatedResult } from 'soapbox/utils/queries'; - -const GroupSearchKeys = { - search: (query?: string) => query ? ['groups', 'search', query] : ['groups', 'search'] as const, -}; - -type PageParam = { - link: string -} - -const useGroupSearch = (search?: string) => { - const api = useApi(); - const features = useFeatures(); - - const getSearchResults = async (pageParam: PageParam): Promise> => { - const nextPageLink = pageParam?.link; - const uri = nextPageLink || '/api/v1/groups/search'; - const response = await api.get(uri, { - params: search ? { - q: search, - } : undefined, - }); - const { data } = response; - - const link = getNextLink(response); - const hasMore = !!link; - const result = data.map(normalizeGroup); - - return { - result, - hasMore, - link, - }; - }; - - const queryInfo = useInfiniteQuery( - GroupSearchKeys.search(search), - ({ pageParam }) => getSearchResults(pageParam), - { - keepPreviousData: true, - enabled: features.groups && !!search, - getNextPageParam: (config) => { - if (config.hasMore) { - return { link: config.link }; - } - - return undefined; - }, - }); - - const data = flattenPages(queryInfo.data); - - return { - ...queryInfo, - groups: data || [], - }; -}; - -export { - useGroupSearch, -}; From 5fa8a21403ddb7a6adca6dfdba62dc5a2f38ee67 Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 4 Apr 2023 14:05:08 -0400 Subject: [PATCH 02/55] Cap Group Admins at 5 --- app/soapbox/components/ui/toast/toast.tsx | 59 +++++++++++-------- .../components/group-member-list-item.tsx | 14 ++++- app/soapbox/features/group/group-members.tsx | 10 ++++ app/soapbox/hooks/api/useGroupMembers.ts | 3 +- app/soapbox/toast.tsx | 1 + app/soapbox/utils/features.ts | 5 ++ 6 files changed, 65 insertions(+), 27 deletions(-) diff --git a/app/soapbox/components/ui/toast/toast.tsx b/app/soapbox/components/ui/toast/toast.tsx index 38bda317e..94fc44da1 100644 --- a/app/soapbox/components/ui/toast/toast.tsx +++ b/app/soapbox/components/ui/toast/toast.tsx @@ -8,6 +8,8 @@ import { ToastText, ToastType } from 'soapbox/toast'; import HStack from '../hstack/hstack'; import Icon from '../icon/icon'; +import Stack from '../stack/stack'; +import Text from '../text/text'; const renderText = (text: ToastText) => { if (typeof text === 'string') { @@ -24,13 +26,14 @@ interface IToast { action?(): void actionLink?: string actionLabel?: ToastText + summary?: string } /** * Customizable Toasts for in-app notifications. */ const Toast = (props: IToast) => { - const { t, message, type, action, actionLink, actionLabel } = props; + const { t, message, type, action, actionLink, actionLabel, summary } = props; const dismissToast = () => toast.dismiss(t.id); @@ -109,35 +112,41 @@ const Toast = (props: IToast) => { }) } > - - - -
- {renderIcon()} -
+ + + + +
+ {renderIcon()} +
-

- {renderText(message)} -

+ + {renderText(message)} + +
+ + {/* Action */} + {renderAction()}
- {/* Action */} - {renderAction()} + {/* Dismiss Button */} +
+ +
- {/* Dismiss Button */} -
- -
-
+ {summary ? ( + {summary} + ) : null} +
); }; diff --git a/app/soapbox/features/group/components/group-member-list-item.tsx b/app/soapbox/features/group/components/group-member-list-item.tsx index 4fa3ccb7a..c88f4e58d 100644 --- a/app/soapbox/features/group/components/group-member-list-item.tsx +++ b/app/soapbox/features/group/components/group-member-list-item.tsx @@ -15,10 +15,14 @@ import { useAccount, useBlockGroupMember, useDemoteGroupMember, usePromoteGroupM import { GroupRoles } from 'soapbox/schemas/group-member'; import toast from 'soapbox/toast'; +import { MAX_ADMIN_COUNT } from '../group-members'; + import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; import type { Group, GroupMember } from 'soapbox/types/entities'; const messages = defineMessages({ + adminLimitTitle: { id: 'group.member.admin.limit.title', defaultMessage: 'Admin limit reached' }, + adminLimitSummary: { id: 'group.member.admin.limit.summary', defaultMessage: 'You can assign up to {count} admins for the group at this time.' }, blockConfirm: { id: 'confirmations.block_from_group.confirm', defaultMessage: 'Ban' }, blockFromGroupHeading: { id: 'confirmations.block_from_group.heading', defaultMessage: 'Ban From Group' }, blockFromGroupMessage: { id: 'confirmations.block_from_group.message', defaultMessage: 'Are you sure you want to ban @{name} from the group?' }, @@ -39,10 +43,11 @@ const messages = defineMessages({ interface IGroupMemberListItem { member: GroupMember group: Group + canPromoteToAdmin: boolean } const GroupMemberListItem = (props: IGroupMemberListItem) => { - const { member, group } = props; + const { canPromoteToAdmin, member, group } = props; const dispatch = useAppDispatch(); const features = useFeatures(); @@ -90,6 +95,13 @@ const GroupMemberListItem = (props: IGroupMemberListItem) => { }; const handleAdminAssignment = () => { + if (!canPromoteToAdmin) { + toast.error(intl.formatMessage(messages.adminLimitTitle), { + summary: intl.formatMessage(messages.adminLimitSummary, { count: MAX_ADMIN_COUNT }), + }); + return; + } + dispatch(openModal('CONFIRM', { heading: intl.formatMessage(messages.promoteConfirm), message: intl.formatMessage(messages.promoteConfirmMessage, { name: account?.username }), diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx index 2facf912f..a3a790a62 100644 --- a/app/soapbox/features/group/group-members.tsx +++ b/app/soapbox/features/group/group-members.tsx @@ -3,6 +3,7 @@ import React, { useMemo } from 'react'; import { PendingItemsRow } from 'soapbox/components/pending-items-row'; import ScrollableList from 'soapbox/components/scrollable-list'; +import { useFeatures } from 'soapbox/hooks'; import { useGroup } from 'soapbox/hooks/api'; import { useGroupMembershipRequests } from 'soapbox/hooks/api/groups/useGroupMembershipRequests'; import { useGroupMembers } from 'soapbox/hooks/api/useGroupMembers'; @@ -18,9 +19,13 @@ interface IGroupMembers { params: { id: string } } +export const MAX_ADMIN_COUNT = 5; + const GroupMembers: React.FC = (props) => { const groupId = props.params.id; + const features = useFeatures(); + const { group, isFetching: isFetchingGroup } = useGroup(groupId); const { groupMembers: owners, isFetching: isFetchingOwners } = useGroupMembers(groupId, GroupRoles.OWNER); const { groupMembers: admins, isFetching: isFetchingAdmins } = useGroupMembers(groupId, GroupRoles.ADMIN); @@ -35,6 +40,10 @@ const GroupMembers: React.FC = (props) => { ...users, ], [owners, admins, users]); + const canPromoteToAdmin = features.groupsAdminMax + ? members.filter((member) => member.role === GroupRoles.ADMIN).length < MAX_ADMIN_COUNT + : true; + return ( <> = (props) => { group={group as Group} member={member} key={member.account.id} + canPromoteToAdmin={canPromoteToAdmin} /> ))} diff --git a/app/soapbox/hooks/api/useGroupMembers.ts b/app/soapbox/hooks/api/useGroupMembers.ts index 669f1c082..3a1c99af5 100644 --- a/app/soapbox/hooks/api/useGroupMembers.ts +++ b/app/soapbox/hooks/api/useGroupMembers.ts @@ -1,10 +1,11 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { GroupMember, groupMemberSchema } from 'soapbox/schemas'; +import { GroupRoles } from 'soapbox/schemas/group-member'; import { useApi } from '../useApi'; -function useGroupMembers(groupId: string, role: string) { +function useGroupMembers(groupId: string, role: GroupRoles) { const api = useApi(); const { entities, ...result } = useEntities( diff --git a/app/soapbox/toast.tsx b/app/soapbox/toast.tsx index 6095737f4..820b68b86 100644 --- a/app/soapbox/toast.tsx +++ b/app/soapbox/toast.tsx @@ -14,6 +14,7 @@ interface IToastOptions { actionLink?: string actionLabel?: ToastText duration?: number + summary?: string } const DEFAULT_DURATION = 4000; diff --git a/app/soapbox/utils/features.ts b/app/soapbox/utils/features.ts index 8558e3b9d..afbece3b4 100644 --- a/app/soapbox/utils/features.ts +++ b/app/soapbox/utils/features.ts @@ -529,6 +529,11 @@ const getInstanceFeatures = (instance: Instance) => { */ groups: v.build === UNRELEASED, + /** + * Cap # of Group Admins to 5 + */ + groupsAdminMax: v.software === TRUTHSOCIAL, + /** * Can see trending/suggested Groups. */ From 2211c6ec671a2be006de972452c6e3a71c5fc6ce Mon Sep 17 00:00:00 2001 From: Chewbacca Date: Tue, 4 Apr 2023 14:06:54 -0400 Subject: [PATCH 03/55] Use medium weight if summary is present --- app/soapbox/components/ui/toast/toast.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/soapbox/components/ui/toast/toast.tsx b/app/soapbox/components/ui/toast/toast.tsx index 94fc44da1..ce6847ced 100644 --- a/app/soapbox/components/ui/toast/toast.tsx +++ b/app/soapbox/components/ui/toast/toast.tsx @@ -120,7 +120,12 @@ const Toast = (props: IToast) => { {renderIcon()} - + {renderText(message)} From 880e63f2ec06bb10d5c9f9c41bd2031f0adf8201 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Tue, 4 Apr 2023 13:42:15 -0500 Subject: [PATCH 04/55] Streamfield: remove plus icon from button, adjust X icon color --- app/soapbox/components/ui/streamfield/streamfield.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/soapbox/components/ui/streamfield/streamfield.tsx b/app/soapbox/components/ui/streamfield/streamfield.tsx index 5c436e70b..fadcce46b 100644 --- a/app/soapbox/components/ui/streamfield/streamfield.tsx +++ b/app/soapbox/components/ui/streamfield/streamfield.tsx @@ -76,7 +76,7 @@ const Streamfield: React.FC = ({ {values.length > minItems && onRemoveItem && ( onRemoveItem(i)} title={intl.formatMessage(messages.remove)} @@ -89,7 +89,6 @@ const Streamfield: React.FC = ({ {onAddItem && (