diff --git a/src/api/groups/GetGroupMembers.ts b/src/api/groups/GetGroupMembers.ts new file mode 100644 index 00000000..58dbce6e --- /dev/null +++ b/src/api/groups/GetGroupMembers.ts @@ -0,0 +1,6 @@ +import { CreateLinkEvent } from '..'; + +export function GetGroupMembers(groupId: number): void +{ + CreateLinkEvent(`groups/members/${groupId}`); +} diff --git a/src/assets/images/groups/icons/group_icon_admin.png b/src/assets/images/groups/icons/group_icon_admin.png new file mode 100644 index 00000000..bad07e4a Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_not_admin.png b/src/assets/images/groups/icons/group_icon_not_admin.png new file mode 100644 index 00000000..7c8b9ee4 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_not_admin.png differ diff --git a/src/assets/images/groups/icons/group_icon_remove_member.png b/src/assets/images/groups/icons/group_icon_remove_member.png new file mode 100644 index 00000000..e6302cb2 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_remove_member.png differ diff --git a/src/assets/images/groups/icons/group_icon_small_owner.png b/src/assets/images/groups/icons/group_icon_small_owner.png new file mode 100644 index 00000000..7230ecc5 Binary files /dev/null and b/src/assets/images/groups/icons/group_icon_small_owner.png differ diff --git a/src/assets/styles/icons.scss b/src/assets/styles/icons.scss index fe595b1b..6ca7b485 100644 --- a/src/assets/styles/icons.scss +++ b/src/assets/styles/icons.scss @@ -603,6 +603,30 @@ height: 14px; } + &.icon-group-small-admin { + background: url('../images/groups/icons/group_icon_admin.png'); + width: 11px; + height: 13px; + } + + &.icon-group-small-not-admin { + background: url('../images/groups/icons/group_icon_not_admin.png'); + width: 11px; + height: 13px; + } + + &.icon-group-remove-member { + background: url('../images/groups/icons/group_icon_remove_member.png'); + width: 13px; + height: 14px; + } + + &.icon-group-small-owner { + background: url('../images/groups/icons/group_icon_small_owner.png'); + width: 13px; + height: 13px; + } + &.icon-navigator-info { background: url('../images/navigator/icons/info.png'); width: 18px; diff --git a/src/views/groups/GroupView.scss b/src/views/groups/GroupView.scss index daaaf82c..934c8a05 100644 --- a/src/views/groups/GroupView.scss +++ b/src/views/groups/GroupView.scss @@ -49,5 +49,6 @@ @import './views/information/GroupInformationView'; @import './views/information-standalone/GroupInformationStandaloneView'; @import './views/manager/GroupManagerView'; +@import './views/members/GroupMembersView'; @import './views/room-information/GroupRoomInformationView'; @import './views/shared-tabs/GroupSharedTabs'; diff --git a/src/views/groups/GroupsView.tsx b/src/views/groups/GroupsView.tsx index 1598c276..89300d77 100644 --- a/src/views/groups/GroupsView.tsx +++ b/src/views/groups/GroupsView.tsx @@ -8,12 +8,15 @@ import { GroupsMessageHandler } from './GroupsMessageHandler'; import { GroupCreatorView } from './views/creator/GroupCreatorView'; import { GroupInformationStandaloneView } from './views/information-standalone/GroupInformationStandaloneView'; import { GroupManagerView } from './views/manager/GroupManagerView'; +import { GroupMembersView } from './views/members/GroupMembersView'; export const GroupsView: FC<{}> = props => { const [ groupsState, dispatchGroupsState ] = useReducer(GroupsReducer, initialGroups); const [ isCreatorVisible, setIsCreatorVisible ] = useState(false); + const [ groupMembersId, setGroupMembersId ] = useState(null); + const [ groupMembersLevel, setGroupMembersLevel ] = useState(null); useEffect(() => { @@ -36,6 +39,13 @@ export const GroupsView: FC<{}> = props => SendMessageHook(new GroupSettingsComposer(Number(parts[2]))); return; + case 'members': + if(!parts[2]) return; + + setGroupMembersId(Number(parts[2])); + + if(parts[3]) setGroupMembersLevel(Number(parts[3])); + return; } }, []); @@ -60,6 +70,12 @@ export const GroupsView: FC<{}> = props => }, []); CreateMessageHook(GroupPurchasedEvent, onGroupPurchasedEvent); + + const closeMembers = useCallback(() => + { + setGroupMembersId(null); + setGroupMembersLevel(null); + }, []); return ( @@ -67,6 +83,7 @@ export const GroupsView: FC<{}> = props =>
setIsCreatorVisible(false) } /> +
diff --git a/src/views/groups/views/information/GroupInformationView.tsx b/src/views/groups/views/information/GroupInformationView.tsx index e550a3ba..99e32161 100644 --- a/src/views/groups/views/information/GroupInformationView.tsx +++ b/src/views/groups/views/information/GroupInformationView.tsx @@ -2,6 +2,7 @@ import { GroupRemoveMemberComposer } from '@nitrots/nitro-renderer'; import { FC, useCallback } from 'react'; import { CreateLinkEvent, GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api'; import { GetGroupManager } from '../../../../api/groups/GetGroupManager'; +import { GetGroupMembers } from '../../../../api/groups/GetGroupMembers'; import { TryJoinGroup } from '../../../../api/groups/TryJoinGroup'; import { SendMessageHook } from '../../../../hooks'; import { CatalogPageName } from '../../../catalog/common/CatalogPageName'; @@ -76,6 +77,12 @@ export const GroupInformationView: FC = props => { switch(action) { + case 'members': + GetGroupMembers(groupInformation.id); + break; + case 'manage': + GetGroupManager(groupInformation.id); + break; case 'homeroom': TryVisitRoom(groupInformation.roomId); break; @@ -94,13 +101,13 @@ export const GroupInformationView: FC = props =>
-
+
handleAction('members') }> { LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) }
{ groupInformation.pendingRequestsCount > 0 &&
{ LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) }
} - { groupInformation.isOwner &&
GetGroupManager(groupInformation.id) }> + { groupInformation.isOwner &&
handleAction('manage') }> { LocalizeText('group.manage') }
}
diff --git a/src/views/groups/views/members/GroupMembersView.scss b/src/views/groups/views/members/GroupMembersView.scss new file mode 100644 index 00000000..7973c711 --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.scss @@ -0,0 +1,29 @@ +.nitro-group-members { + width: 400px; + + .group-badge { + width: 50px; + height: 50px; + } + + .members-list { + height: 300px; + + .list-member { + border: 1px solid #ced4da; + + .avatar-head { + position: relative; + overflow: hidden; + width: 40px; + height: 40px; + + .avatar-image { + position: absolute; + left: -25px; + top: -25px; + } + } + } + } +} diff --git a/src/views/groups/views/members/GroupMembersView.tsx b/src/views/groups/views/members/GroupMembersView.tsx new file mode 100644 index 00000000..92391e4a --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.tsx @@ -0,0 +1,142 @@ +import { GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembersParser, GroupRank } from '@nitrots/nitro-renderer'; +import classNames from 'classnames'; +import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react'; +import { GetSessionDataManager, LocalizeText } from '../../../../api'; +import { CreateMessageHook, SendMessageHook } from '../../../../hooks'; +import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout'; +import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView'; +import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView'; +import { GroupMembersViewProps } from './GroupMembersView.types'; + +export const GroupMembersView: FC = props => +{ + const { groupId = null, levelId = null, onClose = null } = props; + + const [ pageData, setPageData ] = useState(null); + const [ searchQuery, setSearchQuery ] = useState(''); + const [ searchLevelId, setSearchLevelId ] = useState(3); + const [ totalPages, setTotalPages ] = useState(0); + + const searchMembers = useCallback((pageId: number) => + { + if(!groupId) return; + + SendMessageHook(new GroupMembersComposer(groupId, pageId, searchQuery, searchLevelId)); + }, [ groupId, searchQuery, searchLevelId ]); + + const onGroupMembersEvent = useCallback((event: GroupMembersEvent) => + { + const parser = event.getParser(); + + setPageData(parser); + setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize)); + }, []); + + CreateMessageHook(GroupMembersEvent, onGroupMembersEvent); + + useEffect(() => + { + setPageData(null); + setSearchQuery(''); + setSearchLevelId(0); + + if(!groupId) return; + + if(levelId !== null) setSearchLevelId(levelId); + searchMembers(0); + }, [ groupId, levelId ]); + + useEffect(() => + { + searchMembers(0); + }, [ searchLevelId ]); + + const previousPage = useCallback(() => + { + searchMembers(pageData.pageIndex - 1); + }, [ searchMembers, pageData ]); + + const nextPage = useCallback(() => + { + searchMembers(pageData.pageIndex + 1); + }, [ searchMembers, pageData ]); + + const onKeyDown = useCallback((event: KeyboardEvent) => + { + if(event.key !== 'Enter') return; + + searchMembers(pageData.pageIndex); + }, [ searchMembers, pageData ]); + + const getRankDescription = useCallback((member: GroupMemberParser) => + { + if(member.rank === GroupRank.OWNER) return 'group.members.owner'; + + if(pageData.admin) + { + if(member.rank === GroupRank.ADMIN) return 'group.members.removerights'; + + if(member.rank === GroupRank.MEMBER) return 'group.members.giverights'; + } + + return ''; + }, [ pageData ]); + + if(!pageData) return null; + + return ( + + + +
+
+ +
+
+ setSearchQuery(e.target.value) } onBlur={ () => searchMembers(pageData.pageIndex) } onKeyDown={ onKeyDown } /> + +
+
+
+ { pageData.result.map((member, index) => + { + return ( +
+
+
+ +
+
+
{ member.name }
+
{ LocalizeText('group.members.since', ['date'], [member.joinedAt]) }
+
+
+
+ +
+ { member.rank !== GroupRank.OWNER && pageData.admin && member.id !== GetSessionDataManager().userId &&
+ +
} +
+
+
+ ); + }) } +
+
+
+ +
+
{ LocalizeText('group.members.pageinfo', ['amount', 'page', 'totalPages'], [pageData.totalMembersCount.toString(), (pageData.pageIndex + 1).toString(), totalPages.toString()]) }
+
+ +
+
+
+
+ ); +}; diff --git a/src/views/groups/views/members/GroupMembersView.types.ts b/src/views/groups/views/members/GroupMembersView.types.ts new file mode 100644 index 00000000..44d151f6 --- /dev/null +++ b/src/views/groups/views/members/GroupMembersView.types.ts @@ -0,0 +1,6 @@ +export interface GroupMembersViewProps +{ + groupId: number; + levelId: number; + onClose: () => void; +}