mirror of
https://github.com/HabitRPG/habitica.git
synced 2026-04-04 05:33:52 -05:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
54f57445da | ||
|
|
95ef2b1789 | ||
|
|
4d32977e5c | ||
|
|
7fe2504906 | ||
|
|
b74cee3d21 | ||
|
|
5af7733150 | ||
|
|
824bf62e0a | ||
|
|
ac24a5dddd | ||
|
|
9111f59da4 |
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"version": "4.4.5",
|
||||
"version": "4.5.0",
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "habitica",
|
||||
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
|
||||
"version": "4.4.5",
|
||||
"version": "4.5.0",
|
||||
"main": "./website/server/index.js",
|
||||
"dependencies": {
|
||||
"@slack/client": "^3.8.1",
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
generateNext,
|
||||
} from '../../../../helpers/api-unit.helper';
|
||||
import responseMiddleware from '../../../../../website/server/middlewares/response';
|
||||
import packageInfo from '../../../../../package.json';
|
||||
|
||||
describe('response middleware', () => {
|
||||
let res, req, next;
|
||||
@@ -34,6 +35,7 @@ describe('response middleware', () => {
|
||||
data: {field: 1},
|
||||
notifications: [],
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,6 +53,7 @@ describe('response middleware', () => {
|
||||
message: 'hello',
|
||||
notifications: [],
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,6 +70,7 @@ describe('response middleware', () => {
|
||||
data: {field: 1},
|
||||
notifications: [],
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,6 +85,7 @@ describe('response middleware', () => {
|
||||
data: {field: 1},
|
||||
notifications: [],
|
||||
userV: 0,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -104,6 +109,7 @@ describe('response middleware', () => {
|
||||
},
|
||||
],
|
||||
userV: res.locals.user._v,
|
||||
appVersion: packageInfo.version,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -191,6 +191,7 @@ export default {
|
||||
|
||||
const isApiCall = url.indexOf('api/v3') !== -1;
|
||||
const userV = response.data && response.data.userV;
|
||||
const isCron = url.indexOf('/api/v3/cron') === 0 && method === 'post';
|
||||
|
||||
if (this.isUserLoaded && isApiCall && userV) {
|
||||
const oldUserV = this.user._v;
|
||||
@@ -202,7 +203,6 @@ export default {
|
||||
// exclude chat seen requests because with real time chat they would be too many
|
||||
const isChatSeen = url.indexOf('/chat/seen') !== -1 && method === 'post';
|
||||
// exclude POST /api/v3/cron because the user is synced automatically after cron runs
|
||||
const isCron = url.indexOf('/api/v3/cron') === 0 && method === 'post';
|
||||
|
||||
// Something has changed on the user object that was not tracked here, sync the user
|
||||
if (userV - oldUserV > 1 && !isCron && !isChatSeen && !isUserSync && !isTasksSync) {
|
||||
@@ -213,6 +213,21 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the client is updated
|
||||
// const serverAppVersion = response.data.appVersion;
|
||||
// let serverAppVersionState = this.$store.state.serverAppVersion;
|
||||
// let deniedUpdate = this.$store.state.deniedUpdate;
|
||||
// if (isApiCall && !serverAppVersionState) {
|
||||
// this.$store.state.serverAppVersion = serverAppVersion;
|
||||
// } else if (isApiCall && serverAppVersionState !== serverAppVersion && !deniedUpdate || isCron) {
|
||||
// // For reload on cron
|
||||
// if (isCron || confirm(this.$t('habiticaHasUpdated'))) {
|
||||
// location.reload(true);
|
||||
// } else {
|
||||
// this.$store.state.deniedUpdate = true;
|
||||
// }
|
||||
// }
|
||||
|
||||
return response;
|
||||
});
|
||||
|
||||
|
||||
@@ -86,15 +86,12 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(`skin.${set.keys.join(",skin.")}`)') {{ $t('purchaseAll') }}
|
||||
#hair.section.customize-section(v-if='activeTopPage === "hair"')
|
||||
.row.sub-menu.text-center
|
||||
.col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color"}')
|
||||
.row.col-12.sub-menu.text-center
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("color")', :class='{active: activeSubPage === "color"}')
|
||||
strong(v-once) {{$t('color')}}
|
||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("bangs")', :class='{active: activeSubPage === "bangs"}')
|
||||
strong(v-once) {{$t('bangs')}}
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("ponytail")', :class='{active: activeSubPage === "ponytail"}')
|
||||
strong(v-once) {{$t('ponytail')}}
|
||||
.row.sub-menu.text-center
|
||||
.col-3.offset-3.text-center.sub-menu-item(@click='changeSubPage("style")', :class='{active: activeSubPage === "style"}', v-if='editing')
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("style")', :class='{active: activeSubPage === "style"}', v-if='editing')
|
||||
strong(v-once) {{$t('style')}}
|
||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("facialhair")', :class='{active: activeSubPage === "facialhair"}', v-if='editing')
|
||||
strong(v-once) {{$t('facialhair')}}
|
||||
@@ -141,14 +138,6 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair4Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
|
||||
#bangs.row(v-if='activeSubPage === "bangs"')
|
||||
.col-12.customize-options
|
||||
.head_0.option(@click='set({"preferences.hair.bangs": 0})',
|
||||
:class="[{ active: user.preferences.hair.bangs === 0 }, 'hair_bangs_0_' + user.preferences.hair.color]")
|
||||
.option(v-for='option in ["1", "2", "3", "4"]',
|
||||
:class='{active: user.preferences.hair.bangs === option}')
|
||||
.bangs.sprite.customize-option(:class="`hair_bangs_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.bangs": option})')
|
||||
#base-hair.row(v-if='activeSubPage === "ponytail"')
|
||||
.col-12.customize-options
|
||||
.head_0.option(@click='set({"preferences.hair.base": 0})', :class="[{ active: user.preferences.hair.base === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
||||
.option(v-for='option in baseHair1',
|
||||
@@ -166,6 +155,13 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 5
|
||||
button.btn.btn-secondary.purchase-all(@click='unlock(`hair.base.${baseHair2Keys.join(",hair.base.")}`)') {{ $t('purchaseAll') }}
|
||||
#bangs.row(v-if='activeSubPage === "bangs"')
|
||||
.col-12.customize-options
|
||||
.head_0.option(@click='set({"preferences.hair.bangs": 0})',
|
||||
:class="[{ active: user.preferences.hair.bangs === 0 }, 'hair_bangs_0_' + user.preferences.hair.color]")
|
||||
.option(v-for='option in ["1", "2", "3", "4"]',
|
||||
:class='{active: user.preferences.hair.bangs === option}')
|
||||
.bangs.sprite.customize-option(:class="`hair_bangs_${option}_${user.preferences.hair.color}`", @click='set({"preferences.hair.bangs": option})')
|
||||
#facialhair.row(v-if='activeSubPage === "facialhair"')
|
||||
.col-12.customize-options(v-if='editing')
|
||||
.head_0.option(@click='set({"preferences.hair.beard": 0})', :class="[{ active: user.preferences.hair.beard === 0 }, 'hair_base_0_' + user.preferences.hair.color]")
|
||||
@@ -285,7 +281,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
||||
.col-12.text-center(v-if='!ownsSet("background", set.items) && set.identifier !== "incentiveBackgrounds"')
|
||||
.gem-amount
|
||||
.svg-icon.gem(v-html='icons.gem')
|
||||
span 5
|
||||
span 15
|
||||
button.btn.btn-secondary(@click='unlock(setKeys("background", set.items))') Purchase Set
|
||||
|
||||
.container.interests-section(v-if='modalPage === 3 && !editing')
|
||||
|
||||
@@ -2,14 +2,18 @@
|
||||
.row.standard-page
|
||||
.col-6
|
||||
h2 {{ $t('API') }}
|
||||
small {{ $t('APIText') }}
|
||||
p {{ $t('APIText') }}
|
||||
|
||||
.section
|
||||
h6 {{ $t('userId') }}
|
||||
pre.prettyprint {{user.id}}
|
||||
h6 {{ $t('APIToken') }}
|
||||
pre.prettyprint {{apiToken}}
|
||||
small(v-html='$t("APITokenWarning", { hrefTechAssistanceEmail })')
|
||||
.d-flex.align-items-center.mb-3
|
||||
button.btn.btn-secondary(
|
||||
@click="showApiToken = !showApiToken"
|
||||
) {{ $t(`${showApiToken ? 'hide' : 'show'}APIToken`) }}
|
||||
pre.prettyprint.ml-4.mb-0(v-if="showApiToken") {{apiToken}}
|
||||
p(v-html='$t("APITokenWarning", { hrefTechAssistanceEmail })')
|
||||
|
||||
.section
|
||||
h3 {{ $t('thirdPartyApps') }}
|
||||
@@ -78,6 +82,7 @@ export default {
|
||||
url: '',
|
||||
},
|
||||
hrefTechAssistanceEmail: `<a href="mailto:${TECH_ASSISTANCE_EMAIL}">${TECH_ASSISTANCE_EMAIL}</a>`,
|
||||
showApiToken: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
|
||||
@@ -115,7 +115,8 @@
|
||||
div
|
||||
ul.list-inline
|
||||
li(v-for='network in SOCIAL_AUTH_NETWORKS')
|
||||
button.btn.btn-primary(v-if='!user.auth[network.key].id', @click='socialLogin(network.key, user)') {{ $t('registerWithSocial', {network: network.name}) }}
|
||||
// @TODO this is broken
|
||||
button.btn.btn-primary(v-if='!user.auth[network.key].id', @click='socialAuth(network.key, user)') {{ $t('registerWithSocial', {network: network.name}) }}
|
||||
button.btn.btn-primary(disabled='disabled', v-if='!hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('registeredWithSocial', {network: network.name}) }}
|
||||
button.btn.btn-danger(@click='deleteSocialAuth(network.key)', v-if='hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('detachSocial', {network: network.name}) }}
|
||||
hr
|
||||
@@ -377,7 +378,7 @@ export default {
|
||||
auth,
|
||||
});
|
||||
|
||||
this.$router.go('/tasks');
|
||||
window.location.href = '/';
|
||||
},
|
||||
async changeClassForUser (confirmationNeeded) {
|
||||
if (confirmationNeeded && !confirm(this.$t('changeClassConfirmCost'))) return;
|
||||
|
||||
@@ -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
|
||||
@@ -15,7 +20,7 @@
|
||||
v-model="quickAddText", @keyup.enter="quickAdd",
|
||||
ref="quickAdd",
|
||||
)
|
||||
.sortable-tasks(ref="tasksList", v-sortable='', @onsort='sorted')
|
||||
.sortable-tasks(ref="tasksList", v-sortable='activeFilters[type].label !== "scheduled"', @onsort='sorted', data-sortableId)
|
||||
task(
|
||||
v-for="task in taskList",
|
||||
:key="task.id", :task="task",
|
||||
@@ -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: {
|
||||
@@ -288,8 +297,25 @@ export default {
|
||||
}),
|
||||
taskList () {
|
||||
// @TODO: This should not default to user's tasks. It should require that you pass options in
|
||||
if (this.taskListOverride) return this.taskListOverride;
|
||||
return this.tasks[`${this.type}s`];
|
||||
const filter = this.activeFilters[this.type];
|
||||
|
||||
let taskList = this.tasks[`${this.type}s`];
|
||||
if (this.taskListOverride) taskList = this.taskListOverride;
|
||||
|
||||
if (taskList.length > 0 && ['scheduled', 'due'].indexOf(filter.label) === -1) {
|
||||
let taskListSorted = this.$store.dispatch('tasks:order', [
|
||||
taskList,
|
||||
this.user.tasksOrder,
|
||||
]);
|
||||
|
||||
taskList = taskListSorted[`${this.type}s`];
|
||||
}
|
||||
|
||||
if (filter.sort) {
|
||||
taskList = sortBy(taskList, filter.sort);
|
||||
}
|
||||
|
||||
return taskList;
|
||||
},
|
||||
inAppRewards () {
|
||||
let watchRefresh = this.forceRefresh; // eslint-disable-line
|
||||
@@ -365,19 +391,25 @@ export default {
|
||||
loadCompletedTodos: 'tasks:fetchCompletedTodos',
|
||||
createTask: 'tasks:create',
|
||||
}),
|
||||
sorted (data) {
|
||||
async sorted (data) {
|
||||
const filteredList = this.taskList.filter(this.activeFilters[this.type].filter);
|
||||
const sorting = this.taskList;
|
||||
const taskIdToMove = this.taskList[data.oldIndex]._id;
|
||||
const taskIdToMove = filteredList[data.oldIndex]._id;
|
||||
|
||||
// Server
|
||||
const taskIdToReplace = filteredList[data.newIndex];
|
||||
const newIndexOnServer = this.taskList.findIndex(taskId => taskId === taskIdToReplace);
|
||||
let newOrder = await this.$store.dispatch('tasks:move', {
|
||||
taskId: taskIdToMove,
|
||||
position: newIndexOnServer,
|
||||
});
|
||||
this.user.tasksOrder[`${this.type}s`] = newOrder;
|
||||
|
||||
// Client
|
||||
if (sorting) {
|
||||
const deleted = sorting.splice(data.oldIndex, 1);
|
||||
sorting.splice(data.newIndex, 0, deleted[0]);
|
||||
}
|
||||
|
||||
this.$store.dispatch('tasks:move', {
|
||||
taskId: taskIdToMove,
|
||||
position: data.newIndex,
|
||||
});
|
||||
},
|
||||
quickAdd () {
|
||||
const task = taskDefaults({type: this.type, text: this.quickAddText});
|
||||
@@ -393,10 +425,6 @@ export default {
|
||||
this.loadCompletedTodos();
|
||||
}
|
||||
this.activeFilters[type] = filter;
|
||||
|
||||
if (filter.sort) {
|
||||
this.tasks[`${type}s`] = sortBy(this.tasks[`${type}s`], filter.sort);
|
||||
}
|
||||
},
|
||||
setColumnBackgroundVisibility () {
|
||||
this.$nextTick(() => {
|
||||
@@ -465,10 +493,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,23 +1,45 @@
|
||||
import Sortable from 'sortablejs';
|
||||
import uuid from 'uuid';
|
||||
|
||||
let emit = (vnode, eventName, data) => {
|
||||
let handlers = vnode.data && vnode.data.on ||
|
||||
vnode.componentOptions && vnode.componentOptions.listeners;
|
||||
let emit = (vNode, eventName, data) => {
|
||||
let handlers = vNode.data && vNode.data.on ||
|
||||
vNode.componentOptions && vNode.componentOptions.listeners;
|
||||
|
||||
if (handlers && handlers[eventName]) {
|
||||
handlers[eventName].fns(data);
|
||||
}
|
||||
};
|
||||
|
||||
let sortableReferences = {};
|
||||
|
||||
function createSortable (el, vNode) {
|
||||
let sortableRef = Sortable.create(el, {
|
||||
onSort: (evt) => {
|
||||
emit(vNode, 'onsort', {
|
||||
oldIndex: evt.oldIndex,
|
||||
newIndex: evt.newIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
let uniqueId = uuid();
|
||||
sortableReferences[uniqueId] = sortableRef;
|
||||
el.dataset.sortableId = uniqueId;
|
||||
}
|
||||
|
||||
export default {
|
||||
bind (el, binding, vnode) {
|
||||
Sortable.create(el, {
|
||||
onSort: (evt) => {
|
||||
emit(vnode, 'onsort', {
|
||||
oldIndex: evt.oldIndex,
|
||||
newIndex: evt.newIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
bind (el, binding, vNode) {
|
||||
createSortable(el, vNode);
|
||||
},
|
||||
unbind (el) {
|
||||
if (sortableReferences[el.dataset.sortableId]) sortableReferences[el.dataset.sortableId].destroy();
|
||||
},
|
||||
update (el, vNode) {
|
||||
if (sortableReferences[el.dataset.sortableId] && !vNode.value) {
|
||||
sortableReferences[el.dataset.sortableId].destroy();
|
||||
delete sortableReferences[el.dataset.sortableId];
|
||||
return;
|
||||
}
|
||||
if (!sortableReferences[el.dataset.sortableId]) createSortable(el, vNode);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
|
||||
@@ -55,6 +55,8 @@ export default function () {
|
||||
actions,
|
||||
getters,
|
||||
state: {
|
||||
serverAppVersion: '',
|
||||
deniedUpdate: false,
|
||||
title: 'Habitica',
|
||||
isUserLoggedIn,
|
||||
isUserLoaded: false, // Means the user and the user's tasks are ready
|
||||
@@ -134,7 +136,6 @@ export default function () {
|
||||
modalStack: [],
|
||||
brokenChallengeTask: {},
|
||||
equipmentDrawerOpen: true,
|
||||
recentlyPurchased: {},
|
||||
groupPlans: [],
|
||||
groupNotifications: [],
|
||||
},
|
||||
|
||||
@@ -280,5 +280,7 @@
|
||||
"emptyMessagesLine1": "You don't have any messages",
|
||||
"emptyMessagesLine2": "Send a message to start a conversation!",
|
||||
"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?"
|
||||
}
|
||||
|
||||
@@ -72,7 +72,9 @@
|
||||
"APIv3": "API v3",
|
||||
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
|
||||
"APIToken": "API Token (this is a password - see warning above!)",
|
||||
"APITokenWarning": "If you need a new API Token (e.g., if you accidentally shared it), email <%= hrefTechAssistanceEmail %> with your User ID and current Token. Once it is reset you will need to re-authorize everything by logging out of the website and mobile app and by providing the new Token to any other Habitica tools that you use.",
|
||||
"showAPIToken": "Show API Token",
|
||||
"hideAPIToken": "Hide API Token",
|
||||
"APITokenWarning": "If you need a new API Token (e.g., if you accidentally shared it), email <%= hrefTechAssistanceEmail %> with your User ID and current Token. Once it is reset you will need to re-authorize everything by logging out of the website and mobile app and by providing the new Token to any other Habitica tools that you use.",
|
||||
"thirdPartyApps": "Third Party Apps",
|
||||
"dataToolDesc": "A webpage that shows you certain information from your Habitica account, such as statistics about your tasks, equipment, and skills.",
|
||||
"beeminder": "Beeminder",
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import packageInfo from '../../../package.json';
|
||||
|
||||
module.exports = function responseHandler (req, res, next) {
|
||||
// Only used for successful responses
|
||||
res.respond = function respond (status = 200, data = {}, message) {
|
||||
@@ -15,6 +17,8 @@ module.exports = function responseHandler (req, res, next) {
|
||||
response.userV = user._v;
|
||||
}
|
||||
|
||||
response.appVersion = packageInfo.version;
|
||||
|
||||
res.status(status).json(response);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user