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:
@@ -41,25 +41,32 @@
|
||||
:item="item"
|
||||
)
|
||||
|
||||
div(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value)}")
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
||||
span.value(:class="getPriceClass()") {{ item.value }}
|
||||
.purchase-amount(:class="{'notEnough': !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}")
|
||||
.how-many-to-buy(v-if='item.purchaseType !== "gear"')
|
||||
strong {{ $t('howManyToBuy') }}
|
||||
div(v-if='item.purchaseType !== "gear"')
|
||||
.box
|
||||
input(type='number', min='0', v-model='selectedAmountToBuy')
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="icons[getPriceClass()]")
|
||||
span.value(:class="getPriceClass()") {{ item.value }}
|
||||
|
||||
.gems-left(v-if='item.key === "gem"')
|
||||
strong(v-if='gemsLeft > 0') {{ gemsLeft }} {{ $t('gemsRemaining') }}
|
||||
strong(v-if='gemsLeft === 0') {{ $t('maxBuyGems') }}
|
||||
|
||||
div(v-if='attemptingToPurchaseMoreGemsThanAreLeft')
|
||||
| {{$t('notEnoughGemsToBuy')}}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="purchaseGems()",
|
||||
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value)"
|
||||
v-if="getPriceClass() === 'gems' && !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
) {{ $t('purchaseGems') }}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="buyItem()",
|
||||
v-else,
|
||||
:disabled='item.key === "gem" && gemsLeft === 0',
|
||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value)}"
|
||||
:disabled='item.key === "gem" && gemsLeft === 0 || attemptingToPurchaseMoreGemsThanAreLeft',
|
||||
:class="{'notEnough': !preventHealthPotion || !this.enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
) {{ $t('buyNow') }}
|
||||
|
||||
div.limitedTime(v-if="item.event")
|
||||
@@ -101,6 +108,37 @@
|
||||
width: 282px;
|
||||
}
|
||||
|
||||
.purchase-amount {
|
||||
margin-top: 24px;
|
||||
|
||||
.how-many-to-buy {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 74px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-right: 24px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
visibility: hidden;
|
||||
display: none !important;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.content-text {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
font-size: 14px;
|
||||
@@ -217,6 +255,8 @@
|
||||
|
||||
<script>
|
||||
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
|
||||
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
import spellsMixin from 'client/mixins/spells';
|
||||
import planGemLimits from 'common/script/libs/planGemLimits';
|
||||
@@ -248,6 +288,8 @@
|
||||
mixins: [currencyMixin, notifications, spellsMixin, buyMixin],
|
||||
components: {
|
||||
bModal,
|
||||
bDropdown,
|
||||
bDropdownItem,
|
||||
BalanceInfo,
|
||||
EquipmentAttributesGrid,
|
||||
Item,
|
||||
@@ -264,6 +306,7 @@
|
||||
clock: svgClock,
|
||||
}),
|
||||
|
||||
selectedAmountToBuy: 1,
|
||||
isPinned: false,
|
||||
};
|
||||
},
|
||||
@@ -306,10 +349,15 @@
|
||||
if (!this.user.purchased.plan) return 0;
|
||||
return planGemLimits.convCap + this.user.purchased.plan.consecutive.gemCapExtra - this.user.purchased.plan.gemsBought;
|
||||
},
|
||||
attemptingToPurchaseMoreGemsThanAreLeft () {
|
||||
if (this.item && this.item.key && this.item.key === 'gem' && this.selectedAmountToBuy > this.gemsLeft) return true;
|
||||
return false;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
item: function itemChanged () {
|
||||
this.isPinned = this.item && this.item.pinned;
|
||||
this.selectedAmountToBuy = 1;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -320,7 +368,7 @@
|
||||
if (this.item.cast) {
|
||||
this.castStart(this.item);
|
||||
} else if (this.genericPurchase) {
|
||||
this.makeGenericPurchase(this.item);
|
||||
this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||
this.purchased(this.item.text);
|
||||
}
|
||||
|
||||
|
||||
@@ -355,7 +355,7 @@
|
||||
|
||||
}
|
||||
|
||||
.gems-left {
|
||||
.market .gems-left {
|
||||
position: absolute;
|
||||
right: -.5em;
|
||||
top: -.5em;
|
||||
@@ -739,11 +739,6 @@ export default {
|
||||
}
|
||||
},
|
||||
itemSelected (item) {
|
||||
if (item.purchaseType !== 'gear' && this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
featuredItemSelected (item) {
|
||||
|
||||
@@ -18,20 +18,23 @@
|
||||
div.inner-content
|
||||
questDialogContent(:item="item")
|
||||
|
||||
div
|
||||
.purchase-amount
|
||||
.how-many-to-buy
|
||||
strong {{ $t('howManyToBuy') }}
|
||||
.box
|
||||
input(type='number', min='0', v-model='selectedAmountToBuy')
|
||||
span.svg-icon.inline.icon-32(aria-hidden="true", v-html="(priceType === 'gems') ? icons.gem : icons.gold")
|
||||
span.value(:class="priceType") {{ item.value }}
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="purchaseGems()",
|
||||
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value)"
|
||||
v-if="priceType === 'gems' && !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)"
|
||||
) {{ $t('purchaseGems') }}
|
||||
|
||||
|
||||
button.btn.btn-primary(
|
||||
@click="buyItem()",
|
||||
v-else,
|
||||
:class="{'notEnough': !this.enoughCurrency(priceType, item.value)}"
|
||||
:class="{'notEnough': !this.enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
) {{ $t('buyNow') }}
|
||||
|
||||
div.right-sidebar(v-if="item.drop")
|
||||
@@ -52,9 +55,12 @@
|
||||
#buy-quest-modal {
|
||||
@include centeredModal();
|
||||
|
||||
.modal-dialog {
|
||||
margin-top: 25em;
|
||||
}
|
||||
|
||||
.content {
|
||||
text-align: center;
|
||||
max-height: 80vh;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
@@ -167,6 +173,37 @@
|
||||
pointer-events: none;
|
||||
opacity: 0.55;
|
||||
}
|
||||
|
||||
.purchase-amount {
|
||||
margin-top: 24px;
|
||||
|
||||
.how-many-to-buy {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.box {
|
||||
display: inline-block;
|
||||
width: 74px;
|
||||
height: 40px;
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
margin-right: 24px;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
border: none;
|
||||
}
|
||||
|
||||
input::-webkit-contacts-auto-fill-button {
|
||||
visibility: hidden;
|
||||
display: none !important;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -210,6 +247,7 @@
|
||||
}),
|
||||
|
||||
isPinned: false,
|
||||
selectedAmountToBuy: 1,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
@@ -238,10 +276,11 @@
|
||||
},
|
||||
methods: {
|
||||
onChange ($event) {
|
||||
this.selectedAmountToBuy = 1;
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal');
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
|
||||
this.purchased(this.item.text);
|
||||
this.hideDialog();
|
||||
},
|
||||
|
||||
@@ -477,11 +477,6 @@ export default {
|
||||
|
||||
this.selectedItemToBuy = item;
|
||||
|
||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||
},
|
||||
},
|
||||
|
||||
@@ -502,12 +502,6 @@
|
||||
},
|
||||
itemSelected (item) {
|
||||
if (item.locked) return;
|
||||
|
||||
if (this.$store.state.recentlyPurchased[item.key]) {
|
||||
this.makeGenericPurchase(item);
|
||||
return;
|
||||
}
|
||||
|
||||
this.$root.$emit('buyModal::showItem', item);
|
||||
},
|
||||
},
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
<template lang="pug">
|
||||
.tasks-column(:class='type')
|
||||
b-modal(ref="editTaskModal")
|
||||
buy-quest-modal(:item="selectedItemToBuy || {}",
|
||||
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
|
||||
:withPin="true",
|
||||
@change="resetItemToBuy($event)"
|
||||
v-if='type === "reward"')
|
||||
.d-flex
|
||||
h2.tasks-column-title(v-once) {{ $t(types[type].label) }}
|
||||
.filters.d-flex.justify-content-end
|
||||
@@ -200,6 +205,7 @@ import sortable from 'client/directives/sortable.directive';
|
||||
import buyMixin from 'client/mixins/buy';
|
||||
import { mapState, mapActions } from 'client/libs/store';
|
||||
import shopItem from '../shops/shopItem';
|
||||
import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue';
|
||||
|
||||
import { shouldDo } from 'common/script/cron';
|
||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||
@@ -215,6 +221,7 @@ export default {
|
||||
mixins: [buyMixin],
|
||||
components: {
|
||||
Task,
|
||||
BuyQuestModal,
|
||||
bModal,
|
||||
shopItem,
|
||||
},
|
||||
@@ -278,6 +285,8 @@ export default {
|
||||
|
||||
forceRefresh: new Date(),
|
||||
quickAddText: '',
|
||||
|
||||
selectedItemToBuy: {},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -479,10 +488,21 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewardItem.purchaseType === 'quests') {
|
||||
this.selectedItemToBuy = rewardItem;
|
||||
this.$root.$emit('show::modal', 'buy-quest-modal');
|
||||
return;
|
||||
}
|
||||
|
||||
if (rewardItem.purchaseType !== 'gear' || !rewardItem.locked) {
|
||||
this.$emit('openBuyDialog', rewardItem);
|
||||
}
|
||||
},
|
||||
resetItemToBuy ($event) {
|
||||
if (!$event) {
|
||||
this.selectedItemToBuy = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
export default {
|
||||
methods: {
|
||||
makeGenericPurchase (item, type = 'buyModal') {
|
||||
makeGenericPurchase (item, type = 'buyModal', quantity = 1) {
|
||||
this.$store.dispatch('shops:genericPurchase', {
|
||||
pinType: item.pinType,
|
||||
type: item.purchaseType,
|
||||
key: item.key,
|
||||
currency: item.currency,
|
||||
quantity,
|
||||
});
|
||||
|
||||
if (item.purchaseType !== 'gear') {
|
||||
this.$store.state.recentlyPurchased[item.key] = true;
|
||||
}
|
||||
|
||||
this.$root.$emit('playSound', 'Reward');
|
||||
|
||||
if (type !== 'buyModal') {
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import axios from 'axios';
|
||||
import buyOp from 'common/script/ops/buy';
|
||||
import buyQuestOp from 'common/script/ops/buyQuest';
|
||||
import purchaseOp from 'common/script/ops/purchaseWithSpell';
|
||||
import buyMysterySetOp from 'common/script/ops/buyMysterySet';
|
||||
import hourglassPurchaseOp from 'common/script/ops/hourglassPurchase';
|
||||
import sellOp from 'common/script/ops/sell';
|
||||
import unlockOp from 'common/script/ops/unlock';
|
||||
import buyArmoire from 'common/script/ops/buyArmoire';
|
||||
import rerollOp from 'common/script/ops/reroll';
|
||||
import { getDropClass } from 'client/libs/notifications';
|
||||
|
||||
// @TODO: Purchase means gems and buy means gold. That wording is misused below, but we should also change
|
||||
// the generic buy functions to something else. Or have a Gold Vendor and Gem Vendor, etc
|
||||
|
||||
export function buyItem (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyOp(user, {params});
|
||||
let opResult = buyOp(user, {params, quantity});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
@@ -21,32 +22,77 @@ export function buyItem (store, params) {
|
||||
}
|
||||
|
||||
export function buyQuestItem (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyQuestOp(user, {params});
|
||||
let opResult = buyOp(user, {
|
||||
params,
|
||||
type: 'quest',
|
||||
quantity,
|
||||
});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/buy-quest/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'quest'}),
|
||||
};
|
||||
}
|
||||
|
||||
async function buyArmoire (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
|
||||
let buyResult = buyOp(store.state.user.data, {
|
||||
params: {
|
||||
key: 'armoire',
|
||||
},
|
||||
type: 'armoire',
|
||||
quantity,
|
||||
});
|
||||
|
||||
// We need the server result because armoir has random item in the result
|
||||
let result = await axios.post('/api/v3/user/buy/armoire', {
|
||||
type: 'armoire',
|
||||
quantity,
|
||||
});
|
||||
buyResult = result.data.data;
|
||||
|
||||
if (buyResult) {
|
||||
const resData = buyResult;
|
||||
const item = resData.armoire;
|
||||
|
||||
const isExperience = item.type === 'experience';
|
||||
|
||||
if (item.type === 'gear') {
|
||||
store.state.user.data.items.gear.owned[item.dropKey] = true;
|
||||
}
|
||||
|
||||
// @TODO: We might need to abstract notifications to library rather than mixin
|
||||
store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: isExperience ? item.value : item.dropText,
|
||||
type: isExperience ? 'xp' : 'drop',
|
||||
icon: isExperience ? null : getDropClass({type: item.type, key: item.dropKey}),
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function purchase (store, params) {
|
||||
const quantity = params.quantity || 1;
|
||||
const user = store.state.user.data;
|
||||
let opResult = purchaseOp(user, {params});
|
||||
let opResult = purchaseOp(user, {params, quantity});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/purchase/${params.type}/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/purchase/${params.type}/${params.key}`, {quantity}),
|
||||
};
|
||||
}
|
||||
|
||||
export function purchaseMysterySet (store, params) {
|
||||
const user = store.state.user.data;
|
||||
let opResult = buyMysterySetOp(user, {params, noConfirm: true});
|
||||
let opResult = buyOp(user, {params, noConfirm: true, type: 'mystery'});
|
||||
|
||||
return {
|
||||
result: opResult,
|
||||
httpCall: axios.post(`/api/v3/user/buy-mystery-set/${params.key}`),
|
||||
httpCall: axios.post(`/api/v3/user/buy/${params.key}`, {type: 'mystery'}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -75,32 +121,7 @@ export async function genericPurchase (store, params) {
|
||||
case 'mystery_set':
|
||||
return purchaseMysterySet(store, params);
|
||||
case 'armoire': // eslint-disable-line
|
||||
let buyResult = buyArmoire(store.state.user.data);
|
||||
|
||||
// We need the server result because armoir has random item in the result
|
||||
let result = await axios.post('/api/v3/user/buy-armoire');
|
||||
buyResult = result.data.data;
|
||||
|
||||
if (buyResult) {
|
||||
const resData = buyResult;
|
||||
const item = resData.armoire;
|
||||
|
||||
const isExperience = item.type === 'experience';
|
||||
|
||||
if (item.type === 'gear') {
|
||||
store.state.user.data.items.gear.owned[item.dropKey] = true;
|
||||
}
|
||||
|
||||
// @TODO: We might need to abstract notifications to library rather than mixin
|
||||
store.dispatch('snackbars:add', {
|
||||
title: '',
|
||||
text: isExperience ? item.value : item.dropText,
|
||||
type: isExperience ? 'xp' : 'drop',
|
||||
icon: isExperience ? null : getDropClass({type: item.type, key: item.dropKey}),
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
|
||||
await buyArmoire(store, params);
|
||||
return;
|
||||
case 'fortify': {
|
||||
let rerollResult = rerollOp(store.state.user.data);
|
||||
@@ -134,9 +155,5 @@ export async function genericPurchase (store, params) {
|
||||
export function sellItems (store, params) {
|
||||
const user = store.state.user.data;
|
||||
sellOp(user, {params, query: {amount: params.amount}});
|
||||
axios
|
||||
.post(`/api/v3/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
|
||||
// TODO
|
||||
// .then((res) => console.log('equip', res))
|
||||
// .catch((err) => console.error('equip', err));
|
||||
axios.post(`/api/v3/user/sell/${params.type}/${params.key}?amount=${params.amount}`);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ export default function () {
|
||||
modalStack: [],
|
||||
brokenChallengeTask: {},
|
||||
equipmentDrawerOpen: true,
|
||||
recentlyPurchased: {},
|
||||
groupPlans: [],
|
||||
groupNotifications: [],
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user