Items/Market/Quests/misc fixes (#8987)

* use countBadge instead of class

* generic purchase action from buyModal to handle all kind of purchases - able to purchase backgrounds - return backgrounds as flat array

* List MysteryItem & Hourglass in Inventory/Items

* add Subscribers Gem Item (purchase by gold)

* fix mysterybox

* sort unlocked gear first

* add orb of rebirth to market

* remove old quest scroll + class of the quest items

* more padding on countBadge

* use the generic purchase on quests
This commit is contained in:
negue
2017-08-26 12:18:55 +02:00
committed by GitHub
parent 0233f7b486
commit c35e4f5750
19 changed files with 263 additions and 73 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -20,10 +20,3 @@
position: absolute;
top: -9px;
}
.badge-quantity {
color: $white;
right: -9px;
padding: 4.5px 8.5px;
background-color: $orange-100;
}

View File

@@ -32,7 +32,7 @@
h2
| {{ $t(group.key) }}
|
span.badge.badge-pill.badge-default {{group.quantity}}
span.badge.badge-pill.badge-default(v-if="group.key != 'special'") {{group.quantity}}
itemRows(
@@ -47,7 +47,7 @@
item(
:item="context.item",
:key="context.item.key",
:itemContentClass="`${group.classPrefix}${context.item.key}`",
:itemContentClass="context.item.class",
:highlightBorder="isHatchable(currentDraggingPotion, context.item.key)",
v-drag.drop.hatch="context.item.key",
@@ -61,7 +61,10 @@
h4.popover-content-title {{ context.item.text }}
.popover-content-text(v-if="currentDraggingPotion == null") {{ context.item.notes }}
template(slot="itemBadge", scope="context")
span.badge.badge-pill.badge-item.badge-quantity {{ context.item.quantity }}
countBadge(
:show="true",
:count="context.item.quantity"
)
itemRows(
v-else-if="group.key === 'hatchingPotions'",
@@ -75,7 +78,7 @@
item(
:item="context.item",
:key="context.item.key",
:itemContentClass="`${group.classPrefix}${context.item.key}`",
:itemContentClass="context.item.class",
:showPopover="currentDraggingPotion == null",
:active="currentDraggingPotion == context.item",
v-drag.hatch="context.item.key",
@@ -89,7 +92,10 @@
h4.popover-content-title {{ context.item.text }}
.popover-content-text {{ context.item.notes }}
template(slot="itemBadge", scope="context")
span.badge.badge-pill.badge-item.badge-quantity {{ context.item.quantity }}
countBadge(
:show="true",
:count="context.item.quantity"
)
itemRows(
v-else,
@@ -103,14 +109,18 @@
item(
:item="context.item",
:key="context.item.key",
:itemContentClass="`${group.classPrefix}${context.item.key}`",
:showPopover="currentDraggingPotion == null"
:itemContentClass="context.item.class",
:showPopover="currentDraggingPotion == null",
@click="itemClicked(group.key, context.item)",
)
template(slot="popoverContent", scope="context")
h4.popover-content-title {{ context.item.text }}
.popover-content-text {{ context.item.notes }}
template(slot="itemBadge", scope="context")
span.badge.badge-pill.badge-item.badge-quantity {{ context.item.quantity }}
countBadge(
:show="true",
:count="context.item.quantity"
)
hatchedPetDialog(
:pet="hatchedPet",
@@ -162,14 +172,16 @@ import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import Item from 'client/components/inventory/item';
import ItemRows from 'client/components/ui/itemRows';
import CountBadge from 'client/components/ui/countBadge';
import HatchedPetDialog from '../stable/hatchedPetDialog';
import createAnimal from 'client/libs/createAnimal';
import moment from 'moment';
const allowedSpecialItems = ['snowball', 'spookySparkles', 'shinySeed', 'seafoam'];
import notifications from 'client/mixins/notifications';
import DragDropDirective from 'client/directives/dragdrop.directive';
import MouseMoveDirective from 'client/directives/mouseposition.directive';
@@ -191,6 +203,7 @@ const groups = [
let lastMouseMoveEvent = {};
export default {
mixins: [notifications],
name: 'Items',
components: {
Item,
@@ -198,6 +211,7 @@ export default {
bDropdown,
bDropdownItem,
HatchedPetDialog,
CountBadge,
},
directives: {
drag: DragDropDirective,
@@ -243,6 +257,7 @@ export default {
if (isSearched) {
itemsArray.push({
...item,
class: `${group.classPrefix}${item.key}`,
text: item.text(),
notes: item.notes(),
quantity: itemQuantity,
@@ -262,6 +277,30 @@ export default {
});
});
let specialArray = [];
if (this.user.purchased.plan.mysteryItems.length) {
specialArray.push({
key: 'mysteryItem',
class: `inventory_present inventory_present_${moment().format('MM')}`,
text: this.$t('subscriberItemText'),
quantity: this.user.purchased.plan.mysteryItems.length,
});
}
if (this.user.purchased.plan.consecutive.trinkets) {
specialArray.push({
key: 'timeTravelers',
class: 'inventory_special_trinket',
text: this.$t('mysticHourglassPopover'),
quantity: this.user.purchased.plan.consecutive.trinkets,
});
}
if (specialArray.length > 0) {
itemsByType.special = specialArray;
}
return itemsByType;
},
},
@@ -347,6 +386,19 @@ export default {
this.hatchedPet = null;
},
async itemClicked (groupKey, item) {
if (groupKey === 'special') {
if (item.key === 'timeTravelers') {
this.$router.push({name: 'time'});
} else if (item.key === 'mysteryItem') {
let result = await this.$store.dispatch('user:openMysteryItem');
let openedItem = result.data.data;
let text = this.content.gear.flat[openedItem.key].text();
this.drop(this.$t('messageDropMysteryItem', {dropText: text}), openedItem);
}
}
},
mouseMoved ($event) {
if (this.potionClickMode) {

View File

@@ -167,9 +167,10 @@
import BalanceInfo from './balanceInfo.vue';
import currencyMixin from './_currencyMixin';
import notifications from 'client/mixins/notifications';
export default {
mixins: [currencyMixin],
mixins: [currencyMixin, notifications],
components: {
bModal,
BalanceInfo,
@@ -206,6 +207,16 @@
this.$emit('change', $event);
},
buyItem () {
if (this.genericPurchase) {
this.$store.dispatch('shops:genericPurchase', {
pinType: this.item.pinType,
type: this.item.purchaseType,
key: this.item.key,
currency: this.item.currency,
});
this.purchased(this.item.text);
}
this.$emit('buyPressed', this.item);
this.hideDialog();
},
@@ -236,6 +247,10 @@
withPin: {
type: Boolean,
},
genericPurchase: {
type: Boolean,
default: true,
},
},
};
</script>

View File

@@ -150,9 +150,12 @@
span(slot="popoverContent")
h4.popover-content-title {{ item.text }}
template(slot="itemImage", scope="scope")
span.svg-icon.inline.icon-48(v-if="scope.item.key == 'gem'", v-html="icons.gem")
template(slot="itemBadge", scope="ctx")
countBadge(
v-if="item.purchaseType !== 'card'",
v-if="item.showCount != false",
:show="userItems[item.purchaseType][item.key] != 0",
:count="userItems[item.purchaseType][item.key] || 0"
)
@@ -230,8 +233,7 @@
priceType="gold",
:withPin="true",
@change="resetGearToBuy($event)",
@buyPressed="buyGear($event)",
@togglePinned="togglePinned($event)"
@togglePinned="togglePinned($event)",
)
template(slot="item", scope="ctx")
div
@@ -250,14 +252,16 @@
:item="selectedItemToBuy",
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
@change="resetItemToBuy($event)",
@buyPressed="buyItem($event)",
@togglePinned="togglePinned($event)"
@togglePinned="togglePinned($event)",
@buyPressed="purchaseCallback($event)",
:genericPurchase="selectedItemToBuy != null && selectedItemToBuy.key != 'rebirth_orb'"
)
template(slot="item", scope="ctx")
item.flat.bordered-item(
:item="ctx.item",
:itemContentClass="ctx.item.class",
:showPopover="false"
:showPopover="false",
v-if="ctx.item.key != 'gem'"
)
selectMembersModal(
@@ -306,6 +310,10 @@
width: 12px;
height: 12px;
}
.icon-48 {
width: 48px;
height: 48px;
}
.hand-cursor {
cursor: pointer;
@@ -409,6 +417,7 @@
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import svgPin from 'assets/svg/pin.svg';
import svgGem from 'assets/svg/gem.svg';
import svgInformation from 'assets/svg/information.svg';
import svgWarrior from 'assets/svg/warrior.svg';
import svgWizard from 'assets/svg/wizard.svg';
@@ -475,6 +484,7 @@ export default {
icons: Object.freeze({
pin: svgPin,
gem: svgGem,
information: svgInformation,
warrior: svgWarrior,
wizard: svgWizard,
@@ -528,9 +538,50 @@ export default {
items: _map(_filter(this.content.cardTypes, (value) => {
return value.yearRound;
}), (value) => {
return getItemInfo(this.user, 'card', value);
return {
...getItemInfo(this.user, 'card', value),
showCount: false,
};
}),
});
let specialItems = [];
if (this.user.purchased.plan.customerId) {
specialItems.push({
showCount: false,
key: 'gem',
class: 'gem',
pinKey: 'gems',
purchaseType: 'gems',
text: this.$t('subGemName'),
notes: this.$t('subGemPop'),
currency: 'gold',
value: 20,
});
}
if (this.user.flags.rebirthEnabled) {
specialItems.push({
showCount: false,
key: 'rebirth_orb',
class: 'rebirth_orb',
purchaseType: 'rebirth_orb',
text: this.$t('rebirthName'),
notes: this.$t('rebirthPop'),
currency: 'gems',
value: this.user.stats.lvl < 100 ? 6 : '',
});
}
if (specialItems.length > 0) {
categories.push({
identifier: 'special',
text: this.$t('special'),
items: specialItems,
});
}
categories.map((category) => {
this.$set(this.viewOptions, category.identifier, {
selected: true,
@@ -655,7 +706,9 @@ export default {
return !this.userItems.gear.owned[gear.key];
});
result = _sortBy(result, [sortGearTypeMap[sortBy]]);
// first all unlocked
// then the selected sort
result = _sortBy(result, [(item) => item.locked, sortGearTypeMap[sortBy]]);
return result;
},
@@ -735,12 +788,6 @@ export default {
this.$parent.showUnpinNotification(item);
}
},
buyGear (item) {
this.$store.dispatch('shops:buyItem', {key: item.key});
},
buyItem (item) {
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
},
itemSelected (item) {
if (item.purchaseType === 'card') {
if (this.user.party._id) {
@@ -761,6 +808,12 @@ export default {
this.$store.dispatch('user:castSpell', {key: this.selectedCardToBuy.key, targetId: member.id});
this.selectedCardToBuy = null;
},
async purchaseCallback (item) {
if (item.key === 'rebirth_orb') {
await this.$store.dispatch('user:rebirth');
window.location.reload(true);
}
},
},
created () {
this.selectedGroupGearByClass = this.userStats.class;

View File

@@ -235,9 +235,10 @@
import BalanceInfo from '../balanceInfo.vue';
import currencyMixin from '../_currencyMixin';
import QuestInfo from './questInfo.vue';
import notifications from 'client/mixins/notifications';
export default {
mixins: [currencyMixin],
mixins: [currencyMixin, notifications],
components: {
bModal,
BalanceInfo,
@@ -278,6 +279,13 @@
this.$emit('change', $event);
},
buyItem () {
this.$store.dispatch('shops:genericPurchase', {
pinType: this.item.pinType,
type: this.item.purchaseType,
key: this.item.key,
currency: this.item.currency,
});
this.purchased(this.item.text);
this.$emit('buyPressed', this.item);
this.hideDialog();
},

View File

@@ -183,7 +183,6 @@
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
:withPin="true",
@change="resetItemToBuy($event)",
@buyPressed="buyItem($event)",
@togglePinned="togglePinned($event)"
)
template(slot="item", scope="ctx")
@@ -477,9 +476,6 @@ export default {
this.$parent.showUnpinNotification(item);
}
},
buyItem (item) {
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
},
},
created () {
this.$store.dispatch('shops:fetchQuests');

View File

@@ -112,7 +112,6 @@
:priceType="selectedItemToBuy ? selectedItemToBuy.currency : ''",
:withPin="true",
@change="resetItemToBuy($event)",
@buyPressed="buyItem($event)",
@togglePinned="togglePinned($event)"
)
template(slot="item", scope="ctx")
@@ -491,9 +490,6 @@
this.$parent.showUnpinNotification(item);
}
},
buyItem (item) {
this.$store.dispatch('shops:purchase', {type: item.purchaseType, key: item.key});
},
},
created () {
this.$store.dispatch('shops:fetchSeasonal');

View File

@@ -23,6 +23,7 @@ b-popover(
div.image
div(:class="item.class", v-once)
slot(name="itemImage", :item="item")
div.price
span.svg-icon.inline.icon-16(v-html="icons[currencyClass]")

View File

@@ -11,7 +11,7 @@ span.badge.badge-pill.badge-item.badge-count(
right: -9px;
color: $white;
background: $gray-200;
padding: 4.5px 6px;
padding: 4.5px 8.5px;
min-width: 24px;
height: 24px;
box-shadow: 0 1px 1px 0 rgba($black, 0.12);

View File

@@ -86,6 +86,11 @@ export default {
mp (val) {
this.notify(`${this.sign(val)} ${this.round(val)}`, 'mp', 'glyphicon glyphicon-fire', this.sign(val));
},
purchased (itemName) {
this.notify(this.$t('purchasedItem', {
itemName,
}));
},
streak (val) {
this.notify(`${this.$t('streaks')}: ${val}`, 'streak', 'glyphicon glyphicon-repeat');
},

View File

@@ -1,10 +1,12 @@
import axios from 'axios';
import { loadAsyncResource } from 'client/libs/asyncResource';
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';
export function fetchMarket (store, forceLoad = false) { // eslint-disable-line no-shadow
return loadAsyncResource({
@@ -57,44 +59,86 @@ export function fetchTimeTravelers (store, forceLoad = false) { // eslint-disabl
export function buyItem (store, params) {
const user = store.state.user.data;
buyOp(user, {params});
axios
.post(`/api/v3/user/buy/${params.key}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
let opResult = buyOp(user, {params});
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/buy/${params.key}`),
};
}
export function buyQuestItem (store, params) {
const user = store.state.user.data;
let opResult = buyQuestOp(user, {params});
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/buy-quest/${params.key}`),
};
}
export function purchase (store, params) {
const user = store.state.user.data;
purchaseOp(user, {params});
axios
.post(`/api/v3/user/purchase/${params.type}/${params.key}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
let opResult = purchaseOp(user, {params});
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/purchase/${params.type}/${params.key}`),
};
}
export function purchaseMysterySet (store, params) {
const user = store.state.user.data;
buyMysterySetOp(user, {params});
axios
.post(`/api/v3/user/buy-mystery-set/${params.key}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
let opResult = buyMysterySetOp(user, {params});
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/buy-mystery-set/${params.key}`),
};
}
export function purchaseHourglassItem (store, params) {
const user = store.state.user.data;
hourglassPurchaseOp(user, {params});
axios
.post(`/api/v3/user/purchase-hourglass/${params.type}/${params.key}`);
// TODO
// .then((res) => console.log('equip', res))
// .catch((err) => console.error('equip', err));
let opResult = hourglassPurchaseOp(user, {params});
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/purchase-hourglass/${params.type}/${params.key}`),
};
}
export function unlock (store, params) {
const user = store.state.user.data;
let opResult = unlockOp(user, params);
return {
result: opResult,
httpCall: axios.post(`/api/v3/user/unlock?path=${params.query.path}`),
};
}
export function genericPurchase (store, params) {
switch (params.pinType) {
case 'mystery_set':
return purchaseMysterySet(store, params);
case 'potion':
case 'armoire':
case 'marketGear':
return buyItem(store, params);
case 'background':
return unlock(store, {
query: {
path: `background.${params.key}`,
},
});
default:
if (params.pinType === 'quests' && params.currency === 'gold') {
return buyQuestItem(store, params);
} else {
return purchase(store, params);
}
}
}
export function sellItems (store, params) {
const user = store.state.user.data;

View File

@@ -96,3 +96,11 @@ export function castSpell (store, params) {
return axios.post(spellUrl);
}
export function openMysteryItem () {
return axios.post('/api/v3/user/open-mystery-item');
}
export function rebirth () {
return axios.post('/api/v3/user/rebirth');
}

View File

@@ -303,5 +303,6 @@
"health_wellness": "Health & Wellness",
"self_care": "Self-Care",
"sendLink": "Send Link",
"forgotPassword": "Forgot Password"
"forgotPassword": "Forgot Password",
"purchasedItem": "You bought <%= itemName %>"
}

View File

@@ -584,12 +584,20 @@ let backgrounds = {
};
/* eslint-enable quote-props */
let flat = {};
forOwn(backgrounds, function prefillBackgroundSet (backgroundsInSet, set) {
forOwn(backgroundsInSet, function prefillBackground (background, bgKey) {
background.key = bgKey;
background.set = set;
background.price = 7;
flat[bgKey] = background;
});
});
module.exports = backgrounds;
module.exports = {
tree: backgrounds,
flat,
};

View File

@@ -24,7 +24,7 @@ import {
} from './quests';
import appearances from './appearance';
import backgrounds from './appearance/backgrounds.js';
import backgrounds from './appearance/backgrounds';
import spells from './spells';
import subscriptionBlocks from './subscriptionBlocks';
import faq from './faq';
@@ -523,7 +523,8 @@ each(api.food, (food, key) => {
api.appearances = appearances;
api.backgrounds = backgrounds;
api.backgrounds = backgrounds.tree;
api.backgroundsFlat = backgrounds.flat;
api.userDefaults = {
habits: [

View File

@@ -137,7 +137,7 @@ module.exports = function getItemInfo (user, type, item, language = 'en') {
};
}) : undefined,
lvl: item.lvl,
class: locked ? `inventory_quest_scroll_locked inventory_quest_scroll_${item.key}_locked` : `inventory_quest_scroll inventory_quest_scroll_${item.key}`,
class: locked ? `inventory_quest_scroll_${item.key}_locked` : `inventory_quest_scroll_${item.key}`,
purchaseType: 'quests',
path: `quests.${item.key}`,
pinType: 'quests',

View File

@@ -9,6 +9,10 @@ import {
BadRequest,
} from '../libs/errors';
import { removeItemByPath } from './pinnedGearUtils';
import getItemInfo from '../libs/getItemInfo';
import content from '../content/index';
// If item is already purchased -> equip it
// Otherwise unlock it
module.exports = function unlock (user, req = {}, analytics) {
@@ -75,10 +79,11 @@ module.exports = function unlock (user, req = {}, analytics) {
setWith(user, `purchased.${pathPart}`, true, Object);
});
} else {
if (alreadyOwns) { // eslint-disable-line no-lonely-if
let split = path.split('.');
let value = split.pop();
let key = split.join('.');
if (alreadyOwns) { // eslint-disable-line no-lonely-if
if (key === 'background' && value === user.preferences.background) {
value = '';
}
@@ -88,6 +93,10 @@ module.exports = function unlock (user, req = {}, analytics) {
} else {
// Using Object so path[1] won't create an array but an object {path: {1: value}}
setWith(user, `purchased.${path}`, true, Object);
let backgroundContent = content.backgroundsFlat[value];
let itemInfo = getItemInfo(user, 'background', backgroundContent);
removeItemByPath(user, itemInfo.path);
}
}