From ba22c18cd9c59f3bd53785378fcec4cba899aefd Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Tue, 26 Jul 2022 16:59:53 -0400 Subject: [PATCH 001/134] separating group plan creation modal out from groupPlan.vue --- .../groups/groupPlanCreationModal.vue | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 website/client/src/components/groups/groupPlanCreationModal.vue diff --git a/website/client/src/components/groups/groupPlanCreationModal.vue b/website/client/src/components/groups/groupPlanCreationModal.vue new file mode 100644 index 0000000000..2a71afd919 --- /dev/null +++ b/website/client/src/components/groups/groupPlanCreationModal.vue @@ -0,0 +1,230 @@ + + + + + From 8ecd152b411e9126b1c24b9da941e07d3b8508ba Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Wed, 27 Jul 2022 18:25:56 -0400 Subject: [PATCH 002/134] moved modal file from /groups into /group-plans --- .../group-plans/groupPlanCreationModal.vue | 230 ++++++++++++++++++ 1 file changed, 230 insertions(+) create mode 100644 website/client/src/components/group-plans/groupPlanCreationModal.vue diff --git a/website/client/src/components/group-plans/groupPlanCreationModal.vue b/website/client/src/components/group-plans/groupPlanCreationModal.vue new file mode 100644 index 0000000000..2a71afd919 --- /dev/null +++ b/website/client/src/components/group-plans/groupPlanCreationModal.vue @@ -0,0 +1,230 @@ + + + + + From 9bee9d0a069f456609a641ef301a654a9b816501 Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Mon, 1 Aug 2022 17:11:15 -0400 Subject: [PATCH 003/134] working on editing code so the modal emits from group-plans instead of from within the file --- website/client/src/components/groups/groupPlan.vue | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/client/src/components/groups/groupPlan.vue b/website/client/src/components/groups/groupPlan.vue index 6fc137a31f..4c54990d6b 100644 --- a/website/client/src/components/groups/groupPlan.vue +++ b/website/client/src/components/groups/groupPlan.vue @@ -495,6 +495,13 @@ export default { this.changePage(this.PAGES.CREATE_GROUP); this.$root.$emit('bv::show::modal', 'group-plan-modal'); }, + // launchModal () { + // this.$root.$on('group-plan-modal') { + // console.log('hello i am a modal'); + // this.activePage = page; + // this.$root.$emit('bv::show::modal', 'group-plan-modal'); + // }; + // }, changePage (page) { this.activePage = page; window.scrollTo(0, 0); From 999071a15c91dcf0c953c4380f9161c5c56f3303 Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Tue, 2 Aug 2022 16:05:41 -0400 Subject: [PATCH 004/134] still trying to get modal to work --- .../group-plans/groupPlanCreationModal.vue | 192 +++++++++--------- .../src/components/groups/groupPlan.vue | 111 +--------- 2 files changed, 97 insertions(+), 206 deletions(-) diff --git a/website/client/src/components/group-plans/groupPlanCreationModal.vue b/website/client/src/components/group-plans/groupPlanCreationModal.vue index 2a71afd919..c4694bcd2c 100644 --- a/website/client/src/components/group-plans/groupPlanCreationModal.vue +++ b/website/client/src/components/group-plans/groupPlanCreationModal.vue @@ -1,101 +1,100 @@ + diff --git a/website/client/src/components/group-plans/selectGroupTranslatedArray.vue b/website/client/src/components/group-plans/selectGroupTranslatedArray.vue new file mode 100644 index 0000000000..0b201f3d11 --- /dev/null +++ b/website/client/src/components/group-plans/selectGroupTranslatedArray.vue @@ -0,0 +1,86 @@ + + + + + + + diff --git a/website/client/src/components/groups/groupPlan.vue b/website/client/src/components/groups/groupPlan.vue index 83f4a5adf1..4bc9bb2209 100644 --- a/website/client/src/components/groups/groupPlan.vue +++ b/website/client/src/components/groups/groupPlan.vue @@ -1,7 +1,7 @@ @@ -244,7 +253,7 @@ export default { amazonPayments: {}, PAGES: { CREATE_GROUP: 'create-group', - UPGRADE_GROUP: 'upgrade-group', + // UPGRADE_GROUP: 'upgrade-group', PAY: 'pay', }, PAYMENTS: { @@ -262,18 +271,15 @@ export default { demographics: null, }, activePage: 'create-group', - type: 'guild', // Guild or Party @TODO enum this + type: 'guild', }; }, computed: { ...mapState({ user: 'user.data' }), newGroupIsReady () { - return Boolean(this.newGroup.name); + return Boolean(this.newGroup.name) && Boolean(this.newGroup.demographics); }, }, - mounted () { - console.log('i am mounted'); - }, methods: { close () { this.$root.$emit('bv::hide::modal', 'create-group'); @@ -282,9 +288,8 @@ export default { this.activePage = page; }, createGroup () { - console.log('i am giving habitica money now'); - this.changePage(this.PAGES.PAY); console.log(this.newGroup); + this.changePage(this.PAGES.PAY); }, pay (paymentMethod) { const subscriptionKey = 'group_monthly'; // @TODO: Get from content API? @@ -313,9 +318,21 @@ export default { return null; }, + // need to figure out where/how to create the event in amplitude + // right now being sent to console in success() + sendAnalytics () { + return this.newGroup.demographics; + }, onHide () { this.sendingInProgress = false; }, + // temporary function to go with temporary button + success () { + console.log(this.sendAnalytics()); + this.sendAnalytics(); + this.$root.$emit('bv::hide::modal', 'create-group'); + this.$root.$emit('bv::show::modal', 'payments-success-modal'); + }, }, }; diff --git a/website/client/src/components/groups/groupPlan.vue b/website/client/src/components/groups/groupPlan.vue index a96766c402..a4667cc7a2 100644 --- a/website/client/src/components/groups/groupPlan.vue +++ b/website/client/src/components/groups/groupPlan.vue @@ -52,6 +52,7 @@ +
+
Date: Fri, 12 Aug 2022 16:44:58 -0400 Subject: [PATCH 016/134] update: style changes, add upgraded group demographics to success modal, add description counter --- website/client/src/assets/scss/form.scss | 20 +++++---- .../group-plans/groupPlanCreationModal.vue | 23 ++++++++++ .../src/components/payments/successModal.vue | 45 +++++++++++++++++++ website/common/locales/en/groups.json | 2 +- 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/website/client/src/assets/scss/form.scss b/website/client/src/assets/scss/form.scss index 8e21bc06a4..b161472271 100644 --- a/website/client/src/assets/scss/form.scss +++ b/website/client/src/assets/scss/form.scss @@ -180,8 +180,8 @@ input, textarea, input.form-control, textarea.form-control { } // used in checkboxes and radios -$bg-focused-active-control: #4f2993; -$bg-disabled-control: #34303a; +$bg-focused-active-control: $purple-200; +$bg-disabled-control: $gray-10; // custom control .custom-control { @@ -231,17 +231,21 @@ $bg-disabled-control: #34303a; background-color: inherit; } - &:focus:not(:checked):not(:disabled)~.custom-control-label::before, &:active:not(:checked):not(:disabled)~.custom-control-label::before { - box-shadow: 0 0 0 6px rgba($bg-focused-active-control, 0.1); + &:focus:not(:checked):not(:disabled)~.custom-control-label::before, + &:active:not(:checked):not(:disabled)~.custom-control-label::before { + border: 2px solid $gray-300; + box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5); } - &:focus:checked:not(:disabled)~.custom-control-label::before, &:active:checked:not(:disabled)~.custom-control-label::before { - box-shadow: 0 0 0 6px rgba($bg-focused-active-control, 0.1); - border-color: $purple-400; + &:focus:checked:not(:disabled)~.custom-control-label::before, + &:active:checked:not(:disabled)~.custom-control-label::before { + box-shadow: 0 0 0 2px rgba(146, 92, 243, 0.5); + border-color: 2 px solid $purple-400; background-color: $purple-400; } - &:focus:disabled~.custom-control-label::before, &:active:disabled~.custom-control-label::before { + &:focus:disabled~.custom-control-label::before, + &:active:disabled~.custom-control-label::before { box-shadow: 0 0 0 6px rgba($bg-disabled-control, 0.1); } diff --git a/website/client/src/components/group-plans/groupPlanCreationModal.vue b/website/client/src/components/group-plans/groupPlanCreationModal.vue index cf837b13e5..5c2158e579 100644 --- a/website/client/src/components/group-plans/groupPlanCreationModal.vue +++ b/website/client/src/components/group-plans/groupPlanCreationModal.vue @@ -50,13 +50,18 @@ +
+ {{ $t('charactersRemaining', {characters: charactersRemaining}) }} +
@@ -153,10 +158,22 @@ height: 32px; } + .description-label { + margin-bottom: -24px; + } + .name-input, .description-input, .group-input { margin-top: -4px; } + .characters-remaining { + color: $gray-100; + font-size: 0.75rem; + line-height: 1.33; + text-align: right; + margin-bottom: 12px; + } + .description-input { height: 56px; } @@ -217,6 +234,7 @@ } .modal-content { width: 448px; + height: 436px; border-radius: 8px; box-shadow: 0 14px 28px 0 rgba(26, 24, 29, 0.24), 0 10px 10px 0 rgba(26, 24, 29, 0.28); } @@ -265,6 +283,7 @@ export default { type: 'guild', privacy: 'private', name: '', + description: '', leaderOnly: { challenges: false, }, @@ -279,6 +298,10 @@ export default { newGroupIsReady () { return Boolean(this.newGroup.name) && Boolean(this.newGroup.demographics); }, + charactersRemaining () { + const currentLength = this.newGroup.description ? this.newGroup.description.length : 0; + return 250 - currentLength; + }, }, methods: { close () { diff --git a/website/client/src/components/payments/successModal.vue b/website/client/src/components/payments/successModal.vue index 2e0af41cdb..bc96b35f03 100644 --- a/website/client/src/components/payments/successModal.vue +++ b/website/client/src/components/payments/successModal.vue @@ -85,6 +85,38 @@ v-once class="small-text auto-renew" >{{ $t('paymentAutoRenew') }} +
+ + +
+
+ +
- + --> @@ -234,7 +234,7 @@ } .modal-content { width: 448px; - height: 436px; + max-height: 436px; border-radius: 8px; box-shadow: 0 14px 28px 0 rgba(26, 24, 29, 0.24), 0 10px 10px 0 rgba(26, 24, 29, 0.28); } @@ -277,6 +277,7 @@ export default { PAYMENTS: { AMAZON: 'amazon', STRIPE: 'stripe', + // OTHER: 'tempcode', // TEMP CODE }, paymentMethod: '', newGroup: { @@ -316,9 +317,11 @@ export default { }, pay (paymentMethod) { const subscriptionKey = 'group_monthly'; // @TODO: Get from content API? + const demographicsKey = this.newGroup.demographics; const paymentData = { subscription: subscriptionKey, coupon: null, + demographics: demographicsKey, }; if (this.upgradingGroup && this.upgradingGroup._id) { @@ -339,6 +342,11 @@ export default { this.redirectToStripe(paymentData); } + // TEMP CODE + if (this.paymentMethod === this.PAYMENTS.OTHER) { + console.log(paymentData); + } + return null; }, // need to figure out where/how to create the event in amplitude @@ -350,12 +358,13 @@ export default { this.sendingInProgress = false; }, // temporary function to go with temporary button - success () { - console.log(this.sendAnalytics()); - this.sendAnalytics(); - this.$root.$emit('bv::hide::modal', 'create-group'); - this.$root.$emit('bv::show::modal', 'payments-success-modal'); - }, + // success () { + // this.pay(this.PAYMENTS.OTHER); + // console.log(this.sendAnalytics()); + // this.sendAnalytics(); + // this.$root.$emit('bv::hide::modal', 'create-group'); + // this.$root.$emit('bv::show::modal', 'payments-success-modal'); + // }, }, }; diff --git a/website/client/src/components/payments/successModal.vue b/website/client/src/components/payments/successModal.vue index bc96b35f03..805013db51 100644 --- a/website/client/src/components/payments/successModal.vue +++ b/website/client/src/components/payments/successModal.vue @@ -105,8 +105,8 @@ ]" class="group-input" :placeholder="'groupUseDefault'" - :value="newGroup.demographics" - @select="newGroup.demographics = $event" + :value="upgradedGroup.demographics" + @select="upgradedGroup.demographics = $event" />
@@ -119,7 +119,7 @@
+ From 5c13bf1980a415e2353f659c7738d93f80b6db2e Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Thu, 8 Sep 2022 17:34:36 -0400 Subject: [PATCH 032/134] update: add analytics to create and success modals --- .../group-plans/groupPlanCreationModal.vue | 34 ++++-------------- .../src/components/groups/groupPlan.vue | 6 ---- .../src/components/payments/successModal.vue | 35 ++++++++++++------- 3 files changed, 29 insertions(+), 46 deletions(-) diff --git a/website/client/src/components/group-plans/groupPlanCreationModal.vue b/website/client/src/components/group-plans/groupPlanCreationModal.vue index 5b63bafb93..ffc4466cbe 100644 --- a/website/client/src/components/group-plans/groupPlanCreationModal.vue +++ b/website/client/src/components/group-plans/groupPlanCreationModal.vue @@ -119,13 +119,6 @@ :amazon-data="pay(PAYMENTS.AMAZON)" /> - - @@ -258,6 +251,7 @@ import { mapState } from '@/libs/store'; import paymentsButtons from '@/components/payments/buttons/list'; import selectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray'; import lockableLabel from '@/components/tasks/modal-controls/lockableLabel'; +// import * as Analytics from '@/libs/analytics'; export default { components: { @@ -277,7 +271,6 @@ export default { PAYMENTS: { AMAZON: 'amazon', STRIPE: 'stripe', - // OTHER: 'tempcode', // TEMP CODE }, paymentMethod: '', newGroup: { @@ -312,7 +305,10 @@ export default { this.activePage = page; }, createGroup () { - console.log(this.newGroup); + // Analytics.track({ + // name: this.paymentData.demographics, + // }, + // console.log(this.paymentData.demographics)); this.changePage(this.PAGES.PAY); }, pay (paymentMethod) { @@ -342,29 +338,13 @@ export default { this.redirectToStripe(paymentData); } - // TEMP CODE - // if (this.paymentMethod === this.PAYMENTS.OTHER) { - // console.log(paymentData); - // } - return null; }, - // need to figure out where/how to create the event in amplitude - // right now being sent to console in success() - sendAnalytics () { - return this.newGroup.demographics; - }, + onHide () { this.sendingInProgress = false; }, - // temporary function to go with temporary button - // success () { - // this.pay(this.PAYMENTS.OTHER); - // console.log(this.sendAnalytics()); - // this.sendAnalytics(); - // this.$root.$emit('bv::hide::modal', 'create-group'); - // this.$root.$emit('bv::show::modal', 'payments-success-modal'); - // }, + }, }; diff --git a/website/client/src/components/groups/groupPlan.vue b/website/client/src/components/groups/groupPlan.vue index a4667cc7a2..0eb76a8ae4 100644 --- a/website/client/src/components/groups/groupPlan.vue +++ b/website/client/src/components/groups/groupPlan.vue @@ -400,14 +400,8 @@ export default { }, methods: { launchModal () { - console.log('i am creating a group'); - // this.changePage(this.PAGES.CREATE_GROUP); this.$root.$emit('bv::show::modal', 'create-group'); }, - // changePage (page) { - // this.activePage = 'page'; - // window.scrollTo(0, 0); - // }, createGroup () { this.changePage(this.PAGES.PAY); }, diff --git a/website/client/src/components/payments/successModal.vue b/website/client/src/components/payments/successModal.vue index 5827423ea5..577c06d619 100644 --- a/website/client/src/components/payments/successModal.vue +++ b/website/client/src/components/payments/successModal.vue @@ -2,8 +2,8 @@
@@ -136,15 +136,14 @@ >{{ $t('paymentAutoRenew') }} -
- -
+
@@ -325,6 +324,14 @@ export default { isFromBalance () { return this.paymentData.paymentType === 'gift-gems-balance'; }, + upgradedGroup () { + const upgradedGroup = (this.paymentData.paymentType !== 'groupPlan' || this.paymentData.newGroup); + const demographicsKey = upgradedGroup.demographics; + const groupPlanUpgraded = { + demographics: demographicsKey, + }; + return groupPlanUpgraded.demographics; + }, }, mounted () { this.$root.$on('habitica:payment-success', data => { @@ -341,10 +348,12 @@ export default { }, methods: { submit () { + Analytics.track({ + name: this.groupPlanUpgraded.demographics, + }, + console.log(Analytics.track)); this.paymentData = {}; this.$root.$emit('bv::hide::modal', 'payments-success-modal'); - Analytics.track({ }, - console.log(Analytics.track)); }, onwards () { this.paymentData = {}; From e6f903fd2eaea84ae780ebb94dded90faa3d7435 Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Fri, 9 Sep 2022 15:37:08 -0400 Subject: [PATCH 033/134] update: more analytics work --- .../group-plans/groupPlanCreationModal.vue | 17 +++++++---- .../src/components/payments/successModal.vue | 23 ++++++++++---- website/client/src/libs/analytics.js | 30 +++++++++++++++++++ 3 files changed, 59 insertions(+), 11 deletions(-) diff --git a/website/client/src/components/group-plans/groupPlanCreationModal.vue b/website/client/src/components/group-plans/groupPlanCreationModal.vue index ffc4466cbe..7b5546c78d 100644 --- a/website/client/src/components/group-plans/groupPlanCreationModal.vue +++ b/website/client/src/components/group-plans/groupPlanCreationModal.vue @@ -251,7 +251,7 @@ import { mapState } from '@/libs/store'; import paymentsButtons from '@/components/payments/buttons/list'; import selectTranslatedArray from '@/components/tasks/modal-controls/selectTranslatedArray'; import lockableLabel from '@/components/tasks/modal-controls/lockableLabel'; -// import * as Analytics from '@/libs/analytics'; +import * as Analytics from '@/libs/analytics'; export default { components: { @@ -282,6 +282,7 @@ export default { challenges: false, }, demographics: null, + user: '', }, activePage: 'create-group', type: 'guild', @@ -305,10 +306,6 @@ export default { this.activePage = page; }, createGroup () { - // Analytics.track({ - // name: this.paymentData.demographics, - // }, - // console.log(this.paymentData.demographics)); this.changePage(this.PAGES.PAY); }, pay (paymentMethod) { @@ -320,6 +317,16 @@ export default { demographics: demographicsKey, }; + Analytics.track({ + hitType: 'event', + eventName: 'group plan create', + eventAction: 'group plan create', + eventCategory: 'behavior', + createGroupName: this.newGroup.name, + demographicsCreated: this.newGroup.demographics, + typeCreated: this.newGroup.type, + }); + if (this.upgradingGroup && this.upgradingGroup._id) { paymentData.groupId = this.upgradingGroup._id; paymentData.group = this.upgradingGroup; diff --git a/website/client/src/components/payments/successModal.vue b/website/client/src/components/payments/successModal.vue index 577c06d619..b708cd3b42 100644 --- a/website/client/src/components/payments/successModal.vue +++ b/website/client/src/components/payments/successModal.vue @@ -290,6 +290,7 @@ diff --git a/website/server/controllers/api-v3/hall.js b/website/server/controllers/api-v3/hall.js index 15183c686f..456c62a396 100644 --- a/website/server/controllers/api-v3/hall.js +++ b/website/server/controllers/api-v3/hall.js @@ -289,6 +289,9 @@ api.updateHero = { if (updateData.purchased.plan.consecutive.gemCapExtra) { hero.purchased.plan.consecutive.gemCapExtra = updateData.purchased.plan.consecutive.gemCapExtra; // eslint-disable-line max-len } + if (updateData.purchased.plan.consecutive.count) { + hero.purchased.plan.consecutive.count = updateData.purchased.plan.consecutive.count; // eslint-disable-line max-len + } } } From 37e5d6b40aa9c75e192a6ad01a143be80b41007c Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Wed, 14 Sep 2022 17:06:33 -0400 Subject: [PATCH 040/134] update: remove duplicate key from groups.json --- website/common/locales/en/groups.json | 1 - 1 file changed, 1 deletion(-) diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index f46fed3316..f9e1cdd157 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -353,7 +353,6 @@ "groupTeacher": "Teacher setting up tasks for students", "gettingStarted": "Getting Started", "next": "Next", - "cancel": "Cancel", "nameStar": "Name*", "nameStarText": "Add a title", "descriptionOptional": "Description", From 014a4b653ae99638dd527f7d2f9baca6ebf5da34 Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Wed, 14 Sep 2022 17:24:29 -0400 Subject: [PATCH 041/134] update: remove another duplicate key from groups.json --- website/common/locales/en/groups.json | 1 - 1 file changed, 1 deletion(-) diff --git a/website/common/locales/en/groups.json b/website/common/locales/en/groups.json index f9e1cdd157..9d3aea4ff7 100644 --- a/website/common/locales/en/groups.json +++ b/website/common/locales/en/groups.json @@ -352,7 +352,6 @@ "groupManager": "Manager setting up tasks for employees", "groupTeacher": "Teacher setting up tasks for students", "gettingStarted": "Getting Started", - "next": "Next", "nameStar": "Name*", "nameStarText": "Add a title", "descriptionOptional": "Description", From 9a8d1854b93d08dc9b84de812658e206f5a82e4d Mon Sep 17 00:00:00 2001 From: SabreCat Date: Thu, 15 Sep 2022 11:40:48 -0500 Subject: [PATCH 042/134] fix(promo): handle Gems sale defined outside of single top event --- website/server/libs/payments/gems.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/website/server/libs/payments/gems.js b/website/server/libs/payments/gems.js index adb05b906d..831f88213a 100644 --- a/website/server/libs/payments/gems.js +++ b/website/server/libs/payments/gems.js @@ -1,5 +1,6 @@ +import find from 'lodash/find'; import { getAnalyticsServiceByEnvironment } from '../analyticsService'; -import { getCurrentEvent } from '../worldState'; // eslint-disable-line import/no-cycle +import { getCurrentEventList } from '../worldState'; // eslint-disable-line import/no-cycle import { // eslint-disable-line import/no-cycle getUserInfo, sendTxn as txnEmail, @@ -86,9 +87,10 @@ function getAmountForGems (data) { const { gemsBlock } = data; - const currentEvent = getCurrentEvent(); - if (currentEvent && currentEvent.gemsPromo && currentEvent.gemsPromo[gemsBlock.key]) { - return currentEvent.gemsPromo[gemsBlock.key] / 4; + const currentEventsList = getCurrentEventList(); + const promoEvent = find(currentEventsList, event => Boolean(event.gemsPromo)); + if (promoEvent && promoEvent.gemsPromo[gemsBlock.key]) { + return promoEvent.gemsPromo[gemsBlock.key] / 4; } return gemsBlock.gems / 4; From 4c34c68d780aa5a7fe58dea5d111eb8d22a3cd37 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Thu, 15 Sep 2022 12:20:11 -0500 Subject: [PATCH 043/134] fix(test): stub newly relevant function --- test/api/unit/libs/payments/payments.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/api/unit/libs/payments/payments.test.js b/test/api/unit/libs/payments/payments.test.js index 6083744658..fb9928d51d 100644 --- a/test/api/unit/libs/payments/payments.test.js +++ b/test/api/unit/libs/payments/payments.test.js @@ -692,14 +692,14 @@ describe('payments/index', () => { context('Active Promotion', () => { beforeEach(() => { - sinon.stub(worldState, 'getCurrentEvent').returns({ + sinon.stub(worldState, 'getCurrentEventList').returns([{ ...common.content.events.fall2020, event: 'fall2020', - }); + }]); }); afterEach(() => { - worldState.getCurrentEvent.restore(); + worldState.getCurrentEventList.restore(); }); it('applies a discount', async () => { From 719fab8d4bb1c226035565eb4b0c1aca56789541 Mon Sep 17 00:00:00 2001 From: CuriousMagpie Date: Fri, 16 Sep 2022 16:12:34 -0400 Subject: [PATCH 044/134] chore: merge develop in --- habitica-images | 2 +- test/api/unit/libs/email.test.js | 25 - test/api/unit/libs/password.test.js | 2 +- .../v3/integration/user/DELETE-user.test.js | 39 - .../DELETE-user_auth_social_network.test.js | 64 - .../user/auth/POST-user_auth_social.test.js | 141 --- .../auth/POST-user_reset_password.test.js | 13 + website/client/src/assets/svg/facebook.svg | 4 +- website/client/src/assets/svg/instagram.svg | 4 +- website/client/src/assets/svg/tumblr.svg | 3 + website/client/src/assets/svg/twitter.svg | 4 +- website/client/src/components/appFooter.vue | 1119 +++++++++++------ .../client/src/components/auth/authForm.vue | 20 - .../components/auth/registerLoginReset.vue | 4 - .../src/components/groups/groupPlan.vue | 12 +- .../src/components/payments/buyGemsModal.vue | 8 - .../client/src/components/settings/site.vue | 4 +- .../client/src/components/static/privacy.vue | 7 +- .../src/components/static/staticWrapper.vue | 104 +- website/client/vue.config.js | 1 - website/common/locales/da/backgrounds.json | 3 +- website/common/locales/da/challenge.json | 3 +- .../locales/da/communityguidelines.json | 104 +- website/common/locales/da/content.json | 2 +- website/common/locales/da/contrib.json | 5 +- website/common/locales/da/death.json | 4 +- website/common/locales/da/defaulttasks.json | 4 +- website/common/locales/da/faq.json | 36 +- website/common/locales/da/front.json | 9 +- website/common/locales/da/gear.json | 9 +- website/common/locales/da/groups.json | 7 +- website/common/locales/da/limited.json | 173 ++- website/common/locales/da/npc.json | 9 +- website/common/locales/da/overview.json | 6 +- website/common/locales/da/pets.json | 8 +- website/common/locales/da/quests.json | 12 +- website/common/locales/da/questscontent.json | 2 +- website/common/locales/da/rebirth.json | 2 +- website/common/locales/da/settings.json | 8 +- website/common/locales/da/subscriber.json | 16 +- website/common/locales/da/tasks.json | 22 +- website/common/locales/de/groups.json | 2 +- website/common/locales/en/front.json | 11 +- website/common/locales/en/npc.json | 3 +- website/common/locales/en/questsContent.json | 2 +- website/common/locales/en/subscriber.json | 2 +- website/common/locales/es/gear.json | 34 +- website/common/locales/it/backgrounds.json | 9 +- website/common/locales/it/gear.json | 6 +- website/common/locales/ja/backgrounds.json | 9 +- website/common/locales/ja/gear.json | 6 +- website/common/locales/ja/generic.json | 4 +- website/common/locales/ja/quests.json | 2 +- website/common/locales/pt_BR/backgrounds.json | 9 +- website/common/locales/pt_BR/gear.json | 6 +- website/common/locales/ru/backgrounds.json | 9 +- website/common/locales/ru/groups.json | 22 +- website/common/locales/ru/settings.json | 3 +- website/common/locales/ru/subscriber.json | 4 +- website/common/locales/uk/backgrounds.json | 9 +- website/common/locales/uk/gear.json | 18 +- website/common/locales/uk/questscontent.json | 38 +- website/common/locales/zh/backgrounds.json | 9 +- website/common/locales/zh/faq.json | 3 +- website/common/locales/zh/gear.json | 10 +- website/common/locales/zh/npc.json | 4 +- website/common/script/constants.js | 1 - website/common/script/content/bundles.js | 3 +- .../common/script/content/constants/events.js | 6 + .../script/content/shop-featuredItems.js | 14 +- website/server/libs/auth/index.js | 7 +- website/server/libs/auth/social.js | 10 +- website/server/libs/setupPassport.js | 15 - 73 files changed, 1297 insertions(+), 1017 deletions(-) create mode 100644 website/client/src/assets/svg/tumblr.svg diff --git a/habitica-images b/habitica-images index 8d63ff55e5..ce1e117dc9 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 8d63ff55e5c63872f95a3c9106d0b5f90af9ab6f +Subproject commit ce1e117dc94ac4b1757f144e6897e332ccdaf119 diff --git a/test/api/unit/libs/email.test.js b/test/api/unit/libs/email.test.js index b3fe180086..8673e03b62 100644 --- a/test/api/unit/libs/email.test.js +++ b/test/api/unit/libs/email.test.js @@ -13,11 +13,6 @@ function getUser () { username: 'username', email: 'email@email', }, - facebook: { - emails: [{ - value: 'email@facebook', - }], - }, google: { emails: [{ value: 'email@google', @@ -62,30 +57,12 @@ describe('emails', () => { expect(data).to.have.property('canSend', true); }); - it('returns correct user data [facebook users]', () => { - const attachEmail = requireAgain(pathToEmailLib); - const { getUserInfo } = attachEmail; - const user = getUser(); - delete user.profile.name; - delete user.auth.local.email; - delete user.auth.google.emails; - delete user.auth.apple.emails; - - const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); - - expect(data).to.have.property('name', user.auth.local.username); - expect(data).to.have.property('email', user.auth.facebook.emails[0].value); - expect(data).to.have.property('_id', user._id); - expect(data).to.have.property('canSend', true); - }); - it('returns correct user data [google users]', () => { const attachEmail = requireAgain(pathToEmailLib); const { getUserInfo } = attachEmail; const user = getUser(); delete user.profile.name; delete user.auth.local.email; - delete user.auth.facebook.emails; delete user.auth.apple.emails; const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); @@ -103,7 +80,6 @@ describe('emails', () => { delete user.profile.name; delete user.auth.local.email; delete user.auth.google.emails; - delete user.auth.facebook.emails; const data = getUserInfo(user, ['name', 'email', '_id', 'canSend']); @@ -118,7 +94,6 @@ describe('emails', () => { const { getUserInfo } = attachEmail; const user = getUser(); delete user.auth.local.email; - delete user.auth.facebook; delete user.auth.google; delete user.auth.apple; diff --git a/test/api/unit/libs/password.test.js b/test/api/unit/libs/password.test.js index 3ca2643b6d..d9f1572ab5 100644 --- a/test/api/unit/libs/password.test.js +++ b/test/api/unit/libs/password.test.js @@ -246,7 +246,7 @@ describe('Password Utilities', () => { it('returns false if the user has no local auth', async () => { const user = await generateUser({ auth: { - facebook: {}, + google: {}, }, }); const res = await validatePasswordResetCodeAndFindUser(encrypt(JSON.stringify({ diff --git a/test/api/v3/integration/user/DELETE-user.test.js b/test/api/v3/integration/user/DELETE-user.test.js index 18be9792ce..5c176fe141 100644 --- a/test/api/v3/integration/user/DELETE-user.test.js +++ b/test/api/v3/integration/user/DELETE-user.test.js @@ -289,45 +289,6 @@ describe('DELETE /user', () => { }); }); - context('user with Facebook auth', async () => { - beforeEach(async () => { - user = await generateUser({ - auth: { - facebook: { - id: 'facebook-id', - }, - }, - }); - }); - - it('returns an error if confirmation phrase is wrong', async () => { - await expect(user.del('/user', { - password: 'just-do-it', - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('incorrectDeletePhrase', { magicWord: 'DELETE' }), - }); - }); - - it('returns an error if confirmation phrase is not supplied', async () => { - await expect(user.del('/user', { - password: '', - })).to.eventually.be.rejected.and.eql({ - code: 400, - error: 'BadRequest', - message: t('missingPassword'), - }); - }); - - it('deletes a Facebook user', async () => { - await user.del('/user', { - password: DELETE_CONFIRMATION, - }); - await expect(checkExistence('users', user._id)).to.eventually.eql(false); - }); - }); - context('user with Google auth', async () => { beforeEach(async () => { user = await generateUser({ diff --git a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js index 12bef95459..522a49d369 100644 --- a/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js +++ b/test/api/v3/integration/user/auth/DELETE-user_auth_social_network.test.js @@ -20,44 +20,6 @@ describe('DELETE social registration', () => { }); }); - context('Facebook', () => { - it('fails if user does not have an alternative registration method', async () => { - await user.update({ - 'auth.facebook.id': 'some-fb-id', - 'auth.local': { ok: true }, - }); - await expect(user.del('/user/auth/social/facebook')).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('cantDetachSocial'), - }); - }); - - it('succeeds if user has a local registration', async () => { - await user.update({ - 'auth.facebook.id': 'some-fb-id', - }); - - const response = await user.del('/user/auth/social/facebook'); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.facebook).to.be.undefined; - }); - - it('succeeds if user has a google registration', async () => { - await user.update({ - 'auth.facebook.id': 'some-fb-id', - 'auth.google.id': 'some-google-id', - 'auth.local': { ok: true }, - }); - - const response = await user.del('/user/auth/social/facebook'); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.facebook).to.be.undefined; - }); - }); - context('Google', () => { it('fails if user does not have an alternative registration method', async () => { await user.update({ @@ -81,19 +43,6 @@ describe('DELETE social registration', () => { await user.sync(); expect(user.auth.google).to.be.undefined; }); - - it('succeeds if user has a facebook registration', async () => { - await user.update({ - 'auth.google.id': 'some-google-id', - 'auth.facebook.id': 'some-facebook-id', - 'auth.local': { ok: true }, - }); - - const response = await user.del('/user/auth/social/google'); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.goodl).to.be.undefined; - }); }); context('Apple', () => { @@ -119,18 +68,5 @@ describe('DELETE social registration', () => { await user.sync(); expect(user.auth.apple).to.be.undefined; }); - - it('succeeds if user has a facebook registration', async () => { - await user.update({ - 'auth.apple.id': 'some-apple-id', - 'auth.facebook.id': 'some-facebook-id', - 'auth.local': { ok: true }, - }); - - const response = await user.del('/user/auth/social/apple'); - expect(response).to.eql({}); - await user.sync(); - expect(user.auth.goodl).to.be.undefined; - }); }); }); diff --git a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js index c4ffe57efb..df976d0987 100644 --- a/test/api/v3/integration/user/auth/POST-user_auth_social.test.js +++ b/test/api/v3/integration/user/auth/POST-user_auth_social.test.js @@ -12,7 +12,6 @@ describe('POST /user/auth/social', () => { let user; const endpoint = '/user/auth/social'; let randomAccessToken = '123456'; - let randomFacebookId = 'facebookId'; let randomGoogleId = 'googleId'; let network = 'NoNetwork'; @@ -33,146 +32,6 @@ describe('POST /user/auth/social', () => { }); }); - describe('facebook', () => { - beforeEach(async () => { - randomFacebookId = generateUUID(); - const expectedResult = { - id: randomFacebookId, - displayName: 'a facebook user', - emails: [ - { value: `${user.auth.local.username}+facebook@example.com` }, - ], - }; - sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult); - network = 'facebook'; - }); - - afterEach(async () => { - passport._strategies.facebook.userProfile.restore(); - }); - - it('registers a new user', async () => { - const response = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - expect(response.apiToken).to.exist; - expect(response.id).to.exist; - expect(response.newUser).to.be.true; - expect(response.username).to.exist; - - await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a facebook user'); - await expect(getProperty('users', response.id, 'auth.local.lowerCaseUsername')).to.exist; - await expect(getProperty('users', response.id, 'auth.local.email')).to.eventually.equal(`${user.auth.local.username}+facebook@example.com`); - await expect(getProperty('users', response.id, 'auth.facebook.id')).to.eventually.equal(randomFacebookId); - }); - - it('logs an existing user in', async () => { - const registerResponse = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - const response = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - expect(response.apiToken).to.eql(registerResponse.apiToken); - expect(response.id).to.eql(registerResponse.id); - expect(response.newUser).to.be.false; - expect(registerResponse.newUser).to.be.true; - }); - - it('logs an existing user in if they have local auth with matching email', async () => { - passport._strategies.facebook.userProfile.restore(); - const expectedResult = { - id: randomFacebookId, - displayName: 'a facebook user', - emails: [ - { value: user.auth.local.email }, - ], - }; - sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult); - - const response = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - expect(response.apiToken).to.eql(user.apiToken); - expect(response.id).to.eql(user._id); - expect(response.newUser).to.be.false; - }); - - it('logs an existing user into their social account if they have local auth with matching email', async () => { - const registerResponse = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - expect(registerResponse.newUser).to.be.true; - // This is important for existing accounts before the new social handling - passport._strategies.facebook.userProfile.restore(); - const expectedResult = { - id: randomFacebookId, - displayName: 'a facebook user', - emails: [ - { value: user.auth.local.email }, - ], - }; - sandbox.stub(passport._strategies.facebook, 'userProfile').yields(null, expectedResult); - - const response = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - expect(response.apiToken).to.eql(registerResponse.apiToken); - expect(response.id).to.eql(registerResponse.id); - expect(response.apiToken).not.to.eql(user.apiToken); - expect(response.id).not.to.eql(user._id); - expect(response.newUser).to.be.false; - }); - - it('add social auth to an existing user', async () => { - const response = await user.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - expect(response.apiToken).to.eql(user.apiToken); - expect(response.id).to.eql(user._id); - expect(response.newUser).to.be.false; - }); - - it('does not log into other account if social auth already exists', async () => { - const registerResponse = await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - expect(registerResponse.newUser).to.be.true; - - await expect(user.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - })).to.eventually.be.rejected.and.eql({ - code: 401, - error: 'NotAuthorized', - message: t('socialAlreadyExists'), - }); - }); - - xit('enrolls a new user in an A/B test', async () => { - await api.post(endpoint, { - authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase - network, - }); - - await expect(getProperty('users', user._id, '_ABtests')).to.eventually.be.a('object'); - }); - }); - describe('google', () => { beforeEach(async () => { randomGoogleId = generateUUID(); diff --git a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js index 8603fa7cb3..da66e5fd3e 100644 --- a/test/api/v3/integration/user/auth/POST-user_reset_password.test.js +++ b/test/api/v3/integration/user/auth/POST-user_reset_password.test.js @@ -25,6 +25,19 @@ describe('POST /user/reset-password', async () => { expect(user.auth.local.hashed_password).to.not.eql(previousPassword); }); + it('resets password for social users', async () => { + const email = `${user.auth.local.username}+google@example.com`; + await user.update({ 'auth.google.emails': [{ value: email }] }); + await user.sync(); + const previousPassword = user.auth.local.passwordResetCode; + const response = await user.post(endpoint, { + email, + }); + expect(response).to.eql({ data: {}, message: t('passwordReset') }); + await user.sync(); + expect(user.auth.local.passwordResetCode).to.not.eql(previousPassword); + }); + it('same message on error as on success', async () => { const response = await user.post(endpoint, { email: 'nonExistent@email.com', diff --git a/website/client/src/assets/svg/facebook.svg b/website/client/src/assets/svg/facebook.svg index 3150f4d0f9..02315b15bf 100644 --- a/website/client/src/assets/svg/facebook.svg +++ b/website/client/src/assets/svg/facebook.svg @@ -1,3 +1,3 @@ - - + + diff --git a/website/client/src/assets/svg/instagram.svg b/website/client/src/assets/svg/instagram.svg index 80ff7254f5..c5ee2c2447 100644 --- a/website/client/src/assets/svg/instagram.svg +++ b/website/client/src/assets/svg/instagram.svg @@ -1,3 +1,3 @@ - - + + diff --git a/website/client/src/assets/svg/tumblr.svg b/website/client/src/assets/svg/tumblr.svg new file mode 100644 index 0000000000..4e8eabc27e --- /dev/null +++ b/website/client/src/assets/svg/tumblr.svg @@ -0,0 +1,3 @@ + + + diff --git a/website/client/src/assets/svg/twitter.svg b/website/client/src/assets/svg/twitter.svg index c48698bcdb..b477dbb472 100644 --- a/website/client/src/assets/svg/twitter.svg +++ b/website/client/src/assets/svg/twitter.svg @@ -1,3 +1,3 @@ - - + + diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index c3e0b81ac0..963cd6d9d9 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -1,487 +1,792 @@