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:
Keith Holliday
2017-10-23 16:05:35 -05:00
committed by GitHub
parent b74cee3d21
commit 7fe2504906
32 changed files with 578 additions and 265 deletions
+55 -7
View File
@@ -41,25 +41,32 @@
:item="item"
)
div(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value)}")
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
span.value(:class="getPriceClass()") {{ item.value }}
.purchase-amount(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}")
.how-many-to-buy(v-if='item.purchaseType !== "gear"')
strong {{ $t('howManyToBuy') }}
div(v-if='item.purchaseType !== "gear"')
.box
input(type='number', min='0', v-model='selectedAmountToBuy')
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
span.value(:class="getPriceClass()") {{ item.value }}
.gems-left(v-if='item.key === "gem"')
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
div(v-if='attemptingToPurchaseMoreGemsThanAreLeft')
| {{$t('notEnoughGemsToBuy')}}
button.btn.btn-primary(
@click="purchaseGems()",
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value)"
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
) {{ $t('purchaseGems') }}
button.btn.btn-primary(
@click="buyItem()",
v-else,
:disabled='item.key === "gem" && gemsLeft === 0',
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value)}"
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft',
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
) {{ $t('buyNow') }}
div.limitedTime(v-if="item.event")
@@ -101,6 +108,37 @@
width: 282px;
}
.purchase-amount {
margin-top: 24px;
.how-many-to-buy {
margin-bottom: 16px;
}
.box {
display: inline-block;
width: 74px;
height: 40px;
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
margin-right: 24px;
input {
width: 100%;
border: none;
}
input::-webkit-contacts-auto-fill-button {
visibility: hidden;
display: none !important;
pointer-events: none;
position: absolute;
right: 0;
}
}
}
.content-text {
font-family: 'Roboto', sans-serif;
font-size: 14px;
@@ -217,6 +255,8 @@
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import * as Analytics from 'client/libs/analytics';
import spellsMixin from 'client/mixins/spells';
import planGemLimits from 'common/script/libs/planGemLimits';
@@ -248,6 +288,8 @@
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
components: {
bModal,
bDropdown,
bDropdownItem,
BalanceInfo,
EquipmentAttributesGrid,
Item,
@@ -264,6 +306,7 @@
clock: svgClock,
}),
selectedAmountToBuy: 1,
isPinned: false,
};
},
@@ -306,10 +349,15 @@
if (!this.user.purchased.plan) return 0;
return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
},
attemptingToPurchaseMoreGemsThanAreLeft () {
if (this.item && this.item.key && this.item.key === 'gem' && this.selectedAmountToBuy > this.gemsLeft) return true;
return false;
},
},
watch: {
item: function itemChanged () {
this.isPinned = this.item && this.item.pinned;
this.selectedAmountToBuy = 1;
},
},
methods: {
@@ -320,7 +368,7 @@
if (this.item.cast) {
this.castStart(this.item);
} else if (this.genericPurchase) {
this.makeGenericPurchase(this.item);
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
this.purchased(this.item.text);
}
@@ -355,7 +355,7 @@
}
.gems-left {
.market .gems-left {
position: absolute;
right: -.5em;
top: -.5em;
@@ -739,11 +739,6 @@ export default {
}
},
itemSelected (item) {
if (item.purchaseType !== 'gear' && this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('buyModal::showItem', item);
},
featuredItemSelected (item) {
@@ -18,20 +18,23 @@
div.inner-content
questDialogContent(:item="item")
div
.purchase-amount
.how-many-to-buy
strong {{ $t('howManyToBuy') }}
.box
input(type='number', min='0', v-model='selectedAmountToBuy')
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
span.value(:class="priceType") {{ item.value }}
button.btn.btn-primary(
@click="purchaseGems()",
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value)"
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)"
) {{ $t('purchaseGems') }}
button.btn.btn-primary(
@click="buyItem()",
v-else,
:class="{'notEnough': !this.enoughCurrency(priceType, item.value)}"
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
) {{ $t('buyNow') }}
div.right-sidebar(v-if="item.drop")
@@ -52,9 +55,12 @@
#buy-quest-modal {
@include centeredModal();
.modal-dialog {
margin-top: 25em;
}
.content {
text-align: center;
max-height: 80vh;
overflow-y: scroll;
}
@@ -167,6 +173,37 @@
pointer-events: none;
opacity: 0.55;
}
.purchase-amount {
margin-top: 24px;
.how-many-to-buy {
margin-bottom: 16px;
}
.box {
display: inline-block;
width: 74px;
height: 40px;
border-radius: 2px;
background-color: #ffffff;
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
margin-right: 24px;
input {
width: 100%;
border: none;
}
input::-webkit-contacts-auto-fill-button {
visibility: hidden;
display: none !important;
pointer-events: none;
position: absolute;
right: 0;
}
}
}
}
</style>
@@ -210,6 +247,7 @@
}),
isPinned: false,
selectedAmountToBuy: 1,
};
},
watch: {
@@ -238,10 +276,11 @@
},
methods: {
onChange ($event) {
this.selectedAmountToBuy = 1;
this.$emit('change', $event);
},
buyItem () {
this.makeGenericPurchase(this.item, 'buyQuestModal');
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
this.purchased(this.item.text);
this.hideDialog();
},
@@ -477,11 +477,6 @@ export default {
this.selectedItemToBuy = item;
if (this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('show::modal', 'buy-quest-modal');
},
},
@@ -502,12 +502,6 @@
},
itemSelected (item) {
if (item.locked) return;
if (this.$store.state.recentlyPurchased[item.key]) {
this.makeGenericPurchase(item);
return;
}
this.$root.$emit('buyModal::showItem', item);
},
},
@@ -1,6 +1,11 @@
<template lang="pug">
.tasks-column(:class='type')
b-modal(ref="editTaskModal")
buy-quest-modal(:item="selectedItemToBuy || {}",
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
:withPin="true",
@change="resetItemToBuy($event)"
v-if='type === "reward"')
.d-flex
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
.filters.d-flex.justify-content-end
@@ -200,6 +205,7 @@ import sortable from 'client/directives/sortable.directive';
import buyMixin from 'client/mixins/buy';
import { mapState, mapActions } from 'client/libs/store';
import shopItem from '../shops/shopItem';
import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue';
import { shouldDo } from 'common/script/cron';
import inAppRewards from 'common/script/libs/inAppRewards';
@@ -215,6 +221,7 @@ export default {
mixins: [buyMixin],
components: {
Task,
BuyQuestModal,
bModal,
shopItem,
},
@@ -278,6 +285,8 @@ export default {
forceRefresh: new Date(),
quickAddText: '',
selectedItemToBuy: {},
};
},
computed: {
@@ -479,10 +488,21 @@ export default {
return;
}
if (rewardItem.purchaseType === 'quests') {
this.selectedItemToBuy = rewardItem;
this.$root.$emit('show::modal', 'buy-quest-modal');
return;
}
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
this.$emit('openBuyDialog', rewardItem);
}
},
resetItemToBuy ($event) {
if (!$event) {
this.selectedItemToBuy = null;
}
},
},
};
</script>
+2 -5
View File
@@ -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') {
+58 -41
View File
@@ -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}`);
}
-1
View File
@@ -136,7 +136,6 @@ export default function () {
modalStack: [],
brokenChallengeTask: {},
equipmentDrawerOpen: true,
recentlyPurchased: {},
groupPlans: [],
groupNotifications: [],
},