diff --git a/website/client/components/challenges/challengeDetail.vue b/website/client/components/challenges/challengeDetail.vue index efa05268ae..19ddd1f183 100644 --- a/website/client/components/challenges/challengeDetail.vue +++ b/website/client/components/challenges/challengeDetail.vue @@ -281,7 +281,7 @@ export default { }, async loadChallenge () { this.challenge = await this.$store.dispatch('challenges:getChallenge', {challengeId: this.searchId}); - this.members = await this.$store.dispatch('members:getChallengeMembers', {challengeId: this.searchId}); + this.members = await this.loadMembers({ challengeId: this.searchId, includeAllPublicFields: true }); let tasks = await this.$store.dispatch('tasks:getChallengeTasks', {challengeId: this.searchId}); this.tasksByType = { habit: [], @@ -293,6 +293,21 @@ export default { this.tasksByType[task.type].push(task); }); }, + + /** + * Method for loading members of a group, with optional parameters for + * modifying requests. + * + * @param {Object} payload Used for modifying requests for members + */ + loadMembers (payload = null) { + // Remove unnecessary data + if (payload && payload.groupId) { + delete payload.groupId; + } + return this.$store.dispatch('members:getChallengeMembers', payload); + }, + editTask (task) { this.taskFormPurpose = 'edit'; this.editingTask = cloneDeep(task); @@ -334,7 +349,9 @@ export default { this.$store.state.memberModalOptions.challengeId = this.challenge._id; this.$store.state.memberModalOptions.groupId = 'challenge'; // @TODO: change these terrible settings this.$store.state.memberModalOptions.group = this.group; + this.$store.state.memberModalOptions.memberCount = this.challenge.memberCount; this.$store.state.memberModalOptions.viewingMembers = this.members; + this.$store.state.memberModalOptions.fetchMoreMembers = this.loadMembers; this.$root.$emit('bv::show::modal', 'members-modal'); }, async joinChallenge () { diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue index 03df579d1d..d2e7b1468a 100644 --- a/website/client/components/groups/group.vue +++ b/website/client/components/groups/group.vue @@ -375,6 +375,7 @@ export default { silverGuildBadgeIcon, bronzeGuildBadgeIcon, }), + members: [], selectedQuest: {}, sections: { quest: true, @@ -456,14 +457,43 @@ export default { }, }, methods: { - load () { - this.fetchGuild(); - + acceptCommunityGuidelines () { + this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true}); + }, + async load () { + if (this.isParty) { + this.searchId = 'party'; + // @TODO: Set up from old client. Decide what we need and what we don't + // Check Desktop notifs + // Load invites + } + await this.fetchGuild(); + // Fetch group members on load + this.members = await this.loadMembers({ + groupId: this.group._id, + includeAllPublicFields: true, + }); this.$root.$on('updatedGroup', group => { let updatedGroup = extend(this.group, group); this.$set(this.group, updatedGroup); }); }, + + /** + * Method for loading members of a group, with optional parameters for + * modifying requests. + * + * @param {Object} payload Used for modifying requests for members + */ + loadMembers (payload = null) { + // Remove unnecessary data + if (payload && payload.challengeId) { + delete payload.challengeId; + } + + return this.$store.dispatch('members:getGroupMembers', payload); + }, + // @TODO: abstract autocomplete // https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a getCoord (e, text) { @@ -500,6 +530,9 @@ export default { showMemberModal () { this.$store.state.memberModalOptions.groupId = this.group._id; this.$store.state.memberModalOptions.group = this.group; + this.$store.state.memberModalOptions.memberCount = this.group.memberCount; + this.$store.state.memberModalOptions.viewingMembers = this.members; + this.$store.state.memberModalOptions.fetchMoreMembers = this.loadMembers; this.$root.$emit('bv::show::modal', 'members-modal'); }, async sendMessage () { diff --git a/website/client/components/groups/membersModal.vue b/website/client/components/groups/membersModal.vue index 1b6c0cb1b7..eb0199fc03 100644 --- a/website/client/components/groups/membersModal.vue +++ b/website/client/components/groups/membersModal.vue @@ -48,7 +48,7 @@ div span.dropdown-icon-item .svg-icon.inline(v-html="icons.removeIcon") span.text {{$t('removeManager2')}} - .row(v-if='groupId === "challenge"') + .row(v-if='isLoadMoreAvailable') .col-12.text-center button.btn.btn-secondary(@click='loadMoreMembers()') {{ $t('loadMore') }} .row.gradient(v-if='members.length > 3') @@ -296,6 +296,11 @@ export default { isAdmin () { return Boolean(this.user.contributor.admin); }, + isLoadMoreAvailable () { + // Only available if the current length of `members` is less than the + // total size of the Group/Challenge + return this.members.length < this.$store.state.memberModalOptions.memberCount; + }, groupIsSubscribed () { return this.group.purchased.active; }, @@ -311,16 +316,6 @@ export default { sortedMembers () { let sortedMembers = this.members; - if (this.searchTerm) { - sortedMembers = sortedMembers.filter(member => { - return ( - member.profile.name - .toLowerCase() - .indexOf(this.searchTerm.toLowerCase()) !== -1 - ); - }); - } - if (!isEmpty(this.sortOption)) { // Use the memberlist filtered by searchTerm sortedMembers = orderBy(sortedMembers, [this.sortOption.param], [this.sortOption.order]); @@ -337,6 +332,15 @@ export default { group () { this.getMembers(); }, + // Watches `searchTerm` and if present, performs a `searchMembers` action + // and usual `getMembers` otherwise + searchTerm () { + if (this.searchTerm) { + this.searchMembers(this.searchTerm); + } else { + this.getMembers(); + } + }, }, methods: { sendMessage (member) { @@ -345,22 +349,24 @@ export default { userName: member.profile.name, }); }, + async searchMembers (searchTerm = '') { + this.members = await this.$store.state.memberModalOptions.fetchMoreMembers({ + challengeId: this.challengeId, + groupId: this.groupId, + searchTerm, + includeAllPublicFields: true, + }); + }, async getMembers () { let groupId = this.groupId; - if (groupId && groupId !== 'challenge') { - let members = await this.$store.dispatch('members:getGroupMembers', { - groupId, - includeAllPublicFields: true, - }); - this.members = members; + if (groupId && groupId !== 'challenge') { let invites = await this.$store.dispatch('members:getGroupInvites', { groupId, includeAllPublicFields: true, }); this.invites = invites; } - if (this.$store.state.memberModalOptions.viewingMembers.length > 0) { this.members = this.$store.state.memberModalOptions.viewingMembers; } @@ -425,9 +431,11 @@ export default { const lastMember = this.members[this.members.length - 1]; if (!lastMember) return; - let newMembers = await this.$store.dispatch('members:getChallengeMembers', { - challengeId: this.challengeId, - lastMemberId: lastMember._id, + let newMembers = await this.$store.state.memberModalOptions.fetchMoreMembers({ + challengeId: this.challengeId, + groupId: this.groupId, + lastMemberId: lastMember._id, + includeAllPublicFields: true, }); this.members = this.members.concat(newMembers); diff --git a/website/client/components/header/index.vue b/website/client/components/header/index.vue index 3f3602b862..e679b2c5cf 100644 --- a/website/client/components/header/index.vue +++ b/website/client/components/header/index.vue @@ -160,9 +160,10 @@ export default { }, openPartyModal () { if (this.user.party._id) { + // Set the party details for the members-modal component this.$store.state.memberModalOptions.groupId = this.user.party._id; - // @TODO: do we need to fetch party? - // this.$store.state.memberModalOptions.group = this.$store.state.party; + this.$store.state.memberModalOptions.viewingMembers = this.partyMembers; + this.$store.state.memberModalOptions.group = this.user.party; this.$root.$emit('bv::show::modal', 'members-modal'); return; } @@ -174,9 +175,10 @@ export default { } }, }, - async created () { + created () { if (this.user.party && this.user.party._id) { - await this.getPartyMembers(true); + this.$store.state.memberModalOptions.groupId = this.user.party._id; + this.getPartyMembers(); } }, }; diff --git a/website/client/store/actions/members.js b/website/client/store/actions/members.js index 864b6d4d5b..aeff9ad9f6 100644 --- a/website/client/store/actions/members.js +++ b/website/client/store/actions/members.js @@ -7,15 +7,21 @@ let apiV3Prefix = '/api/v3'; export async function getGroupMembers (store, payload) { let url = `${apiV3Prefix}/groups/${payload.groupId}/members`; + const params = {}; + if (payload.includeAllPublicFields) { - url += '?includeAllPublicFields=true'; + params.includeAllPublicFields = true; + } + + if (payload.lastMemberId) { + params.lastId = payload.lastMemberId; } if (payload.searchTerm) { - url += `?search=${payload.searchTerm}`; + params.search = payload.searchTerm; } - let response = await axios.get(url); + let response = await axios.get(url, { params }); return response.data.data; } @@ -35,17 +41,23 @@ export async function getGroupInvites (store, payload) { } export async function getChallengeMembers (store, payload) { - let url = `${apiV3Prefix}/challenges/${payload.challengeId}/members?includeAllPublicFields=true`; + let url = `${apiV3Prefix}/challenges/${payload.challengeId}/members`; + + const params = {}; + + if (payload.includeAllPublicFields) { + params.includeAllPublicFields = true; + } if (payload.lastMemberId) { - url += `&lastId=${payload.lastMemberId}`; + params.lastId = payload.lastMemberId; } if (payload.searchTerm) { - url += `&search=${payload.searchTerm}`; + params.search = payload.searchTerm; } - let response = await axios.get(url); + let response = await axios.get(url, { params }); return response.data.data; } diff --git a/website/server/controllers/api-v3/members.js b/website/server/controllers/api-v3/members.js index ae9201831f..b9e390891e 100644 --- a/website/server/controllers/api-v3/members.js +++ b/website/server/controllers/api-v3/members.js @@ -248,7 +248,8 @@ function _getMembersForItem (type) { } if (req.query.search) { - query['profile.name'] = {$regex: req.query.search}; + // Creates a RegExp expression when querying for profile.name + query['profile.name'] = { $regex: new RegExp(req.query.search, 'i') }; } } else if (type === 'group-invites') { if (group.type === 'guild') { // eslint-disable-line no-lonely-if