diff --git a/website/client/src/app.vue b/website/client/src/app.vue index a49c648541..e454bf53e2 100644 --- a/website/client/src/app.vue +++ b/website/client/src/app.vue @@ -229,6 +229,11 @@ export default { } return Promise.resolve(error); } + if (error.response.status === 404 + && error.response.config.method === 'get' + && error.response.config.url.indexOf('/api/v4/groups/party') !== -1) { + return Promise.reject(error); + } } const errorData = error.response.data; diff --git a/website/client/src/components/group-plans/groupPlanSelectionModal.vue b/website/client/src/components/group-plans/groupPlanSelectionModal.vue new file mode 100644 index 0000000000..118b80bdbc --- /dev/null +++ b/website/client/src/components/group-plans/groupPlanSelectionModal.vue @@ -0,0 +1,577 @@ + + + + + + + diff --git a/website/client/src/components/static/groupPlans.vue b/website/client/src/components/static/groupPlans.vue index 2bcedada89..7bd2d16b9f 100644 --- a/website/client/src/components/static/groupPlans.vue +++ b/website/client/src/components/static/groupPlans.vue @@ -1,5 +1,6 @@ + + + + diff --git a/website/client/src/pages/user-main.vue b/website/client/src/pages/user-main.vue index aa223b7444..8df3031c76 100644 --- a/website/client/src/pages/user-main.vue +++ b/website/client/src/pages/user-main.vue @@ -295,6 +295,10 @@ export default { appState = JSON.parse(appState); if (appState.paymentCompleted) { removeLocalSetting(CONSTANTS.savedAppStateValues.SAVED_APP_STATE); + if (appState.paymentType === 'groupPlan') { + this.$store.state.upgradingGroup = {}; + this.$store.dispatch('guilds:getGroupPlans', true); + } this.$root.$emit('habitica:payment-success', appState); } } diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index 9a08a33a12..5cacc54956 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -428,5 +428,17 @@ "interestedLearningMore": "Interested in Learning More?", "checkGroupPlanFAQ": "Check out the Group Plans FAQ to learn how to get the most out of your shared task experience.", "groupPlanBillingFYI": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You can cancel from the Group Billing tab of your Group Plan. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle.", - "groupPlanBillingFYIShort": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle." + "groupPlanBillingFYIShort": "Group Plan subscriptions automatically renew unless you cancel at least 24 hours before the end of your current period. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you'll see an additional prorated charge for their benefits at your next billing cycle.", + "chooseAnOption": "Choose an Option", + "upgradeExistingGroup": "Upgrade an Existing Group", + "createNewGroup": "Create a New Group", + "yourParty": "Your Party", + "previouslyUpgradedGroup": "Previously upgraded Group", + "inviteOthersForAdditional": "Invite others to your Group for an additional", + "perMember": "per member", + "additionalMembersProrated": "Additional members invited during the month will be added to the next billing cycle's total as a pro-rated charge.", + "oneMember": "1 member", + "membersCount": "<%= count %> members", + "pendingCount": "(<%= count %> pending)", + "upgradeCancelsPendingInvites": "Upgrading your Party will cancel all pending invites" } diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 159adb8466..93c7eb04da 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -334,9 +334,13 @@ api.getGroups = { throw new BadRequest(apiError('guildsOnlyPaginate')); } - const groupFields = basicGroupFields.concat(' description memberCount balance leaderOnly'); + let groupFields = basicGroupFields.concat(' description memberCount balance leaderOnly'); const sort = '-memberCount'; + if (req.query.includeExpiredPlans === 'true') { + groupFields = groupFields.concat(' purchased'); + } + const filters = {}; if (req.query.categories) { const categorySlugs = req.query.categories.split(','); @@ -371,6 +375,10 @@ api.getGroups = { filters.$or.push({ description: searchQuery }); } + if (req.query.includeExpiredPlans === 'true') { + filters.includeExpiredPlans = true; + } + const results = await Group.getGroups({ user, types, diff --git a/website/server/libs/inbox/index.js b/website/server/libs/inbox/index.js index af0d7ae07d..c28400c583 100644 --- a/website/server/libs/inbox/index.js +++ b/website/server/libs/inbox/index.js @@ -1,6 +1,6 @@ import { mapInboxMessage, inboxModel } from '../../models/message'; import { getUserInfo, sendTxn as sendTxnEmail } from '../email'; // eslint-disable-line import/no-cycle -import { sendNotification as sendPushNotification } from '../pushNotifications'; +import { sendNotification as sendPushNotification } from '../pushNotifications'; // eslint-disable-line import/no-cycle export async function sentMessage (sender, receiver, message, translate) { const fakeSending = sender.flags.chatShadowMuted; diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index 93ff285b41..9a0dd3126c 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -153,6 +153,10 @@ async function prepareSubscriptionValues (data) { groupId = group._id; recipient.purchased.plan.quantity = data.sub.quantity; + if (group.type === 'party') { + await group.removeGroupInvitations(); + } + await addSubscriptionToGroupUsers(group); } diff --git a/website/server/models/group.js b/website/server/models/group.js index 36c678c2cd..f22d378652 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -156,7 +156,16 @@ schema.plugin(baseModel, { noSet: ['_id', 'balance', 'quest', 'memberCount', 'chat', 'bannedWordsAllowed', 'challengeCount', 'tasksOrder', 'purchased', 'managers'], private: ['purchased.plan'], toJSONTransform (plainObj, originalDoc) { - if (plainObj.purchased) plainObj.purchased.active = originalDoc.hasActiveGroupPlan(); + if (plainObj.purchased) { + plainObj.purchased.active = originalDoc.hasActiveGroupPlan(); + const plan = originalDoc.purchased && originalDoc.purchased.plan; + if (plan && plan.customerId) { + plainObj.purchased.wasUpgraded = true; + if (plan.dateTerminated) { + plainObj.purchased.dateTerminated = plan.dateTerminated; + } + } + } }, }); @@ -309,13 +318,16 @@ schema.statics.getGroups = async function getGroups (options = {}) { privacy: 'private', _id: { $in: user.guilds }, 'purchased.plan.customerId': { $exists: true }, - $or: [ + }; + if (!filters.includeExpiredPlans) { + query.$or = [ { 'purchased.plan.dateTerminated': null }, { 'purchased.plan.dateTerminated': { $exists: false } }, { 'purchased.plan.dateTerminated': { $gt: new Date() } }, - ], - }; - _.assign(query, filters); + ]; + } + const filtersWithoutCustom = _.omit(filters, ['includeExpiredPlans']); + _.assign(query, filtersWithoutCustom); const privateGuildsQuery = this.find(query).select(groupFields); if (populateLeader === true) privateGuildsQuery.populate('leader', nameFields); privateGuildsQuery.sort(sort);