diff --git a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js index 92acf9023f..bbad7a8401 100644 --- a/test/api/v3/integration/groups/POST-groups_groupId_join.test.js +++ b/test/api/v3/integration/groups/POST-groups_groupId_join.test.js @@ -258,47 +258,6 @@ describe('POST /group/:groupId/join', () => { await expect(user.get('/user')).to.eventually.have.nested.property('items.quests.basilist', 2); }); - it('deletes previous party where the user was the only member', async () => { - const userToInvite = await generateUser(); - const oldParty = await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); - await user.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - await userToInvite.post(`/groups/${party._id}/join`); - - await expect(user.get('/user')).to.eventually.have.nested.property('party._id', party._id); - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(false); - }); - - it('does not allow user to leave a party if a quest was active and they were the only member', async () => { - const userToInvite = await generateUser(); - const oldParty = await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await userToInvite.update({ - [`items.quests.${PET_QUEST}`]: 1, - }); - await userToInvite.post(`/groups/${oldParty._id}/quests/invite/${PET_QUEST}`); - - await expect(checkExistence('groups', oldParty._id)).to.eventually.equal(true); - await user.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - - await expect(userToInvite.post(`/groups/${party._id}/join`)).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('messageCannotLeaveWhileQuesting'), - }); - }); - it('invites joining member to active quest', async () => { await user.update({ [`items.quests.${PET_QUEST}`]: 1, diff --git a/test/api/v3/integration/groups/POST-groups_invite.test.js b/test/api/v3/integration/groups/POST-groups_invite.test.js index 02f8c5e20c..58a478be00 100644 --- a/test/api/v3/integration/groups/POST-groups_invite.test.js +++ b/test/api/v3/integration/groups/POST-groups_invite.test.js @@ -1,6 +1,7 @@ import { v4 as generateUUID } from 'uuid'; import nconf from 'nconf'; import { + createAndPopulateGroup, generateUser, generateGroup, translate as t, @@ -581,20 +582,7 @@ describe('Post /groups/:groupId/invite', () => { }); }); - it('allow inviting a user to a party if they are partying solo', async () => { - const userToInvite = await generateUser(); - await userToInvite.post('/groups', { // add user to a party - name: 'Another Test Party', - type: 'party', - }); - - await inviter.post(`/groups/${party._id}/invite`, { - uuids: [userToInvite._id], - }); - expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id); - }); - - it('allow inviting a user to 2 different parties', async () => { + it('allows inviting a user to 2 different parties', async () => { // Create another inviter const inviter2 = await generateUser(); @@ -635,29 +623,47 @@ describe('Post /groups/:groupId/invite', () => { }); expect((await userToInvite.get('/user')).invitations.parties[0].id).to.equal(party._id); }); + }); + + describe('party size limits', () => { + let party, partyLeader; + + beforeEach(async () => { + group = await createAndPopulateGroup({ + groupDetails: { + name: 'Test Party', + type: 'party', + privacy: 'private', + }, + // Generate party with 20 members + members: PARTY_LIMIT_MEMBERS - 10, + }); + party = group.group; + partyLeader = group.groupLeader; + }); it('allows 30 members in a party', async () => { const invitesToGenerate = []; - // Generate 29 users to invite (29 + leader = 30 members) - for (let i = 0; i < PARTY_LIMIT_MEMBERS - 1; i += 1) { + // Generate 10 new invites + for (let i = 1; i < 10; i += 1) { invitesToGenerate.push(generateUser()); } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - expect(await inviter.post(`/groups/${party._id}/invite`, { + expect(await partyLeader.post(`/groups/${party._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })).to.be.an('array'); }).timeout(10000); - it('does not allow 30+ members in a party', async () => { + it('does not allow >30 members in a party', async () => { const invitesToGenerate = []; - // Generate 30 users to invite (30 + leader = 31 members) - for (let i = 0; i < PARTY_LIMIT_MEMBERS; i += 1) { + // Generate 11 invites + for (let i = 1; i < 11; i += 1) { invitesToGenerate.push(generateUser()); } const generatedInvites = await Promise.all(invitesToGenerate); // Invite users - await expect(inviter.post(`/groups/${party._id}/invite`, { + await expect(partyLeader.post(`/groups/${party._id}/invite`, { uuids: generatedInvites.map(invite => invite._id), })) .to.eventually.be.rejected.and.eql({ diff --git a/website/client/src/assets/scss/icon.scss b/website/client/src/assets/scss/icon.scss index 92930f1124..3ab036333a 100644 --- a/website/client/src/assets/scss/icon.scss +++ b/website/client/src/assets/scss/icon.scss @@ -24,9 +24,9 @@ } } -.icon-16 { - width: 16px; - height: 16px; +.icon-10 { + width: 10px; + height: 10px; } .icon-12 { @@ -34,21 +34,26 @@ height: 12px; } +.icon-16 { + width: 16px; + height: 16px; +} + .icon-24 { width: 24px; height: 24px; } +.icon-32 { + width: 32px; + height: 32px; +} + .icon-48 { width: 48px; height: 48px; } -.icon-10 { - width: 10px; - height: 10px; -} - .inline { display: inline-block; } diff --git a/website/client/src/assets/svg/sync-2.svg b/website/client/src/assets/svg/sync-2.svg new file mode 100644 index 0000000000..61a77879df --- /dev/null +++ b/website/client/src/assets/svg/sync-2.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/website/client/src/components/groups/createPartyModal.vue b/website/client/src/components/groups/createPartyModal.vue index 8b00d4e06c..17c0184331 100644 --- a/website/client/src/components/groups/createPartyModal.vue +++ b/website/client/src/components/groups/createPartyModal.vue @@ -11,9 +11,12 @@
-

+

{{ $t('playInPartyTitle') }} -

+

+
-
+
-

- {{ $t('wantToJoinPartyTitle') }} -

-

-
-
- -
-
- @ -
- {{ user.auth.local.username }} -
-
-
- {{ $t('copy') }} -
-
+ {{ $t('wantToJoinPartyTitle') }} + +

+

+
+
+ {{ $t('currentlyLookingForParty') }} +
+
+
+
- @@ -107,15 +134,27 @@ cursor: pointer; } + .green-bar { + height: 32px; + font-size: 14px; + font-weight: bold; + line-height: 1.71; + text-align: center; + color: $green-1; + background-color: $green-100; + border-radius: 2px; + padding: 4px 0px 4px 0px; + } + .grey-row { background-color: $gray-700; color: #4e4a57; padding: 2em; - border-radius: 0px 0px 2px 2px; + border-radius: 0px 0px 8px 8px; } - h2 { - color: $gray-100; + h1 { + color: $purple-300; } .header-wrap { @@ -132,10 +171,6 @@ border-radius: 2px 2px 0 0; image-rendering: optimizequality; } - - h2 { - color: $purple-200; - } } .heading { @@ -182,6 +217,21 @@ margin: 0.75rem auto 0.75rem 0.25rem; } + p { + line-height: 1.71; + } + + .red-link { + cursor: pointer; + font-size: 14px; + line-height: 1.71; + text-align: center; + color: $maroon-50; + &:hover { + text-decoration: underline; + } + } + .small { color: $gray-200; margin: auto 0.5rem auto 0.25rem; @@ -192,21 +242,35 @@ import { mapState } from '@/libs/store'; import * as Analytics from '@/libs/analytics'; import notifications from '@/mixins/notifications'; +import closeX from '../ui/closeX'; import copyIcon from '@/assets/svg/copy.svg'; export default { + components: { + closeX, + }, mixins: [notifications], data () { return { icons: Object.freeze({ copy: copyIcon, }), + seeking: false, }; }, computed: { ...mapState({ user: 'user.data' }), }, + mounted () { + this.seeking = Boolean(this.user.party.seeking); + Analytics.track({ + eventName: 'Start a Party button', + eventAction: 'Start a Party button', + eventCategory: 'behavior', + hitType: 'event', + }, { trackOnClient: true }); + }, methods: { async createParty () { const group = { @@ -223,7 +287,10 @@ export default { }); this.$root.$emit('bv::hide::modal', 'create-party-modal'); - this.$router.push('/party'); + await this.$router.push('/party'); + }, + close () { + this.$root.$emit('bv::hide::modal', 'create-party-modal'); }, copyUsername () { if (navigator.clipboard) { @@ -238,6 +305,12 @@ export default { } this.text(this.$t('usernameCopied')); }, + seekParty () { + this.$store.dispatch('user:set', { + 'party.seeking': !this.user.party.seeking ? new Date() : null, + }); + this.seeking = !this.seeking; + }, }, }; diff --git a/website/client/src/components/groups/group.vue b/website/client/src/components/groups/group.vue index 85dbf296af..a238ef5664 100644 --- a/website/client/src/components/groups/group.vue +++ b/website/client/src/components/groups/group.vue @@ -542,7 +542,8 @@ export default { await this.$store.dispatch('guilds:leave', data); if (this.isParty) { - this.$router.push({ name: 'tasks' }); + await this.$router.push({ name: 'tasks' }); + window.location.reload(true); } }, upgradeGroup () { diff --git a/website/client/src/components/groups/lookingForParty.vue b/website/client/src/components/groups/lookingForParty.vue new file mode 100644 index 0000000000..eda06f5759 --- /dev/null +++ b/website/client/src/components/groups/lookingForParty.vue @@ -0,0 +1,351 @@ + + + + + diff --git a/website/client/src/components/groups/rightSidebar.vue b/website/client/src/components/groups/rightSidebar.vue index f5eafdcaf6..cab95cea2e 100644 --- a/website/client/src/components/groups/rightSidebar.vue +++ b/website/client/src/components/groups/rightSidebar.vue @@ -2,7 +2,7 @@