Drop Cap Notification, Modal and A/B Test (#12651)

* add drop cap notification

* add drop cap notification

* add dismissible notification

* fix(notification): correct remove icon positioning

* track events

* add modal

* add back files

* fix links and add missing analytics

* fix rounded borders and hide sub info for subscribers

* a/b test

* fix comparison

* Translated using Weblate (Spanish)

Currently translated at 98.2% (55 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/es/

Translated using Weblate (Spanish)

Currently translated at 99.4% (179 of 180 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/es/

Merge branch 'origin/develop' into Weblate.

Translated using Weblate (Spanish)

Currently translated at 99.4% (175 of 176 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/

Translated using Weblate (Spanish (Latin America))

Currently translated at 98.6% (359 of 364 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/

Translated using Weblate (Spanish)

Currently translated at 85.7% (151 of 176 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/es/

Translated using Weblate (Spanish)

Currently translated at 95.3% (538 of 564 strings)

Translation: Habitica/Backgrounds
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/es/

Translated using Weblate (Spanish (Latin America))

Currently translated at 98.6% (359 of 364 strings)

Translation: Habitica/Groups
Translate-URL: https://translate.habitica.com/projects/habitica/groups/es_419/

Translated using Weblate (French)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/fr/

Translated using Weblate (German)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/de/

Translated using Weblate (French)

Currently translated at 100.0% (718 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/fr/

Translated using Weblate (German)

Currently translated at 100.0% (718 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/de/

Translated using Weblate (Czech)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Spells
Translate-URL: https://translate.habitica.com/projects/habitica/spells/cs/

Translated using Weblate (Japanese)

Currently translated at 100.0% (175 of 175 strings)

Translation: Habitica/Subscriber
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ja/

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/

Translated using Weblate (Italian)

Currently translated at 100.0% (718 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/

Translated using Weblate (Czech)

Currently translated at 100.0% (180 of 180 strings)

Translation: Habitica/Settings
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/

Translated using Weblate (Basque)

Currently translated at 100.0% (2 of 2 strings)

Translation: Habitica/Noscript
Translate-URL: https://translate.habitica.com/projects/habitica/noscript/eu/

Translated using Weblate (Basque)

Currently translated at 6.5% (8 of 123 strings)

Translation: Habitica/Communityguidelines
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/eu/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/zh_Hans/

Translated using Weblate (Japanese)

Currently translated at 100.0% (56 of 56 strings)

Translation: Habitica/Messages
Translate-URL: https://translate.habitica.com/projects/habitica/messages/ja/

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (718 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (718 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.8% (717 of 718 strings)

Translation: Habitica/Questscontent
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/pt_BR/

* clarify a/b test values

* add tests

* refactor user dropdown

* fix hover state

* fix user dropdown

* fix user menu hierarchy

* restore i18n files to release version

Co-authored-by: Melior <admin@habitica.com>
This commit is contained in:
Matteo Pagliazzi
2020-10-16 19:50:54 +02:00
committed by GitHub
parent 2832226539
commit e04d4e8bea
24 changed files with 689 additions and 87 deletions
@@ -0,0 +1,250 @@
<template>
<b-modal
id="drop-cap-reached"
size="md"
:hide-header="true"
:hide-footer="!hasSubscription"
>
<div class="text-center">
<div
class="modal-close"
@click="close()"
>
<div
v-once
class="svg-icon"
v-html="icons.close"
></div>
</div>
<h1
v-once
class="header purple"
>
{{ $t('dropCapReached') }}
</h1>
<div class="max-items-wrapper d-flex align-items-center justify-content-center">
<div
class="svg-icon sparkles sparkles-rotate"
v-html="icons.sparkles"
></div>
<div class="max-items-module d-flex align-items-center justify-content-center flex-column">
<h1 class="max-items">
{{ maxItems }}
</h1>
<span
v-once
class="items-text"
>{{ $t('items') }}</span>
</div>
<div
class="svg-icon sparkles"
v-html="icons.sparkles"
></div>
</div>
<p
v-once
class="mb-4"
>
{{ $t('dropCapExplanation') }}
</p>
<a
v-once
class="standard-link d-block mb-3"
@click="toWiki()"
>
{{ $t('dropCapLearnMore') }}
</a>
</div>
<div
slot="modal-footer"
class="footer"
>
<span
v-once
class="purple d-block font-weight-bold mb-3 mt-3"
>
{{ $t('lookingForMoreItems') }}
</span>
<img
class="swords mb-3"
srcset="
~@/assets/images/swords.png,
~@/assets/images/swords@2x.png 2x,
~@/assets/images/swords@3x.png 3x"
src="~@/assets/images/swords.png"
>
<p
v-once
class="subs-benefits mb-3"
>
{{ $t('dropCapSubs') }}
</p>
<button
v-once
class="btn btn-primary"
@click="toLearnMore()"
>
{{ $t('learnMore') }}
</button>
</div>
</b-modal>
</template>
<style lang="scss">
@import '~@/assets/scss/colors.scss';
#drop-cap-reached {
.modal-body {
padding: 0 1.5rem;
}
.modal-footer {
background: $gray-700;
border-top: none;
padding: 0 1.5rem 2rem 1.5rem;
}
.modal-dialog {
width: 20.625rem;
font-size: 0.875rem;
line-height: 1.71;
text-align: center;
}
}
</style>
<style lang="scss" scoped>
@import '~@/assets/scss/colors.scss';
.modal-close {
position: absolute;
width: 18px;
height: 18px;
padding: 4px;
right: 16px;
top: 16px;
cursor: pointer;
.svg-icon {
width: 12px;
height: 12px;
}
}
.subs-benefits {
font-size: 0.75rem;
line-height: 1.33;
font-style: normal;
}
.purple {
color: $purple-300;
}
.header {
font-size: 1.25rem;
line-height: 1.4;
text-align: center;
margin-top: 2rem;
}
.sparkles {
width: 2.5rem;
height: 4rem;
&.sparkles-rotate {
transform: rotate(180deg);
}
}
.max-items-wrapper {
margin: 17px auto;
}
.max-items-module {
background: white;
border-radius: 92px;
border: 8px solid $purple-400;
width: 92px;
height: 92px;
margin-left: 17px;
margin-right: 17px;
.items-text {
font-size: 0.75rem;
line-height: 1.33;
color: $gray-100;
}
}
.max-items {
font-size: 2rem;
line-height: 1.25;
color: $purple-300;
margin: 0;
}
.swords {
width: 7rem;
height: 3rem;
}
</style>
<script>
import closeIcon from '@/assets/svg/close.svg';
import sparkles from '@/assets/svg/star-group.svg';
import * as Analytics from '@/libs/analytics';
import { mapState } from '@/libs/store';
export default {
data () {
return {
icons: Object.freeze({
close: closeIcon,
sparkles,
}),
maxItems: null,
};
},
computed: {
...mapState({ user: 'user.data' }),
hasSubscription () {
return Boolean(this.user.purchased.plan.customerId);
},
},
mounted () {
this.$root.$on('habitica:drop-cap-reached', notification => {
this.maxItems = notification.data.items;
this.$root.$emit('bv::show::modal', 'drop-cap-reached');
});
},
beforeDestroy () {
this.$root.$off('habitica:drop-cap-reached');
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'drop-cap-reached');
},
toWiki () {
window.open('https://habitica.fandom.com/wiki/Drops', '_blank');
Analytics.track({
hitType: 'event',
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Modal > Wiki',
});
},
toLearnMore () {
this.close();
this.$router.push('/user/settings/subscription');
Analytics.track({
hitType: 'event',
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Modal > Subscriptions',
});
},
},
};
</script>
@@ -22,12 +22,15 @@
export default {
props: {
categories: {
type: Array,
required: true,
},
owner: {
type: Boolean,
default: false,
},
member: {
type: Boolean,
default: false,
},
},
@@ -10,7 +10,7 @@
@import '~@/assets/scss/colors.scss';
.message-count {
background-color: $blue-50;
background-color: $red-50;
border-radius: 50%;
height: 20px;
width: 20px;
@@ -31,7 +31,6 @@
right: 0.3em;
top: -0.8em;
padding: 0.2em;
background-color: $red-50;
}
.message-count.top-count-gray {
@@ -118,11 +118,11 @@
}
.notification-remove {
position: absolute;
width: 18px;
height: 18px;
padding: 4px;
right: 24px;
position: relative;
width: 10px;
height: 10px;
right: 0px;
top: 10.5px;
.svg-icon {
width: 10px;
@@ -0,0 +1,43 @@
<template>
<base-notification
:can-remove="canRemove"
:has-icon="false"
:notification="notification"
:read-after-click="true"
@click="action"
>
<div
slot="content"
class="notification-bold-blue"
>
{{ $t('dropCapReached') }}
</div>
</base-notification>
</template>
<script>
import BaseNotification from './base';
import * as Analytics from '@/libs/analytics';
export default {
components: {
BaseNotification,
},
props: {
notification: { type: Object, required: true },
canRemove: { type: Boolean, required: true },
},
methods: {
action () {
this.$root.$emit('habitica:drop-cap-reached', this.notification);
Analytics.track({
hitType: 'event',
eventCategory: 'drop-cap-reached',
eventAction: 'click',
eventLabel: 'Drop Cap Reached > Notification Click',
});
},
},
};
</script>
@@ -149,6 +149,7 @@ import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
import ONBOARDING_COMPLETE from './notifications/onboardingComplete';
import GIFT_ONE_GET_ONE from './notifications/g1g1';
import OnboardingGuide from './onboardingGuide';
import DROP_CAP_REACHED from './notifications/dropCapReached';
export default {
components: {
@@ -178,6 +179,7 @@ export default {
OnboardingGuide,
ONBOARDING_COMPLETE,
GIFT_ONE_GET_ONE,
DROP_CAP_REACHED,
},
data () {
return {
@@ -203,7 +205,7 @@ export default {
'GROUP_TASK_CLAIMED', 'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME', 'ONBOARDING_COMPLETE',
'VERIFY_USERNAME', 'ONBOARDING_COMPLETE', 'DROP_CAP_REACHED',
],
};
},
@@ -23,13 +23,6 @@
slot="dropdown-content"
class="user-dropdown"
>
<a
class="topbar-dropdown-item dropdown-item edit-avatar dropdown-separated"
@click="showAvatar('body', 'size')"
>
<h3>{{ user.profile.name }}</h3>
<span class="small-text">{{ $t('editAvatar') }}</span>
</a>
<a
class="topbar-dropdown-item nav-link dropdown-item
dropdown-separated d-flex justify-content-between align-items-center"
@@ -43,20 +36,20 @@
</a>
<a
class="topbar-dropdown-item dropdown-item"
@click="showAvatar('backgrounds', '2020')"
>{{ $t('backgrounds') }}</a>
@click="showAvatar('body', 'size')"
>{{ $t('editAvatar') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
@click="showProfile('profile')"
>{{ $t('profile') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
@click="showProfile('stats')"
>{{ $t('stats') }}</a>
<a
class="topbar-dropdown-item dropdown-item"
class="topbar-dropdown-item dropdown-item dropdown-separated"
@click="showProfile('achievements')"
>{{ $t('achievements') }}</a>
<a
class="topbar-dropdown-item dropdown-item dropdown-separated"
@click="showProfile('profile')"
>{{ $t('profile') }}</a>
<router-link
class="topbar-dropdown-item dropdown-item"
:to="{name: 'site'}"
@@ -75,19 +68,36 @@
>{{ $t('logout') }}</a>
<li
v-if="!user.purchased.plan.customerId"
@click="showBuyGemsModal()"
class="topbar-dropdown-item dropdown-item dropdown-separated
d-flex flex-column justify-content-center align-items-center dropdown-inactive subs-info"
>
<div class="topbar-dropdown-item dropdown-item text-center">
<h3 class="purple">
{{ $t('needMoreGems') }}
</h3>
<span class="small-text">{{ $t('needMoreGemsInfo') }}</span>
</div>
<div class="learn-background py-2 text-center">
<button class="btn btn-primary btn-lg learn-button">
{{ $t('learnMore') }}
</button>
</div>
<span
v-once
class="purple d-block font-weight-bold mb-3"
>
{{ $t('lookingForMoreItems') }}
</span>
<img
class="swords mb-3"
srcset="
~@/assets/images/swords.png,
~@/assets/images/swords@2x.png 2x,
~@/assets/images/swords@3x.png 3x"
src="~@/assets/images/swords.png"
>
<p
v-once
class="subs-benefits mb-3"
>
{{ $t('dropCapSubs') }}
</p>
<button
v-once
class="btn btn-primary mb-4"
@click="$router.push({name: 'subscription'})"
>
{{ $t('learnMore') }}
</button>
</li>
</div>
</menu-dropdown>
@@ -96,39 +106,30 @@
<style lang='scss' scoped>
@import '~@/assets/scss/colors.scss';
.edit-avatar {
h3 {
color: $gray-10;
margin-bottom: 0px;
}
padding-top: 16px;
padding-bottom: 16px;
}
.user-dropdown {
width: 14.75em;
}
.learn-background {
background: url('~@/assets/images/gem-rain.png') bottom left no-repeat,
url('~@/assets/images/gold-rain.png') bottom right no-repeat;
}
.learn-button {
margin: 0.75em 0.75em 0.75em 1em;
}
.purple {
color: $purple-200;
color: $purple-300;
}
.small-text {
color: $gray-200;
.subs-info {
padding-top: 1.438rem;
padding-bottom: 0;
}
.subs-benefits {
font-size: 0.75rem;
line-height: 1.33;
font-style: normal;
display: block;
white-space: normal;
font-weight: bold;
text-align: center;
}
.swords {
width: 7rem;
height: 3rem;
}
</style>
@@ -2,8 +2,8 @@
<div class="class-badge d-flex justify-content-center">
<div
class="align-self-center svg-icon"
v-html="icons[memberClass]"
:aria-label="$t(memberClass)"
v-html="icons[memberClass]"
></div>
</div>
</template>
@@ -35,6 +35,7 @@
<mind-over-matter />
<onboarding-complete />
<first-drops />
<drop-cap-reached-modal />
</div>
</template>
@@ -145,6 +146,7 @@ import loginIncentives from './achievements/login-incentives';
import onboardingComplete from './achievements/onboardingComplete';
import verifyUsername from './settings/verifyUsername';
import firstDrops from './achievements/firstDrops';
import DropCapReachedModal from '@/components/achievements/dropCapReached';
const NOTIFICATIONS = {
CHALLENGE_JOINED_ACHIEVEMENT: {
@@ -384,6 +386,7 @@ export default {
justAddWater,
onboardingComplete,
firstDrops,
DropCapReachedModal,
},
mixins: [notifications, guide],
data () {
@@ -7,9 +7,9 @@
<span
v-if="withPin"
class="badge-dialog"
tabindex="0"
@click.prevent.stop="togglePinned()"
@keypress.enter.prevent.stop="togglePinned()"
tabindex="0"
>
<pin-badge
:pinned="isPinned"
@@ -19,9 +19,9 @@
<span
class="svg-icon icon-12 close-icon"
aria-hidden="true"
tabindex="0"
@click="hideDialog()"
@keypress.enter="hideDialog()"
tabindex="0"
v-html="icons.close"
></span>
</div>
@@ -154,8 +154,8 @@
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)"
:class="{'notEnough': !preventHealthPotion ||
!enoughCurrency(getPriceClass(), item.value * selectedAmountToBuy)}"
@click="buyItem()"
tabindex="0"
@click="buyItem()"
>
{{ $t('buyNow') }}
</button>
@@ -3,9 +3,9 @@
<div
:id="itemId"
class="item-wrapper"
tabindex="0"
@click="click()"
@keypress.enter="click()"
tabindex="0"
>
<div
class="item"
@@ -30,9 +30,9 @@
:key="filter"
class="filter small-text"
:class="{active: activeFilter.label === filter}"
tabindex="0"
@click="activateFilter(type, filter)"
@keypress.enter="activateFilter(type, filter)"
tabindex="0"
>
{{ $t(filter) }}
</div>
+8 -8
View File
@@ -36,11 +36,11 @@
'task-not-scoreable': isUser !== true
|| (task.group.approval.requested && !task.group.approval.approved),
}, controlClass.up.inner]"
tabindex="0"
@click="(isUser && task.up && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('up') : null"
@keypress.enter="(isUser && task.up && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('up') : null"
tabindex="0"
>
<div
v-if="!isUser"
@@ -66,11 +66,11 @@
<div
class="task-control daily-todo-control"
:class="controlClass.inner"
tabindex="0"
@click="isUser && !task.group.approval.requested
? score(task.completed ? 'down' : 'up' ) : null"
@keypress.enter="isUser && !task.group.approval.requested
? score(task.completed ? 'down' : 'up' ) : null"
tabindex="0"
>
<div
v-if="!isUser"
@@ -97,9 +97,9 @@
<div
class="task-clickable-area"
:class="{'task-clickable-area-user': isUser}"
tabindex="0"
@click="edit($event, task)"
@keypress.enter="edit($event, task)"
tabindex="0"
>
<div class="d-flex justify-content-between">
<h3
@@ -140,8 +140,8 @@
<div
v-if="isUser"
class="dropdown-item"
@click="moveToTop"
tabindex="0"
@click="moveToTop"
@keypress.enter="moveToTop"
>
<span class="dropdown-icon-item">
@@ -155,8 +155,8 @@
<div
v-if="isUser"
class="dropdown-item"
@click="moveToBottom"
tabindex="0"
@click="moveToBottom"
@keypress.enter="moveToBottom"
>
<span class="dropdown-icon-item">
@@ -170,8 +170,8 @@
<div
v-if="showDelete"
class="dropdown-item"
@click="destroy"
tabindex="0"
@click="destroy"
@keypress.enter="destroy"
>
<span class="dropdown-icon-item delete-task-item">
@@ -349,11 +349,11 @@
'task-not-scoreable': isUser !== true
|| (task.group.approval.requested && !task.group.approval.approved),
}, controlClass.down.inner]"
tabindex="0"
@click="(isUser && task.down && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('down') : null"
@keypress.enter="(isUser && task.down && (!task.group.approval.requested
|| task.group.approval.approved)) ? score('down') : null"
tabindex="0"
>
<div
v-if="!isUser"
@@ -373,9 +373,9 @@
v-if="task.type === 'reward'"
class="right-control d-flex align-items-center justify-content-center reward-control"
:class="controlClass.bg"
tabindex="0"
@click="isUser ? score('down') : null"
@keypress.enter="isUser ? score('down') : null"
tabindex="0"
>
<div
class="svg-icon"