mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-13 12:47:28 +01:00
Compare commits
18 Commits
v5.41.7
...
fiz/confir
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3d9cbaa42a | ||
|
|
81b7af35ea | ||
|
|
4468f06074 | ||
|
|
2948824df7 | ||
|
|
de1b509243 | ||
|
|
fd0dedf72e | ||
|
|
8da456f4b7 | ||
|
|
a22691d11f | ||
|
|
1045a17354 | ||
|
|
bf1ea90720 | ||
|
|
ccc7e7d7a7 | ||
|
|
270ff2e034 | ||
|
|
560dc5a896 | ||
|
|
c0d36db6af | ||
|
|
80de056bab | ||
|
|
b154fe2564 | ||
|
|
a4a8dacc31 | ||
|
|
c60822f44f |
10
website/client/src/assets/svg/warning_icon.svg
Normal file
10
website/client/src/assets/svg/warning_icon.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2649_1708)">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M27 36H21V30H27V36ZM48 6V42C48 45.3 45.3 48 42 48H6C2.7 48 0 45.3 0 42V6C0 2.7 2.7 0 6 0H42C45.3 0 48 2.7 48 6ZM42 6H6V42H42V6ZM27 12H21V27H27V12Z" fill="#DE3F3F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_2649_1708">
|
||||
<rect width="48" height="48" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 463 B |
@@ -1,8 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
id="privacy-banner"
|
||||
v-if="!hidden"
|
||||
id="privacy-banner"
|
||||
class="banner d-flex align-items-center justify-content-between py-3 px-4"
|
||||
>
|
||||
<p
|
||||
class="mr-3 mb-0"
|
||||
|
||||
@@ -328,6 +328,8 @@ export default {
|
||||
alreadyReadNotification,
|
||||
nextCron: null,
|
||||
handledNotifications,
|
||||
isInitialLoadComplete: false,
|
||||
pendingRebirthNotification: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -453,6 +455,18 @@ export default {
|
||||
|
||||
return this.runYesterDailies();
|
||||
},
|
||||
async showPendingRebirthModal () {
|
||||
if (this.pendingRebirthNotification) {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
|
||||
await axios.post('/api/v4/notifications/read', {
|
||||
notificationIds: [this.pendingRebirthNotification.id],
|
||||
});
|
||||
|
||||
this.pendingRebirthNotification = null;
|
||||
}
|
||||
},
|
||||
showDeathModal () {
|
||||
this.playSound('Death');
|
||||
this.$root.$emit('bv::show::modal', 'death');
|
||||
@@ -661,6 +675,18 @@ export default {
|
||||
this.showLevelUpNotifications(this.user.stats.lvl);
|
||||
}
|
||||
this.handleUserNotifications(this.user.notifications);
|
||||
|
||||
this.isInitialLoadComplete = true;
|
||||
|
||||
const hasRebirthConfirmationFlag = localStorage.getItem('show-rebirth-confirmation') === 'true';
|
||||
|
||||
if (hasRebirthConfirmationFlag) {
|
||||
localStorage.removeItem('show-rebirth-confirmation');
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
} else {
|
||||
this.showPendingRebirthModal();
|
||||
}
|
||||
},
|
||||
async handleUserNotifications (after) {
|
||||
if (this.$store.state.isRunningYesterdailies) return;
|
||||
@@ -700,8 +726,15 @@ export default {
|
||||
this.$root.$emit('habitica:won-challenge', notification);
|
||||
break;
|
||||
case 'REBIRTH_ACHIEVEMENT':
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
if (localStorage.getItem('show-rebirth-confirmation') === 'true') {
|
||||
markAsRead = false;
|
||||
} else if (!this.isInitialLoadComplete) {
|
||||
this.pendingRebirthNotification = notification;
|
||||
markAsRead = false;
|
||||
} else {
|
||||
this.playSound('Achievement_Unlocked');
|
||||
this.$root.$emit('bv::show::modal', 'rebirth');
|
||||
}
|
||||
break;
|
||||
case 'STREAK_ACHIEVEMENT':
|
||||
this.text(`${this.$t('streaks')}: ${this.user.achievements.streak}`, () => {
|
||||
|
||||
@@ -1,231 +1,231 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="buy-modal"
|
||||
:hide-header="true"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<span
|
||||
v-if="withPin"
|
||||
class="badge-dialog"
|
||||
tabindex="0"
|
||||
@click.prevent.stop="togglePinned()"
|
||||
@keypress.enter.prevent.stop="togglePinned()"
|
||||
id="buy-modal"
|
||||
:hide-header="true"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isPinned"
|
||||
/>
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="svg-icon close-icon icon-16 color"
|
||||
aria-hidden="true"
|
||||
v-if="withPin"
|
||||
class="badge-dialog"
|
||||
tabindex="0"
|
||||
@click="hideDialog()"
|
||||
@keypress.enter="hideDialog()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
</div>
|
||||
<div
|
||||
v-if="item != null"
|
||||
class="content"
|
||||
>
|
||||
<div class="inner-content">
|
||||
<slot
|
||||
name="item"
|
||||
:item="item"
|
||||
>
|
||||
<div v-if="showAvatar">
|
||||
<avatar
|
||||
:show-visual-buffs="false"
|
||||
:member="user"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:with-background="true"
|
||||
:override-avatar-gear="getAvatarOverrides(item)"
|
||||
:sprites-margin="'0px auto 0px -24px'"
|
||||
@click.prevent.stop="togglePinned()"
|
||||
@keypress.enter.prevent.stop="togglePinned()"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isPinned"
|
||||
/>
|
||||
</span>
|
||||
<div>
|
||||
<span
|
||||
class="svg-icon close-icon icon-16 color"
|
||||
aria-hidden="true"
|
||||
tabindex="0"
|
||||
@click="hideDialog()"
|
||||
@keypress.enter="hideDialog()"
|
||||
v-html="icons.close"
|
||||
></span>
|
||||
</div>
|
||||
<div
|
||||
v-if="item != null"
|
||||
class="content"
|
||||
>
|
||||
<div class="inner-content">
|
||||
<slot
|
||||
name="item"
|
||||
:item="item"
|
||||
>
|
||||
<div v-if="showAvatar">
|
||||
<avatar
|
||||
:show-visual-buffs="false"
|
||||
:member="user"
|
||||
:avatar-only="true"
|
||||
:hide-class-badge="true"
|
||||
:with-background="true"
|
||||
:override-avatar-gear="getAvatarOverrides(item)"
|
||||
:sprites-margin="'0px auto 0px -24px'"
|
||||
/>
|
||||
</div>
|
||||
<item
|
||||
v-else-if="item.key === 'gem'"
|
||||
class="flat bordered-item"
|
||||
:item="item"
|
||||
:item-content-class="item.class"
|
||||
:show-popover="false"
|
||||
/>
|
||||
<item
|
||||
v-else-if="item.key != 'gem'"
|
||||
class="flat bordered-item"
|
||||
:item="item"
|
||||
:item-content-class="item.class"
|
||||
:show-popover="false"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="!showAvatar && user.items[item.purchaseType]"
|
||||
class="owned"
|
||||
:class="totalOwned"
|
||||
>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span class="owned-text">{{ $t('owned') }}: <span class="user-amount">{{ totalOwned }}</span></span>
|
||||
</div>
|
||||
<item
|
||||
v-else-if="item.key === 'gem'"
|
||||
class="flat bordered-item"
|
||||
<h4 class="title">
|
||||
{{ itemText }}
|
||||
</h4>
|
||||
<div class="item-notes">
|
||||
{{ itemNotes }}
|
||||
</div>
|
||||
<slot
|
||||
name="additionalInfo"
|
||||
:item="item"
|
||||
:item-content-class="item.class"
|
||||
:show-popover="false"
|
||||
/>
|
||||
<item
|
||||
v-else-if="item.key != 'gem'"
|
||||
class="flat bordered-item"
|
||||
:item="item"
|
||||
:item-content-class="item.class"
|
||||
:show-popover="false"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="!showAvatar && user.items[item.purchaseType]"
|
||||
class="owned"
|
||||
:class="totalOwned"
|
||||
>
|
||||
<!-- eslint-disable-next-line max-len -->
|
||||
<span class="owned-text">{{ $t('owned') }}: <span class="user-amount">{{ totalOwned }}</span></span>
|
||||
</div>
|
||||
<h4 class="title">
|
||||
{{ itemText }}
|
||||
</h4>
|
||||
<div class="item-notes">
|
||||
{{ itemNotes }}
|
||||
</div>
|
||||
<slot
|
||||
name="additionalInfo"
|
||||
:item="item"
|
||||
>
|
||||
<equipmentAttributesGrid
|
||||
v-if="showAttributesGrid"
|
||||
class="attributesGrid"
|
||||
:item="item"
|
||||
:user="user"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="item.value > 0 && !(item.key === 'gem' && gemsLeft < 1)"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<div class="item-cost justify-content-center my-3">
|
||||
<span
|
||||
class="cost d-flex mx-auto"
|
||||
:class="getPriceClass()"
|
||||
>
|
||||
>
|
||||
<equipmentAttributesGrid
|
||||
v-if="showAttributesGrid"
|
||||
class="attributesGrid"
|
||||
:item="item"
|
||||
:user="user"
|
||||
/>
|
||||
</slot>
|
||||
<div
|
||||
v-if="item.value > 0 && !(item.key === 'gem' && gemsLeft < 1)"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<div class="item-cost justify-content-center my-3">
|
||||
<span
|
||||
class="svg-icon icon-24 my-auto mr-1"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
class="cost d-flex mx-auto"
|
||||
:class="getPriceClass()"
|
||||
>
|
||||
<span
|
||||
class="svg-icon icon-24 my-auto mr-1"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
class="my-auto"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
<span
|
||||
class="my-auto"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="showAmountToBuy(item)"
|
||||
class="how-many-to-buy"
|
||||
>
|
||||
{{ $t('howManyToBuy') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="showAmountToBuy(item)"
|
||||
>
|
||||
<number-increment
|
||||
class="number-increment"
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
<div
|
||||
:class="{'notEnough': notEnoughCurrency}"
|
||||
class="total"
|
||||
v-if="showAmountToBuy(item)"
|
||||
class="how-many-to-buy"
|
||||
>
|
||||
<span class="total-text">{{ $t('sendTotal') }}</span>
|
||||
<span
|
||||
class="svg-icon total icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
></span>
|
||||
<span
|
||||
class="total-text"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
{{ $t('howManyToBuy') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="showAmountToBuy(item)"
|
||||
>
|
||||
<number-increment
|
||||
class="number-increment"
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
<div
|
||||
:class="{'notEnough': notEnoughCurrency}"
|
||||
class="total"
|
||||
>
|
||||
<span class="total-text">{{ $t('sendTotal') }}</span>
|
||||
<span
|
||||
class="svg-icon total icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[getPriceClass()]"
|
||||
></span>
|
||||
<span
|
||||
class="total-text"
|
||||
:class="getPriceClass()"
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem' && gemsLeft < 1"
|
||||
class="no-more-gems"
|
||||
>
|
||||
{{ $t('notEnoughGemsToBuy') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="nonSubscriberHourglasses"
|
||||
class="hourglass-nonsub mt-3"
|
||||
>
|
||||
{{ $t('mysticHourglassNeededNoSub') }}
|
||||
</div>
|
||||
<button
|
||||
v-if="getPriceClass() === 'gems'
|
||||
&& !enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
class="btn btn-primary mb-3"
|
||||
@click="purchaseGems()"
|
||||
>
|
||||
{{ $t('purchaseGems') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="nonSubscriberHourglasses"
|
||||
class="btn btn-primary"
|
||||
@click="viewSubscriptions(item)"
|
||||
>
|
||||
{{ $t('viewSubscriptions') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="!(item.key === 'gem' && gemsLeft < 1)"
|
||||
class="btn btn-primary"
|
||||
:disabled="item.key === 'gem' && gemsLeft === 0 ||
|
||||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked ||
|
||||
!preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
:class="{'notEnough': !preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
tabindex="0"
|
||||
@click="buyItem()"
|
||||
>
|
||||
{{ $t('buyNow') }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem' && gemsLeft < 1"
|
||||
class="no-more-gems"
|
||||
>
|
||||
{{ $t('notEnoughGemsToBuy') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="nonSubscriberHourglasses"
|
||||
class="hourglass-nonsub mt-3"
|
||||
>
|
||||
{{ $t('mysticHourglassNeededNoSub') }}
|
||||
</div>
|
||||
<button
|
||||
v-if="getPriceClass() === 'gems'
|
||||
&& !enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
class="btn btn-primary mb-3"
|
||||
@click="purchaseGems()"
|
||||
>
|
||||
{{ $t('purchaseGems') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="nonSubscriberHourglasses"
|
||||
class="btn btn-primary"
|
||||
@click="viewSubscriptions(item)"
|
||||
>
|
||||
{{ $t('viewSubscriptions') }}
|
||||
</button>
|
||||
<button
|
||||
v-else-if="!(item.key === 'gem' && gemsLeft < 1)"
|
||||
class="btn btn-primary"
|
||||
:disabled="item.key === 'gem' && gemsLeft === 0 ||
|
||||
attemptingToPurchaseMoreGemsThanAreLeft || numberInvalid || item.locked ||
|
||||
!preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
|
||||
:class="{'notEnough': !preventHealthPotion ||
|
||||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
|
||||
tabindex="0"
|
||||
@click="buyItem()"
|
||||
>
|
||||
{{ $t('buyNow') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<countdown-banner
|
||||
v-if="item.end && item.owned == null"
|
||||
:end-date="endDate"
|
||||
class="limitedTime available"
|
||||
/>
|
||||
<div
|
||||
v-if="item.key === 'rebirth_orb' && item.value > 0 && user.stats.lvl >= 100"
|
||||
class="free-rebirth d-flex align-items-center"
|
||||
>
|
||||
<div class="m-auto">
|
||||
<span
|
||||
class="svg-icon inline icon-16 mr-2 pt-015"
|
||||
v-html="icons.whiteClock"
|
||||
></span>
|
||||
<span v-html="$t('nextFreeRebirth', {days: nextFreeRebirth})"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem'"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<div
|
||||
v-if="gemsLeft > 0"
|
||||
class="gems-left d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gemsLeft === 0"
|
||||
class="out-of-gems-banner d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
>
|
||||
<span class="user-balance ml-3 my-auto">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="mr-3"
|
||||
:currency-needed="getPriceClass()"
|
||||
:amount-needed="item.value"
|
||||
<countdown-banner
|
||||
v-if="item.end && item.owned == null"
|
||||
:end-date="endDate"
|
||||
class="limitedTime available"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'rebirth_orb' && item.value > 0 && user.stats.lvl >= 100"
|
||||
class="free-rebirth d-flex align-items-center"
|
||||
>
|
||||
<div class="m-auto">
|
||||
<span
|
||||
class="svg-icon inline icon-16 mr-2 pt-015"
|
||||
v-html="icons.whiteClock"
|
||||
></span>
|
||||
<span v-html="$t('nextFreeRebirth', {days: nextFreeRebirth})"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-if="item.key === 'gem'"
|
||||
class="d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<div
|
||||
v-if="gemsLeft > 0"
|
||||
class="gems-left d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
<div
|
||||
v-if="gemsLeft === 0"
|
||||
class="out-of-gems-banner d-flex justify-content-center align-items-center"
|
||||
>
|
||||
<strong>{{ $t('monthlyGems') }} </strong>
|
||||
{{ gemsLeft }} / {{ totalGems }} {{ $t('gemsRemaining') }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
>
|
||||
<span class="user-balance ml-3 my-auto">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="mr-3"
|
||||
:currency-needed="getPriceClass()"
|
||||
:amount-needed="item.value"
|
||||
/>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
@@ -851,10 +851,17 @@ export default {
|
||||
- ownedMounts
|
||||
- ownedItems;
|
||||
|
||||
if (
|
||||
petsRemaining < 0
|
||||
&& !window.confirm(this.$t('purchasePetItemConfirm', { itemText: this.item.text })) // eslint-disable-line no-alert
|
||||
) return;
|
||||
if (petsRemaining < 0) {
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:purchase-confirm', {
|
||||
message: this.$t('purchasePetItemConfirm', { itemText: this.item.text }),
|
||||
currency: this.item.currency,
|
||||
cost: this.item.value * this.selectedAmountToBuy,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.item.purchaseType === 'customization') {
|
||||
@@ -866,15 +873,23 @@ export default {
|
||||
this.purchased(this.item.text);
|
||||
} else {
|
||||
const shouldConfirmPurchase = this.item.currency === 'gems' || this.item.currency === 'hourglasses';
|
||||
if (
|
||||
shouldConfirmPurchase
|
||||
&& !this.confirmPurchase(this.item.currency, this.item.value * this.selectedAmountToBuy)
|
||||
) {
|
||||
return;
|
||||
if (shouldConfirmPurchase) {
|
||||
const confirmed = await this.confirmPurchase(
|
||||
this.item.currency,
|
||||
this.item.value * this.selectedAmountToBuy,
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (this.genericPurchase) {
|
||||
if (this.item.key === 'rebirth_orb') {
|
||||
localStorage.setItem('show-rebirth-confirmation', 'true');
|
||||
}
|
||||
await this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy);
|
||||
await this.purchased(this.item.text);
|
||||
if (this.item.key !== 'rebirth_orb') {
|
||||
await this.purchased(this.item.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
207
website/client/src/components/shops/purchaseConfirmModal.vue
Normal file
207
website/client/src/components/shops/purchaseConfirmModal.vue
Normal file
@@ -0,0 +1,207 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="purchase-confirm-modal"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="purchase-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div class="modal-content-wrapper">
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="currency-chip"
|
||||
:class="currency"
|
||||
>
|
||||
<span
|
||||
class="svg-icon icon-24"
|
||||
v-html="icons[currency]"
|
||||
></span>
|
||||
<span class="cost-value">{{ cost }}</span>
|
||||
</div>
|
||||
<h2 class="modal-title">
|
||||
{{ $t('confirmPurchase') }}
|
||||
</h2>
|
||||
<p class="modal-subtitle">
|
||||
{{ confirmationMessage }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ $t('confirm') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn-cancel"
|
||||
@click="cancel()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import svgGem from '@/assets/svg/gem.svg?raw';
|
||||
import svgHourglass from '@/assets/svg/hourglass.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
confirmationMessage: '',
|
||||
currency: 'gems',
|
||||
cost: 0,
|
||||
resolveCallback: null,
|
||||
icons: Object.freeze({
|
||||
gems: svgGem,
|
||||
hourglasses: svgHourglass,
|
||||
}),
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:purchase-confirm', config => {
|
||||
this.confirmationMessage = config.message;
|
||||
this.currency = config.currency || 'gems';
|
||||
this.cost = config.cost || 0;
|
||||
this.resolveCallback = config.resolve;
|
||||
this.$root.$emit('bv::show::modal', 'purchase-confirm-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:purchase-confirm');
|
||||
},
|
||||
methods: {
|
||||
confirm () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(true);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
cancel () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(false);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'purchase-confirm-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .purchase-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $purple-300;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.currency-chip {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 40px;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: 1.25rem;
|
||||
font-weight: bold;
|
||||
line-height: 1.4;
|
||||
|
||||
&.gems {
|
||||
color: $gems-color;
|
||||
background-color: rgba($green-10, 0.15);
|
||||
}
|
||||
|
||||
&.hourglasses {
|
||||
color: $hourglass-color;
|
||||
background-color: rgba($blue-10, 0.15);
|
||||
}
|
||||
|
||||
.icon-24 {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $purple-300;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -1,119 +1,119 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="buy-quest-modal"
|
||||
:hide-header="true"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<span
|
||||
v-if="withPin"
|
||||
class="badge-dialog"
|
||||
@click.prevent.stop="togglePinned()"
|
||||
id="buy-quest-modal"
|
||||
:hide-header="true"
|
||||
@change="onChange($event)"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isPinned"
|
||||
/>
|
||||
</span>
|
||||
<div class="dialog-close">
|
||||
<close-icon @click="hideDialog()" />
|
||||
</div>
|
||||
<h2 class="text-center textCondensed">
|
||||
{{ $t('questDetails') }}
|
||||
</h2>
|
||||
<div
|
||||
v-if="item != null"
|
||||
class="content"
|
||||
>
|
||||
<div class="inner-content">
|
||||
<questDialogContent
|
||||
:item="item"
|
||||
:abbreviated="true"
|
||||
<span
|
||||
v-if="withPin"
|
||||
class="badge-dialog"
|
||||
@click.prevent.stop="togglePinned()"
|
||||
>
|
||||
<pin-badge
|
||||
:pinned="isPinned"
|
||||
/>
|
||||
<div
|
||||
v-if="item.addlNotes"
|
||||
class="mx-4 mb-3"
|
||||
>
|
||||
{{ item.addlNotes }}
|
||||
</div>
|
||||
<quest-rewards :quest="item" />
|
||||
<div
|
||||
v-if="!item.locked"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<div class="item-cost">
|
||||
<span
|
||||
class="cost"
|
||||
:class="priceType"
|
||||
>
|
||||
</span>
|
||||
<div class="dialog-close">
|
||||
<close-icon @click="hideDialog()" />
|
||||
</div>
|
||||
<h2 class="text-center textCondensed">
|
||||
{{ $t('questDetails') }}
|
||||
</h2>
|
||||
<div
|
||||
v-if="item != null"
|
||||
class="content"
|
||||
>
|
||||
<div class="inner-content">
|
||||
<questDialogContent
|
||||
:item="item"
|
||||
:abbreviated="true"
|
||||
/>
|
||||
<div
|
||||
v-if="item.addlNotes"
|
||||
class="mx-4 mb-3"
|
||||
>
|
||||
{{ item.addlNotes }}
|
||||
</div>
|
||||
<quest-rewards :quest="item" />
|
||||
<div
|
||||
v-if="!item.locked"
|
||||
class="purchase-amount"
|
||||
>
|
||||
<div class="item-cost">
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[priceType]"
|
||||
class="cost"
|
||||
:class="priceType"
|
||||
>
|
||||
<span
|
||||
class="svg-icon inline icon-24"
|
||||
aria-hidden="true"
|
||||
v-html="icons[priceType]"
|
||||
>
|
||||
</span>
|
||||
<span
|
||||
:class="priceType"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="how-many-to-buy">
|
||||
<strong>{{ $t('howManyToBuy') }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<number-increment
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="total-text">
|
||||
{{ $t('sendTotal') }}
|
||||
</span>
|
||||
<span
|
||||
class="svg-icon inline icon-20"
|
||||
aria-hidden="true"
|
||||
v-html="currencyIcon"
|
||||
></span>
|
||||
<span
|
||||
class="total"
|
||||
:class="priceType"
|
||||
>{{ item.value }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="how-many-to-buy">
|
||||
<strong>{{ $t('howManyToBuy') }}</strong>
|
||||
</div>
|
||||
<div>
|
||||
<number-increment
|
||||
@updateQuantity="selectedAmountToBuy = $event"
|
||||
/>
|
||||
</div>
|
||||
<div class="total-row">
|
||||
<span class="total-text">
|
||||
{{ $t('sendTotal') }}
|
||||
</span>
|
||||
<span
|
||||
class="svg-icon inline icon-20"
|
||||
aria-hidden="true"
|
||||
v-html="currencyIcon"
|
||||
></span>
|
||||
<span
|
||||
class="total"
|
||||
:class="priceType"
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
>{{ item.value * selectedAmountToBuy }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
v-if="priceType === 'gems'
|
||||
&& !enoughCurrency(priceType, item.value * selectedAmountToBuy)
|
||||
&& !item.locked"
|
||||
class="btn btn-primary mb-3"
|
||||
@click="purchaseGems()"
|
||||
>
|
||||
{{ $t('purchaseGems') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary mb-4"
|
||||
:class="{'notEnough': !enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
:disabled="numberInvalid"
|
||||
@click="buyItem()"
|
||||
>
|
||||
{{ $t('buyNow') }}
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
v-if="priceType === 'gems'
|
||||
&& !enoughCurrency(priceType, item.value * selectedAmountToBuy)
|
||||
&& !item.locked"
|
||||
class="btn btn-primary mb-3"
|
||||
@click="purchaseGems()"
|
||||
>
|
||||
{{ $t('purchaseGems') }}
|
||||
</button>
|
||||
<button
|
||||
v-else
|
||||
class="btn btn-primary mb-4"
|
||||
:class="{'notEnough': !enoughCurrency(priceType, item.value * selectedAmountToBuy)}"
|
||||
:disabled="numberInvalid"
|
||||
@click="buyItem()"
|
||||
>
|
||||
{{ $t('buyNow') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<countdown-banner
|
||||
v-if="item.end"
|
||||
:end-date="endDate"
|
||||
/>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
>
|
||||
<span class="balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="float-right"
|
||||
:with-hourglass="priceType === 'hourglasses'"
|
||||
:currency-needed="priceType"
|
||||
:amount-needed="item.value"
|
||||
<countdown-banner
|
||||
v-if="item.end"
|
||||
:end-date="endDate"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
slot="modal-footer"
|
||||
class="clearfix"
|
||||
>
|
||||
<span class="balance float-left">{{ $t('yourBalance') }}</span>
|
||||
<balanceInfo
|
||||
class="float-right"
|
||||
:with-hourglass="priceType === 'hourglasses'"
|
||||
:currency-needed="priceType"
|
||||
:amount-needed="item.value"
|
||||
/>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
@@ -510,8 +510,12 @@ export default {
|
||||
this.selectedAmountToBuy = 1;
|
||||
this.$emit('change', $event);
|
||||
},
|
||||
buyItem () {
|
||||
if (!this.confirmPurchase(this.item.currency, this.item.value * this.selectedAmountToBuy)) {
|
||||
async buyItem () {
|
||||
const confirmed = await this.confirmPurchase(
|
||||
this.item.currency,
|
||||
this.item.value * this.selectedAmountToBuy,
|
||||
);
|
||||
if (!confirmed) {
|
||||
return;
|
||||
}
|
||||
this.makeGenericPurchase(this.item, 'buyQuestModal', this.selectedAmountToBuy);
|
||||
|
||||
@@ -64,9 +64,11 @@
|
||||
<li>sexual orientation; and</li>
|
||||
<li>information collected from a known child.</li>
|
||||
</ul>
|
||||
<p><strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong></p>
|
||||
<p>
|
||||
<strong>
|
||||
NOTE: Please do not provide us “sensitive personal information” or “sensitive personal data”, as those terms are defined under applicable privacy laws, unless we directly request that you do so. If you feel, after careful consideration, that it is necessary to provide us certain sensitive personal information or data, please provide us the minimum amount of such information or data that is necessary.
|
||||
</strong>
|
||||
</p>
|
||||
<h3 id="section_1_1">
|
||||
1.1 Information You Provide Directly
|
||||
</h3>
|
||||
@@ -617,7 +619,7 @@
|
||||
7. General Audience Services
|
||||
</h2>
|
||||
<p>
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
The Service is intended for users 18 years or older; you are not permitted to access or use the Service if you are younger than 18. We do not knowingly collect personal information from children under the age of 18 through the Service. We encourage parents and legal guardians to monitor their children’s Internet usage and to help enforce our Privacy Policy by instructing their children to never provide personal information without their permission. If you have reason to believe that a child under the age of 18 has provided personal information to us, please contact us at <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>, and we will delete that information from our databases.
|
||||
</p>
|
||||
|
||||
<h2 id="section_8">
|
||||
@@ -708,7 +710,7 @@
|
||||
|
||||
<p><strong><u>Nevada Residents</u></strong></p>
|
||||
<p>
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href='mailto:privacy@habitica.com'>privacy@habitica.com</a>.
|
||||
Nevada residents may opt out of the sale of certain “covered information” collected by operators of websites or online services. We currently do not sell covered information, as “sale” is defined by such law, and do not have plans to do so. In accordance with Nevada law, you may submit to us a verified request instructing us not to sell your covered information by sending an email to <a href="mailto:privacy@habitica.com">privacy@habitica.com</a>.
|
||||
</p>
|
||||
<p><strong><u>Notice to United Kingdom/European/Switzerland Residents.</u></strong></p>
|
||||
<p>
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
<router-view />
|
||||
</div>
|
||||
<div
|
||||
id="bottom-background"
|
||||
v-if="loginFlow"
|
||||
id="bottom-background"
|
||||
class="bg-purple-300"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
@@ -31,7 +31,10 @@
|
||||
id="bottom-wrap"
|
||||
class="purple-4"
|
||||
>
|
||||
<div id="bottom-background" v-if="!loginFlow">
|
||||
<div
|
||||
v-if="!loginFlow"
|
||||
id="bottom-background"
|
||||
>
|
||||
<div class="seamless_mountains_demo_repeat"></div>
|
||||
<div class="midground_foreground_extended2"></div>
|
||||
</div>
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
BY PURCHASING PREMIUM YOU EXPRESSLY UNDERSTAND AND AGREE TO OUR REFUND POLICY:
|
||||
</p>
|
||||
<p>
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href='mailto:admin@habitica.com'>ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
YOU CAN REQUEST A REFUND OF YOUR MOST RECENT PAYMENT TO US BY CONTACTING US AT <a href="mailto:admin@habitica.com">ADMIN@HABITICA.COM</a>. THE AMOUNT OF YOUR REFUND, IF ANY, WILL BE BASED ON (1) THE AMOUNT OF YOUR PURCHASED BUT UNUSED SUBSCRIPTION BENEFITS AND (2) THE TERMS IMPOSED ON US BY OUR PAYMENT PROCESSING VENDORS (E.G., WITH RESPECT TO THE DURATION OF THE REFUND PERIOD).
|
||||
</p>
|
||||
<p>
|
||||
FOR ANY CUSTOMER WHO PURCHASED PREMIUM IN APPLE INC.'s APP STORE ("APP STORE"), PLEASE CONTACT APPLE INC.'s SUPPORT TEAM: <a
|
||||
|
||||
@@ -1,65 +1,45 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="broken-task-modal"
|
||||
title="Broken Challenge"
|
||||
size="sm"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="broken-task-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div
|
||||
v-if="brokenChallengeTask && brokenChallengeTask.challenge"
|
||||
class="modal-body"
|
||||
class="modal-content-wrapper"
|
||||
>
|
||||
<div
|
||||
v-if="brokenChallengeTask.challenge.broken === 'TASK_DELETED'
|
||||
|| brokenChallengeTask.challenge.broken === 'CHALLENGE_TASK_NOT_FOUND'"
|
||||
>
|
||||
<h2>{{ $t('brokenTask') }}</h2>
|
||||
<div>
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
v-html="icons.warningIcon"
|
||||
></div>
|
||||
<h2 class="modal-title">
|
||||
{{ modalTitle }}
|
||||
</h2>
|
||||
<p class="modal-subtitle">
|
||||
{{ modalSubtitle }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep')"
|
||||
@click="keepAction()"
|
||||
>
|
||||
{{ $t('keepIt') }}
|
||||
{{ keepButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="removeTask(obj)"
|
||||
@click="removeAction()"
|
||||
>
|
||||
{{ $t('removeIt') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="brokenChallengeTask.challenge.broken === 'CHALLENGE_DELETED'">
|
||||
<h2>{{ $t('brokenChallenge') }}</h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep-all')"
|
||||
>
|
||||
{{ $t('keepTasks') }}
|
||||
{{ removeButtonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="unlink('remove-all')"
|
||||
class="btn-cancel"
|
||||
@click="close()"
|
||||
>
|
||||
{{ $t('removeTasks') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="brokenChallengeTask.challenge.broken === 'CHALLENGE_CLOSED'">
|
||||
<h2 v-html="$t('challengeCompleted', {user: brokenChallengeTask.challenge.winner})"></h2>
|
||||
<div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
@click="unlink('keep-all')"
|
||||
>
|
||||
{{ $t('keepTasks') }}
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="unlink('remove-all')"
|
||||
>
|
||||
{{ $t('removeTasks') }}
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -67,23 +47,171 @@
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
padding-bottom: 2em;
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .broken-task-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $maroon-100;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
margin-top: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
::v-deep svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $maroon-100;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapActions } from '@/libs/store';
|
||||
import notifications from '@/mixins/notifications';
|
||||
import warningIcon from '@/assets/svg/warning_icon.svg?raw';
|
||||
|
||||
export default {
|
||||
mixins: [notifications],
|
||||
data () {
|
||||
return {
|
||||
brokenChallengeTask: {},
|
||||
icons: Object.freeze({
|
||||
warningIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
brokenType () {
|
||||
return this.brokenChallengeTask.challenge?.broken;
|
||||
},
|
||||
isSingleTask () {
|
||||
return this.brokenType === 'TASK_DELETED'
|
||||
|| this.brokenType === 'CHALLENGE_TASK_NOT_FOUND';
|
||||
},
|
||||
brokenChallengeTaskCount () {
|
||||
if (!this.brokenChallengeTask.challenge?.id) return 0;
|
||||
const challengeId = this.brokenChallengeTask.challenge.id;
|
||||
const tasksData = this.$store.state.tasks.data;
|
||||
let count = 0;
|
||||
['habits', 'dailys', 'todos', 'rewards'].forEach(type => {
|
||||
if (tasksData[type]) {
|
||||
count += tasksData[type].filter(
|
||||
t => t.challenge && t.challenge.id === challengeId,
|
||||
).length;
|
||||
}
|
||||
});
|
||||
return count;
|
||||
},
|
||||
modalTitle () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('brokenTask');
|
||||
}
|
||||
if (this.brokenType === 'CHALLENGE_CLOSED') {
|
||||
return this.$t('challengeCompleted');
|
||||
}
|
||||
return this.$t('brokenChallenge');
|
||||
},
|
||||
modalSubtitle () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('brokenTaskDescription');
|
||||
}
|
||||
if (this.brokenType === 'CHALLENGE_CLOSED') {
|
||||
return this.$t('challengeCompletedDescription', { user: this.brokenChallengeTask.challenge?.winner });
|
||||
}
|
||||
return this.$t('brokenChallengeDescription');
|
||||
},
|
||||
keepButtonText () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('keepIt');
|
||||
}
|
||||
return this.$t('keepTasks');
|
||||
},
|
||||
removeButtonText () {
|
||||
if (this.isSingleTask) {
|
||||
return this.$t('removeIt');
|
||||
}
|
||||
return this.$t('removeTasks');
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('handle-broken-task', task => {
|
||||
this.brokenChallengeTask = { ...task };
|
||||
@@ -99,8 +227,36 @@ export default {
|
||||
unlinkOneTask: 'tasks:unlinkOneTask',
|
||||
unlinkAllTasks: 'tasks:unlinkAllTasks',
|
||||
}),
|
||||
keepAction () {
|
||||
if (this.isSingleTask) {
|
||||
this.unlink('keep');
|
||||
} else {
|
||||
this.unlink('keep-all');
|
||||
}
|
||||
},
|
||||
async removeAction () {
|
||||
if (this.isSingleTask) {
|
||||
await this.removeTask();
|
||||
} else {
|
||||
await this.unlink('remove-all');
|
||||
}
|
||||
},
|
||||
async unlink (keepOption) {
|
||||
if (keepOption.indexOf('-all') !== -1) {
|
||||
if (keepOption === 'remove-all') {
|
||||
const count = this.brokenChallengeTaskCount;
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
title: count === 1 ? this.$t('deleteTask') : this.$t('deleteXTasks', { count }),
|
||||
description: this.$t('brokenChallengeTaskCount', { count }),
|
||||
message: this.$t('confirmDeleteTasks'),
|
||||
buttonText: count === 1 ? this.$t('deleteTask') : this.$t('deleteXTasks', { count }),
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
await this.unlinkAllTasks({
|
||||
challengeId: this.brokenChallengeTask.challenge.id,
|
||||
keep: keepOption,
|
||||
@@ -122,8 +278,14 @@ export default {
|
||||
});
|
||||
this.close();
|
||||
},
|
||||
removeTask () {
|
||||
if (!window.confirm('Are you sure you want to delete this task?')) return; // eslint-disable-line no-alert
|
||||
async removeTask () {
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
message: this.$t('sureDelete'),
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
this.destroyTask(this.brokenChallengeTask);
|
||||
this.close();
|
||||
},
|
||||
|
||||
@@ -87,7 +87,7 @@
|
||||
ref="tasksList"
|
||||
class="sortable-tasks"
|
||||
:disabled="activeFilter.label === 'scheduled' || !canBeDragged()"
|
||||
scrollSensitivity="64"
|
||||
scroll-sensitivity="64"
|
||||
:delay-on-touch-only="true"
|
||||
:delay="100"
|
||||
@update="taskSorted"
|
||||
|
||||
219
website/client/src/components/tasks/deleteTaskConfirmModal.vue
Normal file
219
website/client/src/components/tasks/deleteTaskConfirmModal.vue
Normal file
@@ -0,0 +1,219 @@
|
||||
<template>
|
||||
<b-modal
|
||||
id="delete-task-confirm-modal"
|
||||
:hide-footer="true"
|
||||
:hide-header="true"
|
||||
modal-class="delete-confirm-modal"
|
||||
centered
|
||||
>
|
||||
<div class="modal-content-wrapper">
|
||||
<div class="top-bar"></div>
|
||||
<div class="modal-body-content">
|
||||
<div
|
||||
class="icon-wrapper"
|
||||
v-html="icons.warningIcon"
|
||||
></div>
|
||||
<h2 class="modal-title">
|
||||
{{ displayTitle }}
|
||||
</h2>
|
||||
<p
|
||||
v-if="description"
|
||||
class="modal-description"
|
||||
>
|
||||
{{ description }}
|
||||
</p>
|
||||
<p class="modal-subtitle">
|
||||
{{ confirmationMessage }}
|
||||
</p>
|
||||
<div class="button-wrapper">
|
||||
<button
|
||||
class="btn btn-danger"
|
||||
@click="confirm()"
|
||||
>
|
||||
{{ buttonText }}
|
||||
</button>
|
||||
<button
|
||||
class="btn-cancel"
|
||||
@click="cancel()"
|
||||
>
|
||||
{{ $t('cancel') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</b-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import warningIcon from '@/assets/svg/warning_icon.svg?raw';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
confirmationMessage: '',
|
||||
taskType: '',
|
||||
description: '',
|
||||
customTitle: '',
|
||||
customButtonText: '',
|
||||
resolveCallback: null,
|
||||
icons: Object.freeze({
|
||||
warningIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
displayTitle () {
|
||||
if (this.customTitle) return this.customTitle;
|
||||
return this.$t('deleteType', { type: this.taskType });
|
||||
},
|
||||
buttonText () {
|
||||
if (this.customButtonText) return this.customButtonText;
|
||||
return this.displayTitle;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:delete-task-confirm', config => {
|
||||
this.confirmationMessage = config.message;
|
||||
this.taskType = config.taskType || '';
|
||||
this.description = config.description || '';
|
||||
this.customTitle = config.title || '';
|
||||
this.customButtonText = config.buttonText || '';
|
||||
this.resolveCallback = config.resolve;
|
||||
this.$root.$emit('bv::show::modal', 'delete-task-confirm-modal');
|
||||
});
|
||||
},
|
||||
beforeDestroy () {
|
||||
this.$root.$off('habitica:delete-task-confirm');
|
||||
},
|
||||
methods: {
|
||||
confirm () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(true);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
cancel () {
|
||||
if (this.resolveCallback) {
|
||||
this.resolveCallback(false);
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'delete-task-confirm-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/assets/scss/colors.scss';
|
||||
|
||||
::v-deep .delete-confirm-modal {
|
||||
.modal-dialog {
|
||||
max-width: 330px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.top-bar {
|
||||
height: 8px;
|
||||
background-color: $maroon-100;
|
||||
}
|
||||
|
||||
.modal-body-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 24px 24px;
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
margin-top: 40px;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
|
||||
::v-deep svg {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
margin-top: 16px;
|
||||
margin-bottom: 0;
|
||||
color: $maroon-100;
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-description {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.modal-description + .modal-subtitle {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.modal-subtitle {
|
||||
margin-top: 12px;
|
||||
margin-bottom: 0;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 700;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
text-align: center;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.button-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-top: 16px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-cancel {
|
||||
background: none;
|
||||
border: none;
|
||||
color: $purple-300;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
letter-spacing: 0;
|
||||
cursor: pointer;
|
||||
padding: 8px 16px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -3,421 +3,421 @@
|
||||
class="task-wrapper"
|
||||
draggable
|
||||
>
|
||||
<div
|
||||
class="task transition"
|
||||
:class="[{
|
||||
'groupTask': task.group.id,
|
||||
'task-not-editable': !teamManagerAccess,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
'link-exempt': !isChallengeTask && !isGroupTask,
|
||||
}, `type_${task.type}`
|
||||
]"
|
||||
@click="castEnd($event, task)"
|
||||
>
|
||||
<div
|
||||
class="d-flex"
|
||||
:class="{'task-not-scoreable': showTaskLockIcon }"
|
||||
class="task transition"
|
||||
:class="[{
|
||||
'groupTask': task.group.id,
|
||||
'task-not-editable': !teamManagerAccess,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
'link-exempt': !isChallengeTask && !isGroupTask,
|
||||
}, `type_${task.type}`
|
||||
]"
|
||||
@click="castEnd($event, task)"
|
||||
>
|
||||
<!-- Habits left side control-->
|
||||
<div
|
||||
v-if="task.type === 'habit'"
|
||||
class="left-control d-flex justify-content-center pt-3"
|
||||
:class="[{
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass
|
||||
}, controlClass.up.bg]"
|
||||
class="d-flex"
|
||||
:class="{'task-not-scoreable': showTaskLockIcon }"
|
||||
>
|
||||
<!-- Habits left side control-->
|
||||
<div
|
||||
class="task-control habit-control"
|
||||
v-if="task.type === 'habit'"
|
||||
class="left-control d-flex justify-content-center pt-3"
|
||||
:class="[{
|
||||
'habit-control-positive-enabled': task.up && !showTaskLockIcon,
|
||||
'habit-control-positive-disabled': !task.up && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.up.inner]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreUp')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.up && !showTaskLockIcon)"
|
||||
@click="score('up')"
|
||||
@keypress.enter="score('up')"
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass
|
||||
}, controlClass.up.bg]"
|
||||
>
|
||||
<div
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.up ? controlClass.up.icon : 'positive'"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon positive"
|
||||
v-html="icons.positive"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Dailies and todos left side control-->
|
||||
<div
|
||||
v-if="task.type === 'daily' || task.type === 'todo'"
|
||||
class="left-control d-flex justify-content-center"
|
||||
:class="[{
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass}, controlClass.bg]"
|
||||
>
|
||||
<div
|
||||
class="task-control daily-todo-control"
|
||||
:class="[
|
||||
{ 'task-not-scoreable': showTaskLockIcon },
|
||||
controlClass.inner,
|
||||
]"
|
||||
tabindex="0"
|
||||
role="checkbox"
|
||||
@click="score(showCheckIcon ? 'down' : 'up' )"
|
||||
@keypress.enter="score(showCheckIcon ? 'down' : 'up' )"
|
||||
>
|
||||
<div
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="controlClass.icon"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon check"
|
||||
:class="{
|
||||
'display-check-icon': showCheckIcon,
|
||||
[controlClass.checkbox]: true,
|
||||
}"
|
||||
v-html="icons.check"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task title, description and icons-->
|
||||
<div
|
||||
class="task-content"
|
||||
:class="contentClass"
|
||||
>
|
||||
<div
|
||||
class="task-clickable-area pt-1 pl-75 pb-0"
|
||||
:class="{ 'cursor-auto': !teamManagerAccess }"
|
||||
tabindex="0"
|
||||
@click="edit($event, task)"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3
|
||||
v-markdown="task.text"
|
||||
class="task-title markdown"
|
||||
:class="{ 'has-notes': task.notes }"
|
||||
></h3>
|
||||
<menu-dropdown
|
||||
v-if="!isRunningYesterdailies && showOptions"
|
||||
ref="taskDropdown"
|
||||
v-b-tooltip.hover.top="$t('options')"
|
||||
tabindex="0"
|
||||
class="task-dropdown mr-1"
|
||||
:right="task.type === 'reward'"
|
||||
>
|
||||
<div slot="dropdown-toggle">
|
||||
<div
|
||||
class="svg-icon dropdown-icon"
|
||||
v-html="icons.menu"
|
||||
></div>
|
||||
</div>
|
||||
<div slot="dropdown-content">
|
||||
<div
|
||||
v-if="showEdit"
|
||||
ref="editTaskItem"
|
||||
class="dropdown-item edit-task-item"
|
||||
tabindex="0"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline edit-icon"
|
||||
v-html="icons.edit"
|
||||
></span>
|
||||
<span class="text">{{ $t('edit') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="moveToTop"
|
||||
@keypress.enter="moveToTop"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline push-to-top"
|
||||
v-html="icons.top"
|
||||
></span>
|
||||
<span class="text">{{ $t('taskToTop') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="moveToBottom"
|
||||
@keypress.enter="moveToBottom"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline push-to-bottom"
|
||||
v-html="icons.bottom"
|
||||
></span>
|
||||
<span class="text">{{ $t('taskToBottom') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDelete"
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="destroy"
|
||||
@keypress.enter="destroy"
|
||||
>
|
||||
<span class="dropdown-icon-item delete-task-item">
|
||||
<span
|
||||
class="svg-icon inline delete"
|
||||
v-html="icons.delete"
|
||||
></span>
|
||||
<span class="text">{{ $t('delete') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</menu-dropdown>
|
||||
</div>
|
||||
<div
|
||||
v-markdown="task.notes"
|
||||
class="task-notes small-text"
|
||||
:class="{'has-checklist': task.notes && hasChecklist}"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="canViewchecklist"
|
||||
class="checklist"
|
||||
:class="{isOpen: !task.collapseChecklist}"
|
||||
>
|
||||
<div class="d-inline-flex">
|
||||
<div
|
||||
v-b-tooltip.hover.right="$t(`${task.collapseChecklist
|
||||
? 'expand': 'collapse'}Checklist`)"
|
||||
class="collapse-checklist mb-2 d-flex align-items-center expand-toggle"
|
||||
:class="{open: !task.collapseChecklist}"
|
||||
tabindex="0"
|
||||
@click="collapseChecklist(task)"
|
||||
@keypress.enter="collapseChecklist(task)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.checklist"
|
||||
></div>
|
||||
<span>{{ checklistProgress }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-for="item in task.checklist"
|
||||
v-if="!task.collapseChecklist"
|
||||
:key="item.id"
|
||||
class="custom-control custom-checkbox checklist-item"
|
||||
:class="{'checklist-item-done': item.completed, 'cursor-auto': showTaskLockIcon}"
|
||||
>
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<input
|
||||
:id="`checklist-${item.id}-${random}`"
|
||||
class="custom-control-input"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
:checked="item.completed"
|
||||
:disabled="castingSpell || showTaskLockIcon"
|
||||
@change="toggleChecklistItem(item)"
|
||||
@keypress.enter="toggleChecklistItem(item)"
|
||||
>
|
||||
<label
|
||||
v-markdown="item.text"
|
||||
class="custom-control-label"
|
||||
:class="{ 'cursor-auto': showTaskLockIcon }"
|
||||
:for="`checklist-${item.id}-${random}`"
|
||||
></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icons small-text d-flex align-items-center">
|
||||
<div
|
||||
v-if="task.type === 'todo' && task.date"
|
||||
class="d-flex align-items-center"
|
||||
:class="{'due-overdue': checkIfOverdue() }"
|
||||
class="task-control habit-control"
|
||||
:class="[{
|
||||
'habit-control-positive-enabled': task.up && !showTaskLockIcon,
|
||||
'habit-control-positive-disabled': !task.up && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.up.inner]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreUp')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.up && !showTaskLockIcon)"
|
||||
@click="score('up')"
|
||||
@keypress.enter="score('up')"
|
||||
>
|
||||
<div
|
||||
v-b-tooltip.hover.bottom="$t('dueDate')"
|
||||
class="svg-icon calendar my-auto"
|
||||
v-html="icons.calendar"
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.up ? controlClass.up.icon : 'positive'"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon positive"
|
||||
v-html="icons.positive"
|
||||
></div>
|
||||
<span>{{ formatDueDate() }}</span>
|
||||
</div>
|
||||
<div class="icons-right d-flex justify-content-end">
|
||||
</div>
|
||||
<!-- Dailies and todos left side control-->
|
||||
<div
|
||||
v-if="task.type === 'daily' || task.type === 'todo'"
|
||||
class="left-control d-flex justify-content-center"
|
||||
:class="[{
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass}, controlClass.bg]"
|
||||
>
|
||||
<div
|
||||
class="task-control daily-todo-control"
|
||||
:class="[
|
||||
{ 'task-not-scoreable': showTaskLockIcon },
|
||||
controlClass.inner,
|
||||
]"
|
||||
tabindex="0"
|
||||
role="checkbox"
|
||||
@click="score(showCheckIcon ? 'down' : 'up' )"
|
||||
@keypress.enter="score(showCheckIcon ? 'down' : 'up' )"
|
||||
>
|
||||
<div
|
||||
v-if="showStreak"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
v-b-tooltip.hover.bottom="task.type === 'daily'
|
||||
? $t('streakCounter') : $t('counter')"
|
||||
class="svg-icon streak"
|
||||
v-html="icons.streak"
|
||||
></div>
|
||||
<span v-if="task.type === 'daily'">{{ task.streak }}</span>
|
||||
<span v-if="task.type === 'habit'">
|
||||
<span
|
||||
v-if="task.up && task.counterUp != 0 && task.down"
|
||||
class="m-0"
|
||||
>+{{ task.counterUp }}</span>
|
||||
<span
|
||||
v-else-if=" task.counterUp !=0 && task.counterDown ==0"
|
||||
class="m-0"
|
||||
>{{ task.counterUp }}</span>
|
||||
<span
|
||||
v-else-if="task.up"
|
||||
class="m-0"
|
||||
>0</span>
|
||||
<span
|
||||
v-if="task.up && task.down"
|
||||
class="m-0"
|
||||
> | </span>
|
||||
<span
|
||||
v-if="task.down && task.counterDown != 0 && task.up"
|
||||
class="m-0"
|
||||
>-{{ task.counterDown }}</span>
|
||||
<span
|
||||
v-else-if="task.counterDown !=0 && task.counterUp ==0"
|
||||
class="m-0"
|
||||
>{{ task.counterDown }}</span>
|
||||
<span
|
||||
v-else-if="task.down"
|
||||
class="m-0"
|
||||
>0</span>
|
||||
</span>
|
||||
</div>
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="controlClass.icon"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-if="task.challenge && task.challenge.id"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
v-if="!task.challenge.broken"
|
||||
v-b-tooltip.hover.bottom="shortName"
|
||||
class="svg-icon challenge"
|
||||
v-html="icons.challenge"
|
||||
></div>
|
||||
<div
|
||||
v-if="task.challenge.broken"
|
||||
v-b-tooltip.hover.bottom="$t('brokenChaLink')"
|
||||
class="svg-icon challenge broken"
|
||||
@click="handleBrokenTask(task)"
|
||||
v-html="icons.brokenChallengeIcon"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasTags && !task.group.id"
|
||||
:id="`tags-icon-${task._id}`"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon tags"
|
||||
v-html="icons.tags"
|
||||
></div>
|
||||
</div>
|
||||
<b-popover
|
||||
v-if="hasTags && !task.group.id"
|
||||
:target="`tags-icon-${task._id}`"
|
||||
triggers="hover"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="tags-popover">
|
||||
<div class="d-flex align-items-center tags-container">
|
||||
v-else
|
||||
class="svg-icon check"
|
||||
:class="{
|
||||
'display-check-icon': showCheckIcon,
|
||||
[controlClass.checkbox]: true,
|
||||
}"
|
||||
v-html="icons.check"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Task title, description and icons-->
|
||||
<div
|
||||
class="task-content"
|
||||
:class="contentClass"
|
||||
>
|
||||
<div
|
||||
class="task-clickable-area pt-1 pl-75 pb-0"
|
||||
:class="{ 'cursor-auto': !teamManagerAccess }"
|
||||
tabindex="0"
|
||||
@click="edit($event, task)"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<div class="d-flex justify-content-between">
|
||||
<h3
|
||||
v-markdown="task.text"
|
||||
class="task-title markdown"
|
||||
:class="{ 'has-notes': task.notes }"
|
||||
></h3>
|
||||
<menu-dropdown
|
||||
v-if="!isRunningYesterdailies && showOptions"
|
||||
ref="taskDropdown"
|
||||
v-b-tooltip.hover.top="$t('options')"
|
||||
tabindex="0"
|
||||
class="task-dropdown mr-1"
|
||||
:right="task.type === 'reward'"
|
||||
>
|
||||
<div slot="dropdown-toggle">
|
||||
<div
|
||||
v-once
|
||||
class="tags-popover-title"
|
||||
>
|
||||
{{ `${$t('tags')}:` }}
|
||||
</div>
|
||||
<div
|
||||
v-for="tag in getTagsFor(task)"
|
||||
:key="tag"
|
||||
v-markdown="tag"
|
||||
class="tag-label"
|
||||
class="svg-icon dropdown-icon"
|
||||
v-html="icons.menu"
|
||||
></div>
|
||||
</div>
|
||||
<div slot="dropdown-content">
|
||||
<div
|
||||
v-if="showEdit"
|
||||
ref="editTaskItem"
|
||||
class="dropdown-item edit-task-item"
|
||||
tabindex="0"
|
||||
@keypress.enter="edit($event, task)"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline edit-icon"
|
||||
v-html="icons.edit"
|
||||
></span>
|
||||
<span class="text">{{ $t('edit') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="moveToTop"
|
||||
@keypress.enter="moveToTop"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline push-to-top"
|
||||
v-html="icons.top"
|
||||
></span>
|
||||
<span class="text">{{ $t('taskToTop') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="moveToBottom"
|
||||
@keypress.enter="moveToBottom"
|
||||
>
|
||||
<span class="dropdown-icon-item">
|
||||
<span
|
||||
class="svg-icon inline push-to-bottom"
|
||||
v-html="icons.bottom"
|
||||
></span>
|
||||
<span class="text">{{ $t('taskToBottom') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="showDelete"
|
||||
class="dropdown-item"
|
||||
tabindex="0"
|
||||
@click="destroy"
|
||||
@keypress.enter="destroy"
|
||||
>
|
||||
<span class="dropdown-icon-item delete-task-item">
|
||||
<span
|
||||
class="svg-icon inline delete"
|
||||
v-html="icons.delete"
|
||||
></span>
|
||||
<span class="text">{{ $t('delete') }}</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</menu-dropdown>
|
||||
</div>
|
||||
<div
|
||||
v-markdown="task.notes"
|
||||
class="task-notes small-text"
|
||||
:class="{'has-checklist': task.notes && hasChecklist}"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="canViewchecklist"
|
||||
class="checklist"
|
||||
:class="{isOpen: !task.collapseChecklist}"
|
||||
>
|
||||
<div class="d-inline-flex">
|
||||
<div
|
||||
v-b-tooltip.hover.right="$t(`${task.collapseChecklist
|
||||
? 'expand': 'collapse'}Checklist`)"
|
||||
class="collapse-checklist mb-2 d-flex align-items-center expand-toggle"
|
||||
:class="{open: !task.collapseChecklist}"
|
||||
tabindex="0"
|
||||
@click="collapseChecklist(task)"
|
||||
@keypress.enter="collapseChecklist(task)"
|
||||
>
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon"
|
||||
v-html="icons.checklist"
|
||||
></div>
|
||||
<span>{{ checklistProgress }}</span>
|
||||
</div>
|
||||
</b-popover>
|
||||
</div>
|
||||
<!-- eslint-disable vue/no-use-v-if-with-v-for -->
|
||||
<div
|
||||
v-for="item in task.checklist"
|
||||
v-if="!task.collapseChecklist"
|
||||
:key="item.id"
|
||||
class="custom-control custom-checkbox checklist-item"
|
||||
:class="{'checklist-item-done': item.completed, 'cursor-auto': showTaskLockIcon}"
|
||||
>
|
||||
<!-- eslint-enable vue/no-use-v-if-with-v-for -->
|
||||
<input
|
||||
:id="`checklist-${item.id}-${random}`"
|
||||
class="custom-control-input"
|
||||
tabindex="0"
|
||||
type="checkbox"
|
||||
:checked="item.completed"
|
||||
:disabled="castingSpell || showTaskLockIcon"
|
||||
@change="toggleChecklistItem(item)"
|
||||
@keypress.enter="toggleChecklistItem(item)"
|
||||
>
|
||||
<label
|
||||
v-markdown="item.text"
|
||||
class="custom-control-label"
|
||||
:class="{ 'cursor-auto': showTaskLockIcon }"
|
||||
:for="`checklist-${item.id}-${random}`"
|
||||
></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="icons small-text d-flex align-items-center">
|
||||
<div
|
||||
v-if="task.type === 'todo' && task.date"
|
||||
class="d-flex align-items-center"
|
||||
:class="{'due-overdue': checkIfOverdue() }"
|
||||
>
|
||||
<div
|
||||
v-b-tooltip.hover.bottom="$t('dueDate')"
|
||||
class="svg-icon calendar my-auto"
|
||||
v-html="icons.calendar"
|
||||
></div>
|
||||
<span>{{ formatDueDate() }}</span>
|
||||
</div>
|
||||
<div class="icons-right d-flex justify-content-end">
|
||||
<div
|
||||
v-if="showStreak"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
v-b-tooltip.hover.bottom="task.type === 'daily'
|
||||
? $t('streakCounter') : $t('counter')"
|
||||
class="svg-icon streak"
|
||||
v-html="icons.streak"
|
||||
></div>
|
||||
<span v-if="task.type === 'daily'">{{ task.streak }}</span>
|
||||
<span v-if="task.type === 'habit'">
|
||||
<span
|
||||
v-if="task.up && task.counterUp != 0 && task.down"
|
||||
class="m-0"
|
||||
>+{{ task.counterUp }}</span>
|
||||
<span
|
||||
v-else-if=" task.counterUp !=0 && task.counterDown ==0"
|
||||
class="m-0"
|
||||
>{{ task.counterUp }}</span>
|
||||
<span
|
||||
v-else-if="task.up"
|
||||
class="m-0"
|
||||
>0</span>
|
||||
<span
|
||||
v-if="task.up && task.down"
|
||||
class="m-0"
|
||||
> | </span>
|
||||
<span
|
||||
v-if="task.down && task.counterDown != 0 && task.up"
|
||||
class="m-0"
|
||||
>-{{ task.counterDown }}</span>
|
||||
<span
|
||||
v-else-if="task.counterDown !=0 && task.counterUp ==0"
|
||||
class="m-0"
|
||||
>{{ task.counterDown }}</span>
|
||||
<span
|
||||
v-else-if="task.down"
|
||||
class="m-0"
|
||||
>0</span>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="task.challenge && task.challenge.id"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
v-if="!task.challenge.broken"
|
||||
v-b-tooltip.hover.bottom="shortName"
|
||||
class="svg-icon challenge"
|
||||
v-html="icons.challenge"
|
||||
></div>
|
||||
<div
|
||||
v-if="task.challenge.broken"
|
||||
v-b-tooltip.hover.bottom="$t('brokenChaLink')"
|
||||
class="svg-icon challenge broken"
|
||||
@click="handleBrokenTask(task)"
|
||||
v-html="icons.brokenChallengeIcon"
|
||||
></div>
|
||||
</div>
|
||||
<div
|
||||
v-if="hasTags && !task.group.id"
|
||||
:id="`tags-icon-${task._id}`"
|
||||
class="d-flex align-items-center"
|
||||
>
|
||||
<div
|
||||
class="svg-icon tags"
|
||||
v-html="icons.tags"
|
||||
></div>
|
||||
</div>
|
||||
<b-popover
|
||||
v-if="hasTags && !task.group.id"
|
||||
:target="`tags-icon-${task._id}`"
|
||||
triggers="hover"
|
||||
placement="bottom"
|
||||
>
|
||||
<div class="tags-popover">
|
||||
<div class="d-flex align-items-center tags-container">
|
||||
<div
|
||||
v-once
|
||||
class="tags-popover-title"
|
||||
>
|
||||
{{ `${$t('tags')}:` }}
|
||||
</div>
|
||||
<div
|
||||
v-for="tag in getTagsFor(task)"
|
||||
:key="tag"
|
||||
v-markdown="tag"
|
||||
class="tag-label"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</b-popover>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Habits right side control-->
|
||||
<div
|
||||
v-if="task.type === 'habit'"
|
||||
class="right-control d-flex justify-content-center pt-3"
|
||||
:class="[{
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass}, controlClass.down.bg]"
|
||||
>
|
||||
<!-- Habits right side control-->
|
||||
<div
|
||||
class="task-control habit-control"
|
||||
v-if="task.type === 'habit'"
|
||||
class="right-control d-flex justify-content-center pt-3"
|
||||
:class="[{
|
||||
'habit-control-negative-enabled': task.down && !showTaskLockIcon,
|
||||
'habit-control-negative-disabled': !task.down && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.down.inner]"
|
||||
'control-bottom-box': task.group.id && !isOpenTask,
|
||||
'control-top-box': approvalsClass}, controlClass.down.bg]"
|
||||
>
|
||||
<div
|
||||
class="task-control habit-control"
|
||||
:class="[{
|
||||
'habit-control-negative-enabled': task.down && !showTaskLockIcon,
|
||||
'habit-control-negative-disabled': !task.down && !showTaskLockIcon,
|
||||
'task-not-scoreable': showTaskLockIcon,
|
||||
}, controlClass.down.inner]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreDown')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.down && !showTaskLockIcon)"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
<div
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.down ? controlClass.down.icon : 'negative'"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon negative"
|
||||
v-html="icons.negative"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Rewards right side control-->
|
||||
<div
|
||||
v-if="task.type === 'reward'"
|
||||
class="right-control d-flex align-items-center justify-content-center reward-control"
|
||||
:class="[
|
||||
{ 'task-not-scoreable': showTaskLockIcon },
|
||||
controlClass.bg,
|
||||
]"
|
||||
tabindex="0"
|
||||
role="button"
|
||||
:aria-label="$t('scoreDown')"
|
||||
:aria-disabled="showTaskLockIcon || (!task.down && !showTaskLockIcon)"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
<div
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon lock"
|
||||
:class="task.down ? controlClass.down.icon : 'negative'"
|
||||
class="svg-icon color lock"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon negative"
|
||||
v-html="icons.negative"
|
||||
class="svg-icon mb-1"
|
||||
v-html="icons.gold"
|
||||
></div>
|
||||
<div class="small-text">
|
||||
{{ task.value }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Rewards right side control-->
|
||||
<div
|
||||
v-if="task.type === 'reward'"
|
||||
class="right-control d-flex align-items-center justify-content-center reward-control"
|
||||
:class="[
|
||||
{ 'task-not-scoreable': showTaskLockIcon },
|
||||
controlClass.bg,
|
||||
]"
|
||||
tabindex="0"
|
||||
@click="score('down')"
|
||||
@keypress.enter="score('down')"
|
||||
>
|
||||
<div
|
||||
v-if="showTaskLockIcon"
|
||||
class="svg-icon color lock"
|
||||
v-html="icons.lock"
|
||||
></div>
|
||||
<div
|
||||
v-else
|
||||
class="svg-icon mb-1"
|
||||
v-html="icons.gold"
|
||||
></div>
|
||||
<div class="small-text">
|
||||
{{ task.value }}
|
||||
</div>
|
||||
</div>
|
||||
<approval-footer
|
||||
v-if="task.group.id && !isOpenTask"
|
||||
:task="task"
|
||||
:group="group"
|
||||
/>
|
||||
</div>
|
||||
<approval-footer
|
||||
v-if="task.group.id && !isOpenTask"
|
||||
:task="task"
|
||||
:group="group"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1177,9 +1177,16 @@ export default {
|
||||
moveToBottom () {
|
||||
this.$emit('moveTo', this.task, 'bottom');
|
||||
},
|
||||
destroy () {
|
||||
async destroy () {
|
||||
const type = this.$t(this.task.type);
|
||||
if (!window.confirm(this.$t('sureDeleteType', { type }))) return; // eslint-disable-line no-alert
|
||||
const confirmed = await new Promise(resolve => {
|
||||
this.$root.$emit('habitica:delete-task-confirm', {
|
||||
message: this.$t('sureDeleteType', { type }),
|
||||
taskType: type,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
if (!confirmed) return;
|
||||
this.destroyTask(this.task);
|
||||
this.$emit('taskDestroyed', this.task);
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -25,8 +25,8 @@
|
||||
type="checkbox"
|
||||
:checked="isChecked"
|
||||
:value="value"
|
||||
@change="handleChange"
|
||||
:disabled="disabled"
|
||||
@change="handleChange"
|
||||
>
|
||||
<label
|
||||
class="toggle-switch-label"
|
||||
|
||||
@@ -39,7 +39,15 @@ export default {
|
||||
};
|
||||
|
||||
const purchaseForKey = currencyToPurchaseForKey[currency];
|
||||
return window.confirm(this.$t(purchaseForKey, { cost })); // eslint-disable-line no-alert
|
||||
|
||||
return new Promise(resolve => {
|
||||
this.$root.$emit('habitica:purchase-confirm', {
|
||||
message: this.$t(purchaseForKey, { cost }),
|
||||
currency,
|
||||
cost,
|
||||
resolve,
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<template>
|
||||
<tr>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="!mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<h3
|
||||
@@ -18,8 +19,9 @@
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
<td colspan="3"
|
||||
<td
|
||||
v-if="mixinData.inlineSettingMixin.modalVisible"
|
||||
colspan="3"
|
||||
>
|
||||
<h3
|
||||
v-once
|
||||
@@ -59,8 +61,8 @@
|
||||
{{ $t('performanceAnalytics') }}
|
||||
</label>
|
||||
<toggle-switch
|
||||
class="mb-auto"
|
||||
v-model="user.preferences.analyticsConsent"
|
||||
class="mb-auto"
|
||||
@change="prefToggled()"
|
||||
/>
|
||||
</div>
|
||||
@@ -151,14 +153,14 @@ import { mapState } from '@/libs/store';
|
||||
import alert from '@/assets/svg/for-css/alert.svg?raw';
|
||||
|
||||
export default {
|
||||
mixins: [
|
||||
GenericUserPreferencesMixin,
|
||||
InlineSettingMixin,
|
||||
],
|
||||
components: {
|
||||
SaveCancelButtons,
|
||||
ToggleSwitch,
|
||||
},
|
||||
mixins: [
|
||||
GenericUserPreferencesMixin,
|
||||
InlineSettingMixin,
|
||||
],
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
<bug-report-success-modal v-if="isUserLoaded" />
|
||||
<external-link-modal />
|
||||
<birthday-modal />
|
||||
<purchase-confirm-modal v-if="isUserLoaded" />
|
||||
<delete-task-confirm-modal v-if="isUserLoaded" />
|
||||
<template v-if="isUserLoaded">
|
||||
<privacy-banner />
|
||||
<chat-banner />
|
||||
@@ -138,6 +140,8 @@ import paymentsSuccessModal from '@/components/payments/successModal';
|
||||
import subCancelModalConfirm from '@/components/payments/cancelModalConfirm';
|
||||
import subCanceledModal from '@/components/payments/canceledModal';
|
||||
import externalLinkModal from '@/components/externalLinkModal.vue';
|
||||
import purchaseConfirmModal from '@/components/shops/purchaseConfirmModal.vue';
|
||||
import deleteTaskConfirmModal from '@/components/tasks/deleteTaskConfirmModal.vue';
|
||||
|
||||
import spellsMixin from '@/mixins/spells';
|
||||
import {
|
||||
@@ -172,6 +176,8 @@ export default {
|
||||
bugReportModal,
|
||||
bugReportSuccessModal,
|
||||
externalLinkModal,
|
||||
purchaseConfirmModal,
|
||||
deleteTaskConfirmModal,
|
||||
},
|
||||
mixins: [notifications, spellsMixin],
|
||||
data () {
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
"challenge": "Challenge",
|
||||
"challengeDetails": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
|
||||
"brokenChaLink": "Broken Challenge Link",
|
||||
"brokenTask": "Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?",
|
||||
"brokenTask": "Broken Challenge Link",
|
||||
"brokenTaskDescription": "This task was part of a challenge, but has been removed from it. What would you like to do?",
|
||||
"keepIt": "Keep It",
|
||||
"removeIt": "Remove It",
|
||||
"removeTasks": "Remove Tasks",
|
||||
"brokenChallenge": "Broken Challenge Link: this task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?",
|
||||
"challengeCompleted": "This challenge has been completed, and the winner was <span class=\"badge\"><%- user %></span>! What to do with the orphan tasks?",
|
||||
"brokenChallenge": "Broken Challenge Link",
|
||||
"brokenChallengeDescription": "This task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?",
|
||||
"challengeCompleted": "Challenge Completed!",
|
||||
"challengeCompletedDescription": "The winner was <%- user %>! What to do with the orphan tasks?",
|
||||
"unsubChallenge": "Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?",
|
||||
"challenges": "Challenges",
|
||||
"endDate": "Ends",
|
||||
|
||||
@@ -242,5 +242,6 @@
|
||||
"whyReportingPlayerPlaceholder": "Reason for report",
|
||||
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica’s Community Guidelines.",
|
||||
"targetUserNotExist": "Target User: '<%= userName %>' does not exist.",
|
||||
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>."
|
||||
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
|
||||
"confirmPurchase": "Confirm Purchase"
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
"fortify": "Fortify",
|
||||
"fortifyComplete": "Fortify complete!",
|
||||
"deleteTaskType": "Delete this <%= type %>",
|
||||
"sureDeleteType": "Are you sure you want to delete this <%= type %>?",
|
||||
"sureDeleteType": "Are you sure you want to delete this task?",
|
||||
"streakCoins": "Streak Bonus!",
|
||||
"taskToTop": "To top",
|
||||
"taskToBottom": "To bottom",
|
||||
@@ -138,5 +138,10 @@
|
||||
"pressEnterToAddTag": "Press Enter to add tag: '<%= tagName %>'",
|
||||
"taskSummary": "<%= type %> Summary",
|
||||
"scoreUp": "Score up",
|
||||
"scoreDown": "Score down"
|
||||
"scoreDown": "Score down",
|
||||
"deleteType": "Delete <%= type %>",
|
||||
"deleteTask": "Delete Task",
|
||||
"deleteXTasks": "Delete <%= count %> Tasks",
|
||||
"brokenChallengeTaskCount": "This is one of <%= count %> tasks that are part of a Challenge that no longer exists.",
|
||||
"confirmDeleteTasks": "Would you like to delete the tasks?"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user