mirror of
https://github.com/sudoxnym/habitica-self-host.git
synced 2026-04-14 19:47:03 +00:00
Bulk purchasing (#9196)
* Moved buy tests * Added mystery buy to buy.js * Added quest purchasing to buy * Added buy special * Moved integration tests to buy folder * Removed buyGear dependency * Removed buyArmoire dependency * Removed buyHealthPotion dependency * Removed myster, quest and special dependency * Replaced functions with factory * Added bulk purchasing to common * Added bulk purchasing to the api * Added bulk purchasing to client * Refactored purchasing function to reduce long method * Added bulk purchase to gem purchases * Added bulk purchasing to api * Added bulk purchasing to gem items on client * Removed bulk from equipment * Removed recentlyPurchased * Fixed style issues and prevented puchasing more gems than are left * Fixed lint * Fixed missing keys * Fixed gem amount notice * Added quest modal to pinned item * Added bulk purchase to gem modal * Fixed styles * Fixed bulk purchase for spells * Fixed modal size * Hid autofill
This commit is contained in:
parent
b74cee3d21
commit
7fe2504906
32 changed files with 578 additions and 265 deletions
|
|
@ -98,4 +98,24 @@ describe('POST /user/purchase/:type/:key', () => {
|
|||
await members[0].sync();
|
||||
expect(members[0].balance).to.equal(oldBalance);
|
||||
});
|
||||
|
||||
describe('bulk purchasing', () => {
|
||||
it('purchases a gem item', async () => {
|
||||
await user.post(`/user/purchase/${type}/${key}`, {quantity: 2});
|
||||
await user.sync();
|
||||
|
||||
expect(user.items[type][key]).to.equal(2);
|
||||
});
|
||||
|
||||
it('can convert gold to gems if subscribed', async () => {
|
||||
let oldBalance = user.balance;
|
||||
await user.update({
|
||||
'purchased.plan.customerId': 'group-plan',
|
||||
'stats.gp': 1000,
|
||||
});
|
||||
await user.post('/user/purchase/gems/gem', {quantity: 2});
|
||||
await user.sync();
|
||||
expect(user.balance).to.equal(oldBalance + 0.50);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../website/common/script';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
|
|
@ -82,4 +82,19 @@ describe('POST /user/buy/:key', () => {
|
|||
itemText: item.text(),
|
||||
}));
|
||||
});
|
||||
|
||||
it('allows for bulk purchases', async () => {
|
||||
await user.update({
|
||||
'stats.gp': 400,
|
||||
'stats.hp': 20,
|
||||
});
|
||||
|
||||
let potion = content.potion;
|
||||
let res = await user.post('/user/buy/potion', {quantity: 2});
|
||||
await user.sync();
|
||||
|
||||
expect(user.stats.hp).to.equal(50);
|
||||
expect(res.data).to.eql(user.stats);
|
||||
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
|
||||
});
|
||||
});
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/buy-armoire', () => {
|
||||
let user;
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/buy-gear/:key', () => {
|
||||
let user;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../website/common/script';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
|
||||
describe('POST /user/buy-mystery-set/:key', () => {
|
||||
let user;
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../website/common/script';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import {
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../website/common/script';
|
||||
} from '../../../../../helpers/api-integration/v3';
|
||||
import shared from '../../../../../../website/common/script';
|
||||
|
||||
let content = shared.content;
|
||||
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
/* eslint-disable camelcase */
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import buy from '../../../website/common/script/ops/buy';
|
||||
import {
|
||||
BadRequest,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
weapon_warrior_0: true,
|
||||
},
|
||||
equipped: {
|
||||
weapon_warrior_0: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when key is not provided', (done) => {
|
||||
try {
|
||||
buy(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('recovers 15 hp', () => {
|
||||
user.stats.hp = 30;
|
||||
buy(user, {params: {key: 'potion'}});
|
||||
expect(user.stats.hp).to.eql(45);
|
||||
});
|
||||
|
||||
it('adds equipment to inventory', () => {
|
||||
user.stats.gp = 31;
|
||||
buy(user, {params: {key: 'armor_warrior_1'}});
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
weapon_warrior_0: true,
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
124
test/common/ops/buy/buy.js
Normal file
124
test/common/ops/buy/buy.js
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/* eslint-disable camelcase */
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../../helpers/common.helper';
|
||||
import buy from '../../../../website/common/script/ops/buy';
|
||||
import {
|
||||
BadRequest,
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
|
||||
describe('shared.ops.buy', () => {
|
||||
let user;
|
||||
|
||||
beforeEach(() => {
|
||||
user = generateUser({
|
||||
items: {
|
||||
gear: {
|
||||
owned: {
|
||||
weapon_warrior_0: true,
|
||||
},
|
||||
equipped: {
|
||||
weapon_warrior_0: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
stats: { gp: 200 },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error when key is not provided', (done) => {
|
||||
try {
|
||||
buy(user);
|
||||
} catch (err) {
|
||||
expect(err).to.be.an.instanceof(BadRequest);
|
||||
expect(err.message).to.equal(i18n.t('missingKeyParam'));
|
||||
done();
|
||||
}
|
||||
});
|
||||
|
||||
it('recovers 15 hp', () => {
|
||||
user.stats.hp = 30;
|
||||
buy(user, {params: {key: 'potion'}});
|
||||
expect(user.stats.hp).to.eql(45);
|
||||
});
|
||||
|
||||
it('adds equipment to inventory', () => {
|
||||
user.stats.gp = 31;
|
||||
|
||||
buy(user, {params: {key: 'armor_warrior_1'}});
|
||||
|
||||
expect(user.items.gear.owned).to.eql({
|
||||
weapon_warrior_0: true,
|
||||
armor_warrior_1: true,
|
||||
eyewear_special_blackTopFrame: true,
|
||||
eyewear_special_blueTopFrame: true,
|
||||
eyewear_special_greenTopFrame: true,
|
||||
eyewear_special_pinkTopFrame: true,
|
||||
eyewear_special_redTopFrame: true,
|
||||
eyewear_special_whiteTopFrame: true,
|
||||
eyewear_special_yellowTopFrame: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('buys Steampunk Accessories Set', () => {
|
||||
user.purchased.plan.consecutive.trinkets = 1;
|
||||
|
||||
buy(user, {
|
||||
params: {
|
||||
key: '301404',
|
||||
},
|
||||
type: 'mystery',
|
||||
});
|
||||
|
||||
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
|
||||
expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
|
||||
expect(user.items.gear.owned).to.have.property('weapon_mystery_301404', true);
|
||||
expect(user.items.gear.owned).to.have.property('armor_mystery_301404', true);
|
||||
expect(user.items.gear.owned).to.have.property('head_mystery_301404', true);
|
||||
expect(user.items.gear.owned).to.have.property('eyewear_mystery_301404', true);
|
||||
});
|
||||
|
||||
it('buys a Quest scroll', () => {
|
||||
user.stats.gp = 205;
|
||||
|
||||
buy(user, {
|
||||
params: {
|
||||
key: 'dilatoryDistress1',
|
||||
},
|
||||
type: 'quest',
|
||||
});
|
||||
|
||||
expect(user.items.quests).to.eql({dilatoryDistress1: 1});
|
||||
expect(user.stats.gp).to.equal(5);
|
||||
});
|
||||
|
||||
it('buys a special item', () => {
|
||||
user.stats.gp = 11;
|
||||
let item = content.special.thankyou;
|
||||
|
||||
let [data, message] = buy(user, {
|
||||
params: {
|
||||
key: 'thankyou',
|
||||
},
|
||||
type: 'special',
|
||||
});
|
||||
|
||||
expect(user.stats.gp).to.equal(1);
|
||||
expect(user.items.special.thankyou).to.equal(1);
|
||||
expect(data).to.eql({
|
||||
items: user.items,
|
||||
stats: user.stats,
|
||||
});
|
||||
expect(message).to.equal(i18n.t('messageBought', {
|
||||
itemText: item.text(),
|
||||
}));
|
||||
});
|
||||
|
||||
it('allows for bulk purchases', () => {
|
||||
user.stats.hp = 30;
|
||||
buy(user, {params: {key: 'potion'}, quantity: 2});
|
||||
expect(user.stats.hp).to.eql(50);
|
||||
});
|
||||
});
|
||||
|
|
@ -2,15 +2,15 @@
|
|||
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import count from '../../../website/common/script/count';
|
||||
import buyArmoire from '../../../website/common/script/ops/buyArmoire';
|
||||
import randomVal from '../../../website/common/script/libs/randomVal';
|
||||
import content from '../../../website/common/script/content/index';
|
||||
} from '../../../helpers/common.helper';
|
||||
import count from '../../../../website/common/script/count';
|
||||
import buyArmoire from '../../../../website/common/script/ops/buyArmoire';
|
||||
import randomVal from '../../../../website/common/script/libs/randomVal';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
function getFullArmoire () {
|
||||
let fullArmoire = {};
|
||||
|
|
@ -3,13 +3,13 @@
|
|||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import buyGear from '../../../website/common/script/ops/buyGear';
|
||||
import shared from '../../../website/common/script';
|
||||
} from '../../../helpers/common.helper';
|
||||
import buyGear from '../../../../website/common/script/ops/buyGear';
|
||||
import shared from '../../../../website/common/script';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buyGear', () => {
|
||||
let user;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
/* eslint-disable camelcase */
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import buyHealthPotion from '../../../website/common/script/ops/buyHealthPotion';
|
||||
} from '../../../helpers/common.helper';
|
||||
import buyHealthPotion from '../../../../website/common/script/ops/buyHealthPotion';
|
||||
import {
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buyHealthPotion', () => {
|
||||
let user;
|
||||
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import buyMysterySet from '../../../website/common/script/ops/buyMysterySet';
|
||||
} from '../../../helpers/common.helper';
|
||||
import buyMysterySet from '../../../../website/common/script/ops/buyMysterySet';
|
||||
import {
|
||||
NotAuthorized,
|
||||
NotFound,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buyMysterySet', () => {
|
||||
let user;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import buyQuest from '../../../website/common/script/ops/buyQuest';
|
||||
} from '../../../helpers/common.helper';
|
||||
import buyQuest from '../../../../website/common/script/ops/buyQuest';
|
||||
import {
|
||||
NotAuthorized,
|
||||
NotFound,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
|
||||
describe('shared.ops.buyQuest', () => {
|
||||
let user;
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import buySpecialSpell from '../../../website/common/script/ops/buySpecialSpell';
|
||||
import buySpecialSpell from '../../../../website/common/script/ops/buySpecialSpell';
|
||||
import {
|
||||
BadRequest,
|
||||
NotFound,
|
||||
NotAuthorized,
|
||||
} from '../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../website/common/script/i18n';
|
||||
} from '../../../../website/common/script/libs/errors';
|
||||
import i18n from '../../../../website/common/script/i18n';
|
||||
import {
|
||||
generateUser,
|
||||
} from '../../helpers/common.helper';
|
||||
import content from '../../../website/common/script/content/index';
|
||||
} from '../../../helpers/common.helper';
|
||||
import content from '../../../../website/common/script/content/index';
|
||||
|
||||
describe('shared.ops.buySpecialSpell', () => {
|
||||
let user;
|
||||
|
|
@ -138,6 +138,7 @@ describe('shared.ops.purchase', () => {
|
|||
user.balance = userGemAmount;
|
||||
user.stats.gp = goldPoints;
|
||||
user.purchased.plan.gemsBought = 0;
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
});
|
||||
|
||||
it('purchases gems', () => {
|
||||
|
|
@ -226,4 +227,39 @@ describe('shared.ops.purchase', () => {
|
|||
clock.restore();
|
||||
});
|
||||
});
|
||||
|
||||
context('bulk purchase', () => {
|
||||
let userGemAmount = 10;
|
||||
|
||||
before(() => {
|
||||
user.balance = userGemAmount;
|
||||
user.stats.gp = goldPoints;
|
||||
user.purchased.plan.gemsBought = 0;
|
||||
user.purchased.plan.customerId = 'customer-id';
|
||||
});
|
||||
|
||||
it('makes bulk purchases of gems', () => {
|
||||
let [, message] = purchase(user, {
|
||||
params: {type: 'gems', key: 'gem'},
|
||||
quantity: 2,
|
||||
});
|
||||
|
||||
expect(message).to.equal(i18n.t('plusOneGem'));
|
||||
expect(user.balance).to.equal(userGemAmount + 0.50);
|
||||
expect(user.purchased.plan.gemsBought).to.equal(2);
|
||||
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate * 2);
|
||||
});
|
||||
|
||||
it('makes bulk purchases of eggs', () => {
|
||||
let type = 'eggs';
|
||||
let key = 'TigerCub';
|
||||
|
||||
purchase(user, {
|
||||
params: {type, key},
|
||||
quantity: 2,
|
||||
});
|
||||
|
||||
expect(user.items[type][key]).to.equal(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -41,25 +41,32 @@
|
|||
:item="item"
|
||||
)
|
||||
|
||||
div(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value)}")
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
||||
span.value(:class="getPriceClass()") {{ item.value }}
|
||||
.purchase-amount(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}")
|
||||
.how-many-to-buy(v-if='item.purchaseType !== "gear"')
|
||||
strong {{ $t('howManyToBuy') }}
|
||||
div(v-if='item.purchaseType !== "gear"')
|
||||
.box
|
||||
input(type='number', min='0', v-model='selectedAmountToBuy')
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
||||
span.value(:class="getPriceClass()") {{ item.value }}
|
||||
|
||||
.gems-left(v-if='item.key === "gem"')
|
||||
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
|
||||
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
|
||||
|
||||
div(v-if='attemptingToPurchaseMoreGemsThanAreLeft')
|
||||
| {{$t('notEnoughGemsToBuy')}}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="purchaseGems()",
|
||||
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value)"
|
||||
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
) {{ $t('purchaseGems') }}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="buyItem()",
|
||||
v-else,
|
||||
:disabled='item.key === "gem" && gemsLeft === 0',
|
||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value)}"
|
||||
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft',
|
||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
) {{ $t('buyNow') }}
|
||||
|
||||
div.limitedTime(v-if="item.event")
|
||||
|
|
@ -101,6 +108,37 @@
|
|||
width: 282px;
|
||||
}
|
||||
|
||||
.purchase-amount {
|
||||
margin-top: 24px;
|
||||
|
||||
.how-many-to-buy {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 74px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-right: 24px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
visibility: hidden;
|
||||
display: none !important;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
|
|
@ -217,6 +255,8 @@
|
|||
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import planGemLimits from 'common/script/libs/planGemLimits';
|
||||
|
|
@ -248,6 +288,8 @@
|
|||
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
||||
components: {
|
||||
bModal,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
BalanceInfo,
|
||||
EquipmentAttributesGrid,
|
||||
Item,
|
||||
|
|
@ -264,6 +306,7 @@
|
|||
clock: svgClock,
|
||||
}),
|
||||
|
||||
selectedAmountToBuy: 1,
|
||||
isPinned: false,
|
||||
};
|
||||
},
|
||||
|
|
@ -306,10 +349,15 @@
|
|||
if (!this.user.purchased.plan) return 0;
|
||||
return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
|
||||
},
|
||||
attemptingToPurchaseMoreGemsThanAreLeft () {
|
||||
if (this.item && this.item.key && this.item.key === 'gem' && this.selectedAmountToBuy > this.gemsLeft) return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
this.isPinned = this.item && this.item.pinned;
|
||||
this.selectedAmountToBuy = 1;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
@ -320,7 +368,7 @@
|
|||
if (this.item.cast) {
|
||||
this.castStart(this.item);
|
||||
} else if (this.genericPurchase) {
|
||||
this.makeGenericPurchase(this.item);
|
||||
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||
this.purchased(this.item.text);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -355,7 +355,7 @@
|
|||
|
||||
}
|
||||
|
||||
.gems-left {
|
||||
.market .gems-left {
|
||||
position: absolute;
|
||||
right: -.5em;
|
||||
top: -.5em;
|
||||
|
|
@ -739,11 +739,6 @@ export default {
|
|||
}
|
||||
},
|
||||
itemSelected (item) {
|
||||
if (item.purchaseType !== 'gear' && this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
featuredItemSelected (item) {
|
||||
|
|
|
|||
|
|
@ -18,20 +18,23 @@
|
|||
div.inner-content
|
||||
questDialogContent(:item="item")
|
||||
|
||||
div
|
||||
.purchase-amount
|
||||
.how-many-to-buy
|
||||
strong {{ $t('howManyToBuy') }}
|
||||
.box
|
||||
input(type='number', min='0', v-model='selectedAmountToBuy')
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||
span.value(:class="priceType") {{ item.value }}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="purchaseGems()",
|
||||
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value)"
|
||||
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)"
|
||||
) {{ $t('purchaseGems') }}
|
||||
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="buyItem()",
|
||||
v-else,
|
||||
:class="{'notEnough': !this.enoughCurrency(priceType, item.value)}"
|
||||
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
) {{ $t('buyNow') }}
|
||||
|
||||
div.right-sidebar(v-if="item.drop")
|
||||
|
|
@ -52,9 +55,12 @@
|
|||
#buy-quest-modal {
|
||||
@include centeredModal();
|
||||
|
||||
.modal-dialog {
|
||||
margin-top: 25em;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
max-height: 80vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
|
|
@ -167,6 +173,37 @@
|
|||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.purchase-amount {
|
||||
margin-top: 24px;
|
||||
|
||||
.how-many-to-buy {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 74px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-right: 24px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
visibility: hidden;
|
||||
display: none !important;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
@ -210,6 +247,7 @@
|
|||
}),
|
||||
|
||||
isPinned: false,
|
||||
selectedAmountToBuy: 1,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
|
|
@ -238,10 +276,11 @@
|
|||
},
|
||||
methods: {
|
||||
onChange ($event) {
|
||||
this.selectedAmountToBuy = 1;
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal');
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
|
||||
this.purchased(this.item.text);
|
||||
this.hideDialog();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -477,11 +477,6 @@ export default {
|
|||
|
||||
this.selectedItemToBuy = item;
|
||||
|
||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -502,12 +502,6 @@
|
|||
},
|
||||
itemSelected (item) {
|
||||
if (item.locked) return;
|
||||
|
||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<template lang="pug">
|
||||
.tasks-column(:class='type')
|
||||
b-modal(ref="editTaskModal")
|
||||
buy-quest-modal(:item="selectedItemToBuy || {}",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)"
|
||||
v-if='type === "reward"')
|
||||
.d-flex
|
||||
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
||||
.filters.d-flex.justify-content-end
|
||||
|
|
@ -200,6 +205,7 @@ import sortable from 'client/directives/sortable.directive';
|
|||
import buyMixin from 'client/mixins/buy';
|
||||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue';
|
||||
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||
|
|
@ -215,6 +221,7 @@ export default {
|
|||
mixins: [buyMixin],
|
||||
components: {
|
||||
Task,
|
||||
BuyQuestModal,
|
||||
bModal,
|
||||
shopItem,
|
||||
},
|
||||
|
|
@ -278,6 +285,8 @@ export default {
|
|||
|
||||
forceRefresh: new Date(),
|
||||
quickAddText: '',
|
||||
|
||||
selectedItemToBuy: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
|
@ -479,10 +488,21 @@ export default {
|
|||
return;
|
||||
}
|
||||
|
||||
if (rewardItem.purchaseType === 'quests') {
|
||||
this.selectedItemToBuy = rewardItem;
|
||||
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
|
||||
this.$emit('openBuyDialog', rewardItem);
|
||||
}
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
export default {
|
||||
methods: {
|
||||
makeGenericPurchase (item, type = 'buyModal') {
|
||||
makeGenericPurchase (item, type = 'buyModal', quantity = 1) {
|
||||
this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: item.pinType,
|
||||
type: item.purchaseType,
|
||||
key: item.key,
|
||||
currency: item.currency,
|
||||
quantity,
|
||||
});
|
||||
|
||||
if (item.purchaseType !== 'gear') {
|
||||
this.$store.state.recentlyPurchased[item.key] = true;
|
||||
}
|
||||
|
||||
this.$root.$emit('playSound', 'Reward');
|
||||
|
||||
if (type !== 'buyModal') {
|
||||
|
|
|
|||
|
|
@ -1,18 +1,19 @@
|
|||
import axios from 'axios';
|
||||
import buyOp from 'common/script/ops/buy';
|
||||
import buyQuestOp from 'common/script/ops/buyQuest';
|
||||
import purchaseOp from 'common/script/ops/purchaseWithSpell';
|
||||
import buyMysterySetOp from 'common/script/ops/buyMysterySet';
|
||||
import hourglassPurchaseOp from 'common/script/ops/hourglassPurchase';
|
||||
import sellOp from 'common/script/ops/sell';
|
||||
import unlockOp from 'common/script/ops/unlock';
|
||||
import buyArmoire from 'common/script/ops/buyArmoire';
|
||||
import rerollOp from 'common/script/ops/reroll';
|
||||
import { getDropClass } from 'client/libs/notifications';
|
||||
|
||||
// @TODO: Purchase means gems and buy means gold. That wording is misused below, but we should also change
|
||||
// the generic buy functions to something else. Or have a Gold Vendor and Gem Vendor, etc
|
||||
|
||||
export function buyItem (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyOp(user, {params});
|
||||
let opResult = buyOp(user, {params, quantity});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
|
|
@ -21,32 +22,77 @@ export function buyItem (store, params) {
|
|||
}
|
||||
|
||||
export function buyQuestItem (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyQuestOp(user, {params});
|
||||
let opResult = buyOp(user, {
|
||||
params,
|
||||
type: 'quest',
|
||||
quantity,
|
||||
});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/buy-quest/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'quest'}),
|
||||
};
|
||||
}
|
||||
|
||||
async function buyArmoire (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
|
||||
let buyResult = buyOp(store.state.user.data, {
|
||||
params: {
|
||||
key: 'armoire',
|
||||
},
|
||||
type: 'armoire',
|
||||
quantity,
|
||||
});
|
||||
|
||||
// We need the server result because armoir has random item in the result
|
||||
let result = await axios.post('/api/v3/user/buy/armoire', {
|
||||
type: 'armoire',
|
||||
quantity,
|
||||
});
|
||||
buyResult = result.data.data;
|
||||
|
||||
if (buyResult) {
|
||||
const resData = buyResult;
|
||||
const item = resData.armoire;
|
||||
|
||||
const isExperience = item.type === 'experience';
|
||||
|
||||
if (item.type === 'gear') {
|
||||
store.state.user.data.items.gear.owned[item.dropKey] = true;
|
||||
}
|
||||
|
||||
// @TODO: We might need to abstract notifications to library rather than mixin
|
||||
store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: isExperience ? item.value : item.dropText,
|
||||
type: isExperience ? 'xp' : 'drop',
|
||||
icon: isExperience ? null : getDropClass({type: item.type, key: item.dropKey}),
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function purchase (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = purchaseOp(user, {params});
|
||||
let opResult = purchaseOp(user, {params, quantity});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/purchase/${params.type}/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/purchase/${params.type}/${params.key}`, {quantity}),
|
||||
};
|
||||
}
|
||||
|
||||
export function purchaseMysterySet (store, params) {
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyMysterySetOp(user, {params, noConfirm: true});
|
||||
let opResult = buyOp(user, {params, noConfirm: true, type: 'mystery'});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/buy-mystery-set/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'mystery'}),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -75,32 +121,7 @@ export async function genericPurchase (store, params) {
|
|||
case 'mystery_set':
|
||||
return purchaseMysterySet(store, params);
|
||||
case 'armoire': // eslint-disable-line
|
||||
let buyResult = buyArmoire(store.state.user.data);
|
||||
|
||||
// We need the server result because armoir has random item in the result
|
||||
let result = await axios.post('/api/v3/user/buy-armoire');
|
||||
buyResult = result.data.data;
|
||||
|
||||
if (buyResult) {
|
||||
const resData = buyResult;
|
||||
const item = resData.armoire;
|
||||
|
||||
const isExperience = item.type === 'experience';
|
||||
|
||||
if (item.type === 'gear') {
|
||||
store.state.user.data.items.gear.owned[item.dropKey] = true;
|
||||
}
|
||||
|
||||
// @TODO: We might need to abstract notifications to library rather than mixin
|
||||
store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: isExperience ? item.value : item.dropText,
|
||||
type: isExperience ? 'xp' : 'drop',
|
||||
icon: isExperience ? null : getDropClass({type: item.type, key: item.dropKey}),
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
|
||||
await buyArmoire(store, params);
|
||||
return;
|
||||
case 'fortify': {
|
||||
let rerollResult = rerollOp(store.state.user.data);
|
||||
|
|
@ -134,9 +155,5 @@ export async function genericPurchase (store, params) {
|
|||
export function sellItems (store, params) {
|
||||
const user = store.state.user.data;
|
||||
sellOp(user, {params, query: {amount: params.amount}});
|
||||
axios
|
||||
.post(`/api/v3/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
axios.post(`/api/v3/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -136,7 +136,6 @@ export default function () {
|
|||
modalStack: [],
|
||||
brokenChallengeTask: {},
|
||||
equipmentDrawerOpen: true,
|
||||
recentlyPurchased: {},
|
||||
groupPlans: [],
|
||||
groupNotifications: [],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -281,5 +281,6 @@
|
|||
"emptyMessagesLine2": "Send a message to start a conversation!",
|
||||
"letsgo": "Let's Go!",
|
||||
"selected": "Selected",
|
||||
"howManyToBuy": "How many would you like to buy?",
|
||||
"habiticaHasUpdated": "There is a new version of Habitica. Would you like to refresh to get the latest updates?"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -204,5 +204,6 @@
|
|||
"subscriptionAlreadySubscribed1": "To see your subscription details and cancel, renew, or change your subscription, please go to <a href='/user/settings/subscription'>User icon > Settings > Subscription</a>.",
|
||||
"purchaseAll": "Purchase All",
|
||||
"gemsPurchaseNote": "Subscribers can buy gems for gold in the Market! For easy access, you can also pin the gem to your Rewards column.",
|
||||
"gemsRemaining": "gems remaining"
|
||||
"gemsRemaining": "gems remaining",
|
||||
"notEnoughGemsToBuy": "You are unable to buy that amount of gems"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,18 +6,45 @@ import {
|
|||
import buyHealthPotion from './buyHealthPotion';
|
||||
import buyArmoire from './buyArmoire';
|
||||
import buyGear from './buyGear';
|
||||
import buyMysterySet from './buyMysterySet';
|
||||
import buyQuest from './buyQuest';
|
||||
import buySpecialSpell from './buySpecialSpell';
|
||||
|
||||
// @TODO: remove the req option style. Dependency on express structure is an anti-pattern
|
||||
// We should either have more parms or a set structure validated by a Type checker
|
||||
|
||||
// @TODO: when we are sure buy is the only function used, let's move the buy files to a folder
|
||||
|
||||
module.exports = function buy (user, req = {}, analytics) {
|
||||
let key = get(req, 'params.key');
|
||||
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
|
||||
|
||||
// @TODO: Slowly remove the need for key and use type instead
|
||||
// This should evenutally be the 'factory' function with vendor classes
|
||||
let type = get(req, 'type');
|
||||
if (!type) type = key;
|
||||
|
||||
// @TODO: For now, builk purchasing is here, but we should probably have a parent vendor
|
||||
// class that calls the factory and handles larger operations. If there is more than just bulk
|
||||
let quantity = 1;
|
||||
if (req.quantity) quantity = req.quantity;
|
||||
|
||||
let buyRes;
|
||||
if (key === 'potion') {
|
||||
buyRes = buyHealthPotion(user, req, analytics);
|
||||
} else if (key === 'armoire') {
|
||||
buyRes = buyArmoire(user, req, analytics);
|
||||
} else {
|
||||
buyRes = buyGear(user, req, analytics);
|
||||
|
||||
for (let i = 0; i < quantity; i += 1) {
|
||||
if (type === 'potion') {
|
||||
buyRes = buyHealthPotion(user, req, analytics);
|
||||
} else if (type === 'armoire') {
|
||||
buyRes = buyArmoire(user, req, analytics);
|
||||
} else if (type === 'mystery') {
|
||||
buyRes = buyMysterySet(user, req, analytics);
|
||||
} else if (type === 'quest') {
|
||||
buyRes = buyQuest(user, req, analytics);
|
||||
} else if (type === 'special') {
|
||||
buyRes = buySpecialSpell(user, req, analytics);
|
||||
} else {
|
||||
buyRes = buyGear(user, req, analytics);
|
||||
}
|
||||
}
|
||||
|
||||
return buyRes;
|
||||
|
|
|
|||
|
|
@ -14,68 +14,52 @@ import {
|
|||
import { removeItemByPath } from './pinnedGearUtils';
|
||||
import getItemInfo from '../libs/getItemInfo';
|
||||
|
||||
module.exports = function purchase (user, req = {}, analytics) {
|
||||
let type = get(req.params, 'type');
|
||||
let key = get(req.params, 'key');
|
||||
function buyGems (user, analytics, req, key) {
|
||||
let convRate = planGemLimits.convRate;
|
||||
let convCap = planGemLimits.convCap;
|
||||
convCap += user.purchased.plan.consecutive.gemCapExtra;
|
||||
|
||||
// Some groups limit their members ability to obtain gems
|
||||
// The check is async so it's done on the server (in server/controllers/api-v3/user#purchase)
|
||||
// only and not on the client,
|
||||
// resulting in a purchase that will seem successful until the request hit the server.
|
||||
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
|
||||
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
|
||||
}
|
||||
|
||||
if (user.stats.gp < convRate) {
|
||||
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
|
||||
}
|
||||
|
||||
if (user.purchased.plan.gemsBought >= convCap) {
|
||||
throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language));
|
||||
}
|
||||
|
||||
user.balance += 0.25;
|
||||
user.purchased.plan.gemsBought++;
|
||||
user.stats.gp -= convRate;
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('purchase gems', {
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
acquireMethod: 'Gold',
|
||||
goldCost: convRate,
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
pick(user, splitWhitespace('stats balance')),
|
||||
i18n.t('plusOneGem', req.language),
|
||||
];
|
||||
}
|
||||
|
||||
function getItemAndPrice (user, type, key, req) {
|
||||
let item;
|
||||
let price;
|
||||
|
||||
if (!type) {
|
||||
throw new BadRequest(i18n.t('typeRequired', req.language));
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
throw new BadRequest(i18n.t('keyRequired', req.language));
|
||||
}
|
||||
|
||||
if (type === 'gems' && key === 'gem') {
|
||||
let convRate = planGemLimits.convRate;
|
||||
let convCap = planGemLimits.convCap;
|
||||
convCap += user.purchased.plan.consecutive.gemCapExtra;
|
||||
|
||||
// Some groups limit their members ability to obtain gems
|
||||
// The check is async so it's done on the server (in server/controllers/api-v3/user#purchase)
|
||||
// only and not on the client,
|
||||
// resulting in a purchase that will seem successful until the request hit the server.
|
||||
|
||||
if (!user.purchased || !user.purchased.plan || !user.purchased.plan.customerId) {
|
||||
throw new NotAuthorized(i18n.t('mustSubscribeToPurchaseGems', req.language));
|
||||
}
|
||||
|
||||
if (user.stats.gp < convRate) {
|
||||
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
|
||||
}
|
||||
|
||||
if (user.purchased.plan.gemsBought >= convCap) {
|
||||
throw new NotAuthorized(i18n.t('reachedGoldToGemCap', {convCap}, req.language));
|
||||
}
|
||||
|
||||
user.balance += 0.25;
|
||||
user.purchased.plan.gemsBought++;
|
||||
user.stats.gp -= convRate;
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('purchase gems', {
|
||||
uuid: user._id,
|
||||
itemKey: key,
|
||||
acquireMethod: 'Gold',
|
||||
goldCost: convRate,
|
||||
category: 'behavior',
|
||||
headers: req.headers,
|
||||
});
|
||||
}
|
||||
|
||||
return [
|
||||
pick(user, splitWhitespace('stats balance')),
|
||||
i18n.t('plusOneGem', req.language),
|
||||
];
|
||||
}
|
||||
|
||||
let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear', 'bundles'];
|
||||
if (acceptedTypes.indexOf(type) === -1) {
|
||||
throw new NotFound(i18n.t('notAccteptedType', req.language));
|
||||
}
|
||||
|
||||
if (type === 'gear') {
|
||||
item = content.gear.flat[key];
|
||||
|
||||
|
|
@ -98,17 +82,10 @@ module.exports = function purchase (user, req = {}, analytics) {
|
|||
price = item.value / 4;
|
||||
}
|
||||
|
||||
if (!item.canBuy(user)) {
|
||||
throw new NotAuthorized(i18n.t('messageNotAvailable', req.language));
|
||||
}
|
||||
|
||||
if (!user.balance || user.balance < price) {
|
||||
throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
|
||||
}
|
||||
|
||||
let itemInfo = getItemInfo(user, type, item);
|
||||
removeItemByPath(user, itemInfo.path);
|
||||
return {item, price};
|
||||
}
|
||||
|
||||
function purchaseItem (user, item, price, type, key) {
|
||||
user.balance -= price;
|
||||
|
||||
if (type === 'gear') {
|
||||
|
|
@ -127,6 +104,50 @@ module.exports = function purchase (user, req = {}, analytics) {
|
|||
}
|
||||
user.items[type][key]++;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = function purchase (user, req = {}, analytics) {
|
||||
let type = get(req.params, 'type');
|
||||
let key = get(req.params, 'key');
|
||||
let quantity = req.quantity || 1;
|
||||
|
||||
if (!type) {
|
||||
throw new BadRequest(i18n.t('typeRequired', req.language));
|
||||
}
|
||||
|
||||
if (!key) {
|
||||
throw new BadRequest(i18n.t('keyRequired', req.language));
|
||||
}
|
||||
|
||||
if (type === 'gems' && key === 'gem') {
|
||||
let gemResponse;
|
||||
for (let i = 0; i < quantity; i += 1) {
|
||||
gemResponse = buyGems(user, analytics, req, key);
|
||||
}
|
||||
return gemResponse;
|
||||
}
|
||||
|
||||
let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear', 'bundles'];
|
||||
if (acceptedTypes.indexOf(type) === -1) {
|
||||
throw new NotFound(i18n.t('notAccteptedType', req.language));
|
||||
}
|
||||
|
||||
let {price, item} = getItemAndPrice(user, type, key, req);
|
||||
|
||||
if (!item.canBuy(user)) {
|
||||
throw new NotAuthorized(i18n.t('messageNotAvailable', req.language));
|
||||
}
|
||||
|
||||
if (!user.balance || user.balance < price) {
|
||||
throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
|
||||
}
|
||||
|
||||
let itemInfo = getItemInfo(user, type, item);
|
||||
removeItemByPath(user, itemInfo.path);
|
||||
|
||||
for (let i = 0; i < quantity; i += 1) {
|
||||
purchaseItem(user, item, price, type, key);
|
||||
}
|
||||
|
||||
if (analytics) {
|
||||
analytics.track('acquire item', {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
import buySpecialSpellOp from './buySpecialSpell';
|
||||
import buy from './buy';
|
||||
import purchaseOp from './purchase';
|
||||
import get from 'lodash/get';
|
||||
|
||||
module.exports = function purchaseWithSpell (user, req = {}, analytics) {
|
||||
const type = get(req.params, 'type');
|
||||
|
||||
return type === 'spells' ? buySpecialSpellOp(user, req) : purchaseOp(user, req, analytics);
|
||||
// Set up type for buy function - different than the above type.
|
||||
req.type = 'special';
|
||||
|
||||
return type === 'spells' ? buy(user, req, analytics) : purchaseOp(user, req, analytics);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -874,11 +874,21 @@ api.buy = {
|
|||
let buyRes;
|
||||
let specialKeys = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
||||
|
||||
// @TODO: Remove this when mobile passes type in body
|
||||
let type = req.params.key;
|
||||
if (specialKeys.indexOf(req.params.key) !== -1) {
|
||||
buyRes = common.ops.buySpecialSpell(user, req);
|
||||
} else {
|
||||
buyRes = common.ops.buy(user, req, res.analytics);
|
||||
type = 'special';
|
||||
}
|
||||
req.type = type;
|
||||
|
||||
// @TODO: right now common follow express structure, but we should decouple the dependency
|
||||
if (req.body.type) req.type = req.body.type;
|
||||
|
||||
let quantity = 1;
|
||||
if (req.body.quantity) quantity = req.body.quantity;
|
||||
req.quantity = quantity;
|
||||
|
||||
buyRes = common.ops.buy(user, req, res.analytics);
|
||||
|
||||
await user.save();
|
||||
res.respond(200, ...buyRes);
|
||||
|
|
@ -926,7 +936,7 @@ api.buyGear = {
|
|||
url: '/user/buy-gear/:key',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buyGearRes = common.ops.buyGear(user, req, res.analytics);
|
||||
let buyGearRes = common.ops.buy(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...buyGearRes);
|
||||
},
|
||||
|
|
@ -966,7 +976,9 @@ api.buyArmoire = {
|
|||
url: '/user/buy-armoire',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buyArmoireResponse = common.ops.buyArmoire(user, req, res.analytics);
|
||||
req.type = 'armoire';
|
||||
req.params.key = 'armoire';
|
||||
let buyArmoireResponse = common.ops.buy(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...buyArmoireResponse);
|
||||
},
|
||||
|
|
@ -1004,7 +1016,9 @@ api.buyHealthPotion = {
|
|||
url: '/user/buy-health-potion',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buyHealthPotionResponse = common.ops.buyHealthPotion(user, req, res.analytics);
|
||||
req.type = 'potion';
|
||||
req.params.key = 'potion';
|
||||
let buyHealthPotionResponse = common.ops.buy(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...buyHealthPotionResponse);
|
||||
},
|
||||
|
|
@ -1044,7 +1058,8 @@ api.buyMysterySet = {
|
|||
url: '/user/buy-mystery-set/:key',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buyMysterySetRes = common.ops.buyMysterySet(user, req, res.analytics);
|
||||
req.type = 'mystery';
|
||||
let buyMysterySetRes = common.ops.buy(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...buyMysterySetRes);
|
||||
},
|
||||
|
|
@ -1084,7 +1099,8 @@ api.buyQuest = {
|
|||
url: '/user/buy-quest/:key',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buyQuestRes = common.ops.buyQuest(user, req, res.analytics);
|
||||
req.type = 'quest';
|
||||
let buyQuestRes = common.ops.buy(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...buyQuestRes);
|
||||
},
|
||||
|
|
@ -1123,7 +1139,8 @@ api.buySpecialSpell = {
|
|||
url: '/user/buy-special-spell/:key',
|
||||
async handler (req, res) {
|
||||
let user = res.locals.user;
|
||||
let buySpecialSpellRes = common.ops.buySpecialSpell(user, req);
|
||||
req.type = 'special';
|
||||
let buySpecialSpellRes = common.ops.buy(user, req);
|
||||
await user.save();
|
||||
res.respond(200, ...buySpecialSpellRes);
|
||||
},
|
||||
|
|
@ -1337,6 +1354,11 @@ api.purchase = {
|
|||
if (!canGetGems) throw new NotAuthorized(res.t('groupPolicyCannotGetGems'));
|
||||
}
|
||||
|
||||
// Req is currently used as options. Slighly confusing, but this will solve that for now.
|
||||
let quantity = 1;
|
||||
if (req.body.quantity) quantity = req.body.quantity;
|
||||
req.quantity = quantity;
|
||||
|
||||
let purchaseRes = common.ops.purchaseWithSpell(user, req, res.analytics);
|
||||
await user.save();
|
||||
res.respond(200, ...purchaseRes);
|
||||
|
|
|
|||
Loading…
Reference in a new issue