mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-13 15:38:59 +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();
|
await members[0].sync();
|
||||||
expect(members[0].balance).to.equal(oldBalance);
|
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 {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import shared from '../../../../../website/common/script';
|
import shared from '../../../../../../website/common/script';
|
||||||
|
|
||||||
let content = shared.content;
|
let content = shared.content;
|
||||||
|
|
||||||
|
|
@ -82,4 +82,19 @@ describe('POST /user/buy/:key', () => {
|
||||||
itemText: item.text(),
|
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 {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST /user/buy-armoire', () => {
|
describe('POST /user/buy-armoire', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST /user/buy-gear/:key', () => {
|
describe('POST /user/buy-gear/:key', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import shared from '../../../../../website/common/script';
|
import shared from '../../../../../../website/common/script';
|
||||||
|
|
||||||
let content = shared.content;
|
let content = shared.content;
|
||||||
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
|
|
||||||
describe('POST /user/buy-mystery-set/:key', () => {
|
describe('POST /user/buy-mystery-set/:key', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import shared from '../../../../../website/common/script';
|
import shared from '../../../../../../website/common/script';
|
||||||
|
|
||||||
let content = shared.content;
|
let content = shared.content;
|
||||||
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
translate as t,
|
translate as t,
|
||||||
} from '../../../../helpers/api-integration/v3';
|
} from '../../../../../helpers/api-integration/v3';
|
||||||
import shared from '../../../../../website/common/script';
|
import shared from '../../../../../../website/common/script';
|
||||||
|
|
||||||
let content = shared.content;
|
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 {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import count from '../../../website/common/script/count';
|
import count from '../../../../website/common/script/count';
|
||||||
import buyArmoire from '../../../website/common/script/ops/buyArmoire';
|
import buyArmoire from '../../../../website/common/script/ops/buyArmoire';
|
||||||
import randomVal from '../../../website/common/script/libs/randomVal';
|
import randomVal from '../../../../website/common/script/libs/randomVal';
|
||||||
import content from '../../../website/common/script/content/index';
|
import content from '../../../../website/common/script/content/index';
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
function getFullArmoire () {
|
function getFullArmoire () {
|
||||||
let fullArmoire = {};
|
let fullArmoire = {};
|
||||||
|
|
@ -3,13 +3,13 @@
|
||||||
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
import sinon from 'sinon'; // eslint-disable-line no-shadow
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import buyGear from '../../../website/common/script/ops/buyGear';
|
import buyGear from '../../../../website/common/script/ops/buyGear';
|
||||||
import shared from '../../../website/common/script';
|
import shared from '../../../../website/common/script';
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
describe('shared.ops.buyGear', () => {
|
describe('shared.ops.buyGear', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
/* eslint-disable camelcase */
|
/* eslint-disable camelcase */
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import buyHealthPotion from '../../../website/common/script/ops/buyHealthPotion';
|
import buyHealthPotion from '../../../../website/common/script/ops/buyHealthPotion';
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
describe('shared.ops.buyHealthPotion', () => {
|
describe('shared.ops.buyHealthPotion', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -2,13 +2,13 @@
|
||||||
|
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import buyMysterySet from '../../../website/common/script/ops/buyMysterySet';
|
import buyMysterySet from '../../../../website/common/script/ops/buyMysterySet';
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
NotFound,
|
NotFound,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
describe('shared.ops.buyMysterySet', () => {
|
describe('shared.ops.buyMysterySet', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import buyQuest from '../../../website/common/script/ops/buyQuest';
|
import buyQuest from '../../../../website/common/script/ops/buyQuest';
|
||||||
import {
|
import {
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
NotFound,
|
NotFound,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
|
|
||||||
describe('shared.ops.buyQuest', () => {
|
describe('shared.ops.buyQuest', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
import buySpecialSpell from '../../../website/common/script/ops/buySpecialSpell';
|
import buySpecialSpell from '../../../../website/common/script/ops/buySpecialSpell';
|
||||||
import {
|
import {
|
||||||
BadRequest,
|
BadRequest,
|
||||||
NotFound,
|
NotFound,
|
||||||
NotAuthorized,
|
NotAuthorized,
|
||||||
} from '../../../website/common/script/libs/errors';
|
} from '../../../../website/common/script/libs/errors';
|
||||||
import i18n from '../../../website/common/script/i18n';
|
import i18n from '../../../../website/common/script/i18n';
|
||||||
import {
|
import {
|
||||||
generateUser,
|
generateUser,
|
||||||
} from '../../helpers/common.helper';
|
} from '../../../helpers/common.helper';
|
||||||
import content from '../../../website/common/script/content/index';
|
import content from '../../../../website/common/script/content/index';
|
||||||
|
|
||||||
describe('shared.ops.buySpecialSpell', () => {
|
describe('shared.ops.buySpecialSpell', () => {
|
||||||
let user;
|
let user;
|
||||||
|
|
@ -138,6 +138,7 @@ describe('shared.ops.purchase', () => {
|
||||||
user.balance = userGemAmount;
|
user.balance = userGemAmount;
|
||||||
user.stats.gp = goldPoints;
|
user.stats.gp = goldPoints;
|
||||||
user.purchased.plan.gemsBought = 0;
|
user.purchased.plan.gemsBought = 0;
|
||||||
|
user.purchased.plan.customerId = 'customer-id';
|
||||||
});
|
});
|
||||||
|
|
||||||
it('purchases gems', () => {
|
it('purchases gems', () => {
|
||||||
|
|
@ -226,4 +227,39 @@ describe('shared.ops.purchase', () => {
|
||||||
clock.restore();
|
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"
|
:item="item"
|
||||||
)
|
)
|
||||||
|
|
||||||
div(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value)}")
|
.purchase-amount(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}")
|
||||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
.how-many-to-buy(v-if='item.purchaseType !== "gear"')
|
||||||
span.value(:class="getPriceClass()") {{ item.value }}
|
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"')
|
.gems-left(v-if='item.key === "gem"')
|
||||||
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
|
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
|
||||||
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
|
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
|
||||||
|
|
||||||
|
div(v-if='attemptingToPurchaseMoreGemsThanAreLeft')
|
||||||
|
| {{$t('notEnoughGemsToBuy')}}
|
||||||
|
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
@click="purchaseGems()",
|
@click="purchaseGems()",
|
||||||
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value)"
|
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||||
) {{ $t('purchaseGems') }}
|
) {{ $t('purchaseGems') }}
|
||||||
|
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
@click="buyItem()",
|
@click="buyItem()",
|
||||||
v-else,
|
v-else,
|
||||||
:disabled='item.key === "gem" && gemsLeft === 0',
|
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft',
|
||||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value)}"
|
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||||
) {{ $t('buyNow') }}
|
) {{ $t('buyNow') }}
|
||||||
|
|
||||||
div.limitedTime(v-if="item.event")
|
div.limitedTime(v-if="item.event")
|
||||||
|
|
@ -101,6 +108,37 @@
|
||||||
width: 282px;
|
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 {
|
.content-text {
|
||||||
font-family: 'Roboto', sans-serif;
|
font-family: 'Roboto', sans-serif;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|
@ -217,6 +255,8 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
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 * as Analytics from 'client/libs/analytics';
|
||||||
import spellsMixin from 'client/mixins/spells';
|
import spellsMixin from 'client/mixins/spells';
|
||||||
import planGemLimits from 'common/script/libs/planGemLimits';
|
import planGemLimits from 'common/script/libs/planGemLimits';
|
||||||
|
|
@ -248,6 +288,8 @@
|
||||||
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
||||||
components: {
|
components: {
|
||||||
bModal,
|
bModal,
|
||||||
|
bDropdown,
|
||||||
|
bDropdownItem,
|
||||||
BalanceInfo,
|
BalanceInfo,
|
||||||
EquipmentAttributesGrid,
|
EquipmentAttributesGrid,
|
||||||
Item,
|
Item,
|
||||||
|
|
@ -264,6 +306,7 @@
|
||||||
clock: svgClock,
|
clock: svgClock,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
selectedAmountToBuy: 1,
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
@ -306,10 +349,15 @@
|
||||||
if (!this.user.purchased.plan) return 0;
|
if (!this.user.purchased.plan) return 0;
|
||||||
return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
|
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: {
|
watch: {
|
||||||
item: function itemChanged () {
|
item: function itemChanged () {
|
||||||
this.isPinned = this.item && this.item.pinned;
|
this.isPinned = this.item && this.item.pinned;
|
||||||
|
this.selectedAmountToBuy = 1;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
@ -320,7 +368,7 @@
|
||||||
if (this.item.cast) {
|
if (this.item.cast) {
|
||||||
this.castStart(this.item);
|
this.castStart(this.item);
|
||||||
} else if (this.genericPurchase) {
|
} else if (this.genericPurchase) {
|
||||||
this.makeGenericPurchase(this.item);
|
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||||
this.purchased(this.item.text);
|
this.purchased(this.item.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -355,7 +355,7 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.gems-left {
|
.market .gems-left {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: -.5em;
|
right: -.5em;
|
||||||
top: -.5em;
|
top: -.5em;
|
||||||
|
|
@ -739,11 +739,6 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemSelected (item) {
|
itemSelected (item) {
|
||||||
if (item.purchaseType !== 'gear' && this.$store.state.recentlyPurchased[item.key]) {
|
|
||||||
this.makeGenericPurchase(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$emit('buyModal::showItem', item);
|
this.$root.$emit('buyModal::showItem', item);
|
||||||
},
|
},
|
||||||
featuredItemSelected (item) {
|
featuredItemSelected (item) {
|
||||||
|
|
|
||||||
|
|
@ -18,20 +18,23 @@
|
||||||
div.inner-content
|
div.inner-content
|
||||||
questDialogContent(:item="item")
|
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.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||||
span.value(:class="priceType") {{ item.value }}
|
span.value(:class="priceType") {{ item.value }}
|
||||||
|
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
@click="purchaseGems()",
|
@click="purchaseGems()",
|
||||||
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value)"
|
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)"
|
||||||
) {{ $t('purchaseGems') }}
|
) {{ $t('purchaseGems') }}
|
||||||
|
|
||||||
|
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
@click="buyItem()",
|
@click="buyItem()",
|
||||||
v-else,
|
v-else,
|
||||||
:class="{'notEnough': !this.enoughCurrency(priceType, item.value)}"
|
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||||
) {{ $t('buyNow') }}
|
) {{ $t('buyNow') }}
|
||||||
|
|
||||||
div.right-sidebar(v-if="item.drop")
|
div.right-sidebar(v-if="item.drop")
|
||||||
|
|
@ -52,9 +55,12 @@
|
||||||
#buy-quest-modal {
|
#buy-quest-modal {
|
||||||
@include centeredModal();
|
@include centeredModal();
|
||||||
|
|
||||||
|
.modal-dialog {
|
||||||
|
margin-top: 25em;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
max-height: 80vh;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -167,6 +173,37 @@
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
opacity: 0.55;
|
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>
|
</style>
|
||||||
|
|
||||||
|
|
@ -210,6 +247,7 @@
|
||||||
}),
|
}),
|
||||||
|
|
||||||
isPinned: false,
|
isPinned: false,
|
||||||
|
selectedAmountToBuy: 1,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
|
@ -238,10 +276,11 @@
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange ($event) {
|
onChange ($event) {
|
||||||
|
this.selectedAmountToBuy = 1;
|
||||||
this.$emit('change', $event);
|
this.$emit('change', $event);
|
||||||
},
|
},
|
||||||
buyItem () {
|
buyItem () {
|
||||||
this.makeGenericPurchase(this.item, 'buyQuestModal');
|
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
|
||||||
this.purchased(this.item.text);
|
this.purchased(this.item.text);
|
||||||
this.hideDialog();
|
this.hideDialog();
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -477,11 +477,6 @@ export default {
|
||||||
|
|
||||||
this.selectedItemToBuy = item;
|
this.selectedItemToBuy = item;
|
||||||
|
|
||||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
|
||||||
this.makeGenericPurchase(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$emit('show::modal', 'buy-quest-modal');
|
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -502,12 +502,6 @@
|
||||||
},
|
},
|
||||||
itemSelected (item) {
|
itemSelected (item) {
|
||||||
if (item.locked) return;
|
if (item.locked) return;
|
||||||
|
|
||||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
|
||||||
this.makeGenericPurchase(item);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$emit('buyModal::showItem', item);
|
this.$root.$emit('buyModal::showItem', item);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.tasks-column(:class='type')
|
.tasks-column(:class='type')
|
||||||
b-modal(ref="editTaskModal")
|
b-modal(ref="editTaskModal")
|
||||||
|
buy-quest-modal(:item="selectedItemToBuy || {}",
|
||||||
|
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||||
|
:withPin="true",
|
||||||
|
@change="resetItemToBuy($event)"
|
||||||
|
v-if='type === "reward"')
|
||||||
.d-flex
|
.d-flex
|
||||||
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
||||||
.filters.d-flex.justify-content-end
|
.filters.d-flex.justify-content-end
|
||||||
|
|
@ -200,6 +205,7 @@ import sortable from 'client/directives/sortable.directive';
|
||||||
import buyMixin from 'client/mixins/buy';
|
import buyMixin from 'client/mixins/buy';
|
||||||
import { mapState, mapActions } from 'client/libs/store';
|
import { mapState, mapActions } from 'client/libs/store';
|
||||||
import shopItem from '../shops/shopItem';
|
import shopItem from '../shops/shopItem';
|
||||||
|
import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue';
|
||||||
|
|
||||||
import { shouldDo } from 'common/script/cron';
|
import { shouldDo } from 'common/script/cron';
|
||||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||||
|
|
@ -215,6 +221,7 @@ export default {
|
||||||
mixins: [buyMixin],
|
mixins: [buyMixin],
|
||||||
components: {
|
components: {
|
||||||
Task,
|
Task,
|
||||||
|
BuyQuestModal,
|
||||||
bModal,
|
bModal,
|
||||||
shopItem,
|
shopItem,
|
||||||
},
|
},
|
||||||
|
|
@ -278,6 +285,8 @@ export default {
|
||||||
|
|
||||||
forceRefresh: new Date(),
|
forceRefresh: new Date(),
|
||||||
quickAddText: '',
|
quickAddText: '',
|
||||||
|
|
||||||
|
selectedItemToBuy: {},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -479,10 +488,21 @@ export default {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rewardItem.purchaseType === 'quests') {
|
||||||
|
this.selectedItemToBuy = rewardItem;
|
||||||
|
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
|
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
|
||||||
this.$emit('openBuyDialog', rewardItem);
|
this.$emit('openBuyDialog', rewardItem);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
resetItemToBuy ($event) {
|
||||||
|
if (!$event) {
|
||||||
|
this.selectedItemToBuy = null;
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,14 @@
|
||||||
export default {
|
export default {
|
||||||
methods: {
|
methods: {
|
||||||
makeGenericPurchase (item, type = 'buyModal') {
|
makeGenericPurchase (item, type = 'buyModal', quantity = 1) {
|
||||||
this.$store.dispatch('shops:genericPurchase', {
|
this.$store.dispatch('shops:genericPurchase', {
|
||||||
pinType: item.pinType,
|
pinType: item.pinType,
|
||||||
type: item.purchaseType,
|
type: item.purchaseType,
|
||||||
key: item.key,
|
key: item.key,
|
||||||
currency: item.currency,
|
currency: item.currency,
|
||||||
|
quantity,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (item.purchaseType !== 'gear') {
|
|
||||||
this.$store.state.recentlyPurchased[item.key] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$root.$emit('playSound', 'Reward');
|
this.$root.$emit('playSound', 'Reward');
|
||||||
|
|
||||||
if (type !== 'buyModal') {
|
if (type !== 'buyModal') {
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,19 @@
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import buyOp from 'common/script/ops/buy';
|
import buyOp from 'common/script/ops/buy';
|
||||||
import buyQuestOp from 'common/script/ops/buyQuest';
|
|
||||||
import purchaseOp from 'common/script/ops/purchaseWithSpell';
|
import purchaseOp from 'common/script/ops/purchaseWithSpell';
|
||||||
import buyMysterySetOp from 'common/script/ops/buyMysterySet';
|
|
||||||
import hourglassPurchaseOp from 'common/script/ops/hourglassPurchase';
|
import hourglassPurchaseOp from 'common/script/ops/hourglassPurchase';
|
||||||
import sellOp from 'common/script/ops/sell';
|
import sellOp from 'common/script/ops/sell';
|
||||||
import unlockOp from 'common/script/ops/unlock';
|
import unlockOp from 'common/script/ops/unlock';
|
||||||
import buyArmoire from 'common/script/ops/buyArmoire';
|
|
||||||
import rerollOp from 'common/script/ops/reroll';
|
import rerollOp from 'common/script/ops/reroll';
|
||||||
import { getDropClass } from 'client/libs/notifications';
|
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) {
|
export function buyItem (store, params) {
|
||||||
|
const quantity = params.quantity || 1;
|
||||||
const user = store.state.user.data;
|
const user = store.state.user.data;
|
||||||
let opResult = buyOp(user, {params});
|
let opResult = buyOp(user, {params, quantity});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: opResult,
|
result: opResult,
|
||||||
|
|
@ -21,32 +22,77 @@ export function buyItem (store, params) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buyQuestItem (store, params) {
|
export function buyQuestItem (store, params) {
|
||||||
|
const quantity = params.quantity || 1;
|
||||||
const user = store.state.user.data;
|
const user = store.state.user.data;
|
||||||
let opResult = buyQuestOp(user, {params});
|
let opResult = buyOp(user, {
|
||||||
|
params,
|
||||||
|
type: 'quest',
|
||||||
|
quantity,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: opResult,
|
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) {
|
export function purchase (store, params) {
|
||||||
|
const quantity = params.quantity || 1;
|
||||||
const user = store.state.user.data;
|
const user = store.state.user.data;
|
||||||
let opResult = purchaseOp(user, {params});
|
let opResult = purchaseOp(user, {params, quantity});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: opResult,
|
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) {
|
export function purchaseMysterySet (store, params) {
|
||||||
const user = store.state.user.data;
|
const user = store.state.user.data;
|
||||||
let opResult = buyMysterySetOp(user, {params, noConfirm: true});
|
let opResult = buyOp(user, {params, noConfirm: true, type: 'mystery'});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
result: opResult,
|
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':
|
case 'mystery_set':
|
||||||
return purchaseMysterySet(store, params);
|
return purchaseMysterySet(store, params);
|
||||||
case 'armoire': // eslint-disable-line
|
case 'armoire': // eslint-disable-line
|
||||||
let buyResult = buyArmoire(store.state.user.data);
|
await buyArmoire(store, params);
|
||||||
|
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
case 'fortify': {
|
case 'fortify': {
|
||||||
let rerollResult = rerollOp(store.state.user.data);
|
let rerollResult = rerollOp(store.state.user.data);
|
||||||
|
|
@ -134,9 +155,5 @@ export async function genericPurchase (store, params) {
|
||||||
export function sellItems (store, params) {
|
export function sellItems (store, params) {
|
||||||
const user = store.state.user.data;
|
const user = store.state.user.data;
|
||||||
sellOp(user, {params, query: {amount: params.amount}});
|
sellOp(user, {params, query: {amount: params.amount}});
|
||||||
axios
|
axios.post(`/api/v3/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
|
||||||
.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));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,6 @@ export default function () {
|
||||||
modalStack: [],
|
modalStack: [],
|
||||||
brokenChallengeTask: {},
|
brokenChallengeTask: {},
|
||||||
equipmentDrawerOpen: true,
|
equipmentDrawerOpen: true,
|
||||||
recentlyPurchased: {},
|
|
||||||
groupPlans: [],
|
groupPlans: [],
|
||||||
groupNotifications: [],
|
groupNotifications: [],
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -281,5 +281,6 @@
|
||||||
"emptyMessagesLine2": "Send a message to start a conversation!",
|
"emptyMessagesLine2": "Send a message to start a conversation!",
|
||||||
"letsgo": "Let's Go!",
|
"letsgo": "Let's Go!",
|
||||||
"selected": "Selected",
|
"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?"
|
"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>.",
|
"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",
|
"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.",
|
"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 buyHealthPotion from './buyHealthPotion';
|
||||||
import buyArmoire from './buyArmoire';
|
import buyArmoire from './buyArmoire';
|
||||||
import buyGear from './buyGear';
|
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) {
|
module.exports = function buy (user, req = {}, analytics) {
|
||||||
let key = get(req, 'params.key');
|
let key = get(req, 'params.key');
|
||||||
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
|
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;
|
let buyRes;
|
||||||
if (key === 'potion') {
|
|
||||||
buyRes = buyHealthPotion(user, req, analytics);
|
for (let i = 0; i < quantity; i += 1) {
|
||||||
} else if (key === 'armoire') {
|
if (type === 'potion') {
|
||||||
buyRes = buyArmoire(user, req, analytics);
|
buyRes = buyHealthPotion(user, req, analytics);
|
||||||
} else {
|
} else if (type === 'armoire') {
|
||||||
buyRes = buyGear(user, req, analytics);
|
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;
|
return buyRes;
|
||||||
|
|
|
||||||
|
|
@ -14,68 +14,52 @@ import {
|
||||||
import { removeItemByPath } from './pinnedGearUtils';
|
import { removeItemByPath } from './pinnedGearUtils';
|
||||||
import getItemInfo from '../libs/getItemInfo';
|
import getItemInfo from '../libs/getItemInfo';
|
||||||
|
|
||||||
module.exports = function purchase (user, req = {}, analytics) {
|
function buyGems (user, analytics, req, key) {
|
||||||
let type = get(req.params, 'type');
|
let convRate = planGemLimits.convRate;
|
||||||
let key = get(req.params, 'key');
|
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 item;
|
||||||
let price;
|
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') {
|
if (type === 'gear') {
|
||||||
item = content.gear.flat[key];
|
item = content.gear.flat[key];
|
||||||
|
|
||||||
|
|
@ -98,17 +82,10 @@ module.exports = function purchase (user, req = {}, analytics) {
|
||||||
price = item.value / 4;
|
price = item.value / 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!item.canBuy(user)) {
|
return {item, price};
|
||||||
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);
|
|
||||||
|
|
||||||
|
function purchaseItem (user, item, price, type, key) {
|
||||||
user.balance -= price;
|
user.balance -= price;
|
||||||
|
|
||||||
if (type === 'gear') {
|
if (type === 'gear') {
|
||||||
|
|
@ -127,6 +104,50 @@ module.exports = function purchase (user, req = {}, analytics) {
|
||||||
}
|
}
|
||||||
user.items[type][key]++;
|
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) {
|
if (analytics) {
|
||||||
analytics.track('acquire item', {
|
analytics.track('acquire item', {
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
import buySpecialSpellOp from './buySpecialSpell';
|
import buy from './buy';
|
||||||
import purchaseOp from './purchase';
|
import purchaseOp from './purchase';
|
||||||
import get from 'lodash/get';
|
import get from 'lodash/get';
|
||||||
|
|
||||||
module.exports = function purchaseWithSpell (user, req = {}, analytics) {
|
module.exports = function purchaseWithSpell (user, req = {}, analytics) {
|
||||||
const type = get(req.params, 'type');
|
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 buyRes;
|
||||||
let specialKeys = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
|
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) {
|
if (specialKeys.indexOf(req.params.key) !== -1) {
|
||||||
buyRes = common.ops.buySpecialSpell(user, req);
|
type = 'special';
|
||||||
} else {
|
|
||||||
buyRes = common.ops.buy(user, req, res.analytics);
|
|
||||||
}
|
}
|
||||||
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyRes);
|
res.respond(200, ...buyRes);
|
||||||
|
|
@ -926,7 +936,7 @@ api.buyGear = {
|
||||||
url: '/user/buy-gear/:key',
|
url: '/user/buy-gear/:key',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyGearRes);
|
res.respond(200, ...buyGearRes);
|
||||||
},
|
},
|
||||||
|
|
@ -966,7 +976,9 @@ api.buyArmoire = {
|
||||||
url: '/user/buy-armoire',
|
url: '/user/buy-armoire',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyArmoireResponse);
|
res.respond(200, ...buyArmoireResponse);
|
||||||
},
|
},
|
||||||
|
|
@ -1004,7 +1016,9 @@ api.buyHealthPotion = {
|
||||||
url: '/user/buy-health-potion',
|
url: '/user/buy-health-potion',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyHealthPotionResponse);
|
res.respond(200, ...buyHealthPotionResponse);
|
||||||
},
|
},
|
||||||
|
|
@ -1044,7 +1058,8 @@ api.buyMysterySet = {
|
||||||
url: '/user/buy-mystery-set/:key',
|
url: '/user/buy-mystery-set/:key',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyMysterySetRes);
|
res.respond(200, ...buyMysterySetRes);
|
||||||
},
|
},
|
||||||
|
|
@ -1084,7 +1099,8 @@ api.buyQuest = {
|
||||||
url: '/user/buy-quest/:key',
|
url: '/user/buy-quest/:key',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buyQuestRes);
|
res.respond(200, ...buyQuestRes);
|
||||||
},
|
},
|
||||||
|
|
@ -1123,7 +1139,8 @@ api.buySpecialSpell = {
|
||||||
url: '/user/buy-special-spell/:key',
|
url: '/user/buy-special-spell/:key',
|
||||||
async handler (req, res) {
|
async handler (req, res) {
|
||||||
let user = res.locals.user;
|
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();
|
await user.save();
|
||||||
res.respond(200, ...buySpecialSpellRes);
|
res.respond(200, ...buySpecialSpellRes);
|
||||||
},
|
},
|
||||||
|
|
@ -1337,6 +1354,11 @@ api.purchase = {
|
||||||
if (!canGetGems) throw new NotAuthorized(res.t('groupPolicyCannotGetGems'));
|
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);
|
let purchaseRes = common.ops.purchaseWithSpell(user, req, res.analytics);
|
||||||
await user.save();
|
await user.save();
|
||||||
res.respond(200, ...purchaseRes);
|
res.respond(200, ...purchaseRes);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue