From 0d617d4de8afd04132d9fa4bf1d4cc6c7f3e0653 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?marcin=20miko=C5=82ajczak?= Date: Sat, 17 Dec 2022 00:07:35 +0100 Subject: [PATCH] Groups: simple member list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: marcin mikołajczak --- app/soapbox/features/group/group-members.tsx | 89 +++++++++++++++++++ app/soapbox/features/group/group-timeline.tsx | 8 +- app/soapbox/features/ui/index.tsx | 2 + .../features/ui/util/async-components.ts | 4 + app/soapbox/reducers/group-memberships.ts | 4 +- 5 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 app/soapbox/features/group/group-members.tsx diff --git a/app/soapbox/features/group/group-members.tsx b/app/soapbox/features/group/group-members.tsx new file mode 100644 index 0000000000..0947876f4d --- /dev/null +++ b/app/soapbox/features/group/group-members.tsx @@ -0,0 +1,89 @@ +import debounce from 'lodash/debounce'; +import React, { useCallback, useEffect } from 'react'; +import { defineMessages, useIntl } from 'react-intl'; + +import { expandGroupMemberships, fetchGroup, fetchGroupMemberships } from 'soapbox/actions/groups'; +import ScrollableList from 'soapbox/components/scrollable-list'; +import { CardHeader, CardTitle } from 'soapbox/components/ui'; +import AccountContainer from 'soapbox/containers/account-container'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; + +import type { List } from 'soapbox/reducers/group-memberships'; + +type RouteParams = { id: string }; + +interface IGroupMembers { + params: RouteParams, +} + +const messages = defineMessages({ + heading: { id: 'column.group_members', defaultMessage: 'Group members' }, + adminSubheading: { id: 'groups.admin_subheading', defaultMessage: 'Group administrators' }, + moderatorSubheading: { id: 'groups.moderator_subheading', defaultMessage: 'Group moderators' }, + userSubheading: { id: 'groups.user_subheading', defaultMessage: 'Users' }, +}); + +const GroupMembers: React.FC = (props) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const groupId = props.params.id; + + const admins = useAppSelector((state) => state.group_memberships.admin.get(groupId)); + const moderators = useAppSelector((state) => state.group_memberships.moderator.get(groupId)); + const users = useAppSelector((state) => state.group_memberships.user.get(groupId)); + + const handleLoadMore = (role: 'admin' | 'moderator' | 'user') => { + dispatch(expandGroupMemberships(groupId, role)); + }; + + const handleLoadMoreAdmins = useCallback(debounce(() => { + handleLoadMore('admin'); + }, 300, { leading: true }), []); + + const handleLoadMoreModerators = useCallback(debounce(() => { + handleLoadMore('moderator'); + }, 300, { leading: true }), []); + + const handleLoadMoreUsers = useCallback(debounce(() => { + handleLoadMore('user'); + }, 300, { leading: true }), []); + + const renderMemberships = (memberships: List | undefined, role: 'admin' | 'moderator' | 'user', handler: () => void) => { + if (!memberships?.isLoading && !memberships?.items.count()) return; + + return ( + + + + + + {memberships?.items?.map(accountId => )} + + + ); + }; + + useEffect(() => { + dispatch(fetchGroup(groupId)); + + dispatch(fetchGroupMemberships(groupId, 'admin')); + dispatch(fetchGroupMemberships(groupId, 'moderator')); + dispatch(fetchGroupMemberships(groupId, 'user')); + }, [groupId]); + + return ( + <> + {renderMemberships(admins, 'admin', handleLoadMoreAdmins)} + {renderMemberships(moderators, 'moderator', handleLoadMoreModerators)} + {renderMemberships(users, 'user', handleLoadMoreUsers)} + + ); +}; + +export default GroupMembers; diff --git a/app/soapbox/features/group/group-timeline.tsx b/app/soapbox/features/group/group-timeline.tsx index c2dd3ac3d2..a99dd030b8 100644 --- a/app/soapbox/features/group/group-timeline.tsx +++ b/app/soapbox/features/group/group-timeline.tsx @@ -32,16 +32,14 @@ const GroupTimeline: React.FC = (props) => { dispatch(fetchGroup(groupId)); dispatch(expandGroupTimeline(groupId)); + dispatch(groupCompose(`group:${groupId}`, groupId)); + const disconnect = dispatch(connectGroupStream(groupId)); return () => { disconnect(); }; - }, []); - - useEffect(() => { - dispatch(groupCompose(`group:${groupId}`, groupId)); - }, []); + }, [groupId]); return ( diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx index 8d8f399ea8..e5db4eb646 100644 --- a/app/soapbox/features/ui/index.tsx +++ b/app/soapbox/features/ui/index.tsx @@ -114,6 +114,7 @@ import { EventDiscussion, Events, Groups, + GroupMembers, GroupTimeline, } from './util/async-components'; import { WrappedRoute } from './util/react-router-helpers'; @@ -278,6 +279,7 @@ const SwitchingColumnsArea: React.FC = ({ children }) => { + diff --git a/app/soapbox/features/ui/util/async-components.ts b/app/soapbox/features/ui/util/async-components.ts index 1b088a63f9..28705750b7 100644 --- a/app/soapbox/features/ui/util/async-components.ts +++ b/app/soapbox/features/ui/util/async-components.ts @@ -546,6 +546,10 @@ export function Groups() { return import(/* webpackChunkName: "features/groups" */'../../groups'); } +export function GroupMembers() { + return import(/* webpackChunkName: "features/groups" */'../../group/group-members'); +} + export function GroupTimeline() { return import(/* webpackChunkName: "features/groups" */'../../group/group-timeline'); } diff --git a/app/soapbox/reducers/group-memberships.ts b/app/soapbox/reducers/group-memberships.ts index dc12e28bed..ed890abe74 100644 --- a/app/soapbox/reducers/group-memberships.ts +++ b/app/soapbox/reducers/group-memberships.ts @@ -30,11 +30,11 @@ const ReducerRecord = ImmutableRecord({ }); export type GroupRole = 'admin' | 'moderator' | 'user'; -type List = ReturnType; +export type List = ReturnType; type State = ReturnType; const normalizeList = (state: State, path: string[], memberships: APIEntity[], next: string | null) => { - return state.setIn(path, ImmutableMap({ + return state.setIn(path, ListRecord({ next, items: ImmutableOrderedSet(memberships.map(item => item.account.id)), isLoading: false,