Tasks v2 Part 2 (#9236)

* start updating colors for tasks controls

* finish updating controls colors

* change hoevr behavior

* change transition duration

* update color with transition

* refactor menu wip

* wip

* upgrade vue deps

* fix warnings

* fix menu

* misc fixes

* more fixes

* fix badge

* fix margins in menu

* wip tooltips

* tooltips

* fix checklist colors

* add badges

* fix quick add input

* add transition to task controls too

* add batch add

* fix task filtering

* finish tasks badges

* fix menu

* upgrade deps

* fix shop items using all the same image

* fix animation

* disable client tests until we remove phantomjs

* revert changes to tasks colors

* fix opacity in task modal inputs

* remove client unit tests from travis

* wip task dropdown

* fix z-index for task footer/header

* fixes and add files

* fixes

* bigger clickable area

* more space to open task dropdown

* droddown position

* fix menu position

* make sure other dropdowns get closed correctly

* fixes

* start to fix z-index

* draggable = false for task dropdown

* fix for dropdown position

* implement move to top / bottom

* fix push to bottom

* typo

* fix drag and drop

* use standard code

* wider click area for dropdown

* unify badge look

* fix padding

* misc fixes

* more fixes

* make dropdown scrollable

* misc fixes

* fix padding for notes

* use existing code instead of new props
This commit is contained in:
Matteo Pagliazzi
2017-11-02 21:07:38 +01:00
committed by GitHub
parent 0e958fd306
commit a208ba4aba
38 changed files with 2186 additions and 924 deletions
+177
View File
@@ -0,0 +1,177 @@
<template lang="pug">
div
create-party-modal
#app-header.row(:class="{'hide-header': $route.name === 'groupPlan'}")
members-modal(:hide-badge="true")
member-details(
:member="user",
:class-badge-position="'next-to-name'",
:is-header="true",
)
.view-party.d-flex.align-items-center(
v-if="user.party && user.party._id && partyMembers && partyMembers.length > 1",
)
button.btn.btn-primary(@click='openPartyModal()') {{ $t('viewParty') }}
.party-members.d-flex(
v-if="partyMembers && partyMembers.length > 1",
v-resize="1500",
@resized="setPartyMembersWidth($event)"
)
member-details(
v-for="(member, $index) in partyMembers",
:key="member._id",
v-if="member._id !== user._id && $index < membersToShow",
:member="member",
condensed=true,
@onHover="expandMember(member._id)",
:expanded="member._id === expandedMember",
:is-header="true",
:class-badge-position="'hidden'",
)
.no-party.d-flex.justify-content-center.text-center(v-else)
.align-self-center(v-once)
h3 {{ $t('battleWithFriends') }}
span.small-text(v-html="$t('inviteFriendsParty')")
br
button.btn.btn-primary(@click='openPartyModal()') {{ partyMembers && partyMembers.length > 1 ? $t('startAParty') : $t('inviteFriends') }}
a.useMobileApp(v-if="isAndroidMobile()", v-once, href="https://play.google.com/store/apps/details?id=com.habitrpg.android.habitica") {{ $t('useMobileApps') }}
a.useMobileApp(v-if="isIOSMobile()", v-once, href="https://itunes.apple.com/us/app/habitica-gamified-task-manager/id994882113?mt=8") {{ $t('useMobileApps') }}
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.useMobileApp {
background: red;
color: white;
z-index: 10;
width: 100%;
margin: 10px 5px 0 0;
height: 64px;
text-align: center;
display: flex;
align-items: center;
}
#app-header {
margin-top: 56px;
padding-left: 24px;
padding-top: 9px;
padding-bottom: 8px;
background: $purple-50;
color: $header-color;
flex-wrap: nowrap;
position: relative;
}
.hide-header {
display: none;
}
.no-party, .party-members {
flex-grow: 1;
}
.view-party {
position: absolute;
z-index: 10;
right: 0;
padding-right: 40px;
padding-left: 10px;
height: calc(100% - 9px);
background-image: linear-gradient(to right, rgba($purple-50, 0), $purple-50);
}
.no-party {
.small-text {
color: $header-color;
flex-wrap: nowrap;
}
h3 {
color: $white;
margin-bottom: 4px;
}
.btn {
margin-top: 16px;
}
}
</style>
<script>
import { mapGetters, mapActions } from 'client/libs/store';
import MemberDetails from '../memberDetails';
import createPartyModal from '../groups/createPartyModal';
import membersModal from '../groups/membersModal';
import ResizeDirective from 'client/directives/resize.directive';
export default {
directives: {
resize: ResizeDirective,
},
components: {
MemberDetails,
createPartyModal,
membersModal,
},
data () {
return {
expandedMember: null,
currentWidth: 0,
};
},
computed: {
...mapGetters({
user: 'user:data',
partyMembers: 'party:members',
}),
showHeader () {
if (this.$store.state.hideHeader) return false;
return true;
},
membersToShow () {
return Math.floor(this.currentWidth / 140) + 1;
},
},
methods: {
...mapActions({
getPartyMembers: 'party:getMembers',
}),
isAndroidMobile () {
return navigator.userAgent.match(/Android/i);
},
isIOSMobile () {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
expandMember (memberId) {
if (this.expandedMember === memberId) {
this.expandedMember = null;
} else {
this.expandedMember = memberId;
}
},
openPartyModal () {
if (this.user.party._id) {
this.$store.state.memberModalOptions.groupId = this.user.party._id;
// @TODO: do we need to fetch party?
// this.$store.state.memberModalOptions.group = this.$store.state.party;
this.$root.$emit('show::modal', 'members-modal');
return;
}
this.$root.$emit('show::modal', 'create-party-modal');
},
setPartyMembersWidth ($event) {
if (this.currentWidth !== $event.width) {
this.currentWidth = $event.width;
}
},
},
created () {
if (this.user.party && this.user.party._id) this.getPartyMembers();
},
};
</script>
+311
View File
@@ -0,0 +1,311 @@
<template lang="pug">
div
inbox-modal
creator-intro
profile
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-md
.navbar-header
.logo.svg-icon.hidden-lg-down(v-html="icons.logo")
.svg-icon.gryphon.hidden-xl-up
b-collapse#nav_collapse.collapse.navbar-collapse(is-nav)
ul.navbar-nav.mr-auto
router-link.nav-item(tag="li", :to="{name: 'tasks'}", exact)
a.nav-link(v-once) {{ $t('tasks') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'items'}", :class="{'active': $route.path.startsWith('/inventory')}")
a.nav-link(v-once) {{ $t('inventory') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'items'}", exact) {{ $t('items') }}
router-link.dropdown-item(:to="{name: 'equipment'}") {{ $t('equipment') }}
router-link.dropdown-item(:to="{name: 'stable'}") {{ $t('stable') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'market'}", :class="{'active': $route.path.startsWith('/shop')}")
a.nav-link(v-once) {{ $t('shops') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'market'}", exact) {{ $t('market') }}
router-link.dropdown-item(:to="{name: 'quests'}") {{ $t('quests') }}
router-link.dropdown-item(:to="{name: 'seasonal'}") {{ $t('titleSeasonalShop') }}
router-link.dropdown-item(:to="{name: 'time'}") {{ $t('titleTimeTravelers') }}
router-link.nav-item(tag="li", :to="{name: 'party'}", v-if='this.user.party._id')
a.nav-link(v-once) {{ $t('party') }}
.nav-item(@click='openPartyModal()', v-if='!this.user.party._id')
a.nav-link(v-once) {{ $t('party') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
a.nav-link(v-once) {{ $t('guilds') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'groupPlan'}", :class="{'active': $route.path.startsWith('/group-plans')}")
a.nav-link(v-once) {{ $t('group') }}
.dropdown-menu
router-link.dropdown-item(v-for='group in groupPlans', :key='group._id', :to="{name: 'groupPlanDetailTaskInformation', params: {groupId: group._id}}") {{ group.name }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'myChallenges'}", :class="{'active': $route.path.startsWith('/challenges')}")
a.nav-link(v-once) {{ $t('challenges') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'myChallenges'}") {{ $t('myChallenges') }}
router-link.dropdown-item(:to="{name: 'findChallenges'}") {{ $t('findChallenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
a.nav-link(v-once) {{ $t('help') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'faq'}") {{ $t('faq') }}
router-link.dropdown-item(:to="{name: 'overview'}") {{ $t('overview') }}
router-link.dropdown-item(to="/groups/guild/a29da26b-37de-4a71-b0c6-48e72a900dac") {{ $t('reportBug') }}
router-link.dropdown-item(to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a") {{ $t('askAQuestion') }}
a.dropdown-item(href="https://trello.com/c/odmhIqyW/440-read-first-table-of-contents", target='_blank') {{ $t('requestAF') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Contributing_to_Habitica", target='_blank') {{ $t('contributing') }}
a.dropdown-item(href="http://habitica.wikia.com/wiki/Habitica_Wiki", target='_blank') {{ $t('wiki') }}
.d-flex.align-items-center
.item-with-icon(v-if="userHourglasses > 0")
.svg-icon(v-html="icons.hourglasses", v-b-tooltip.hover.bottom="$t('mysticHourglassesTooltip')")
span {{ userHourglasses }}
.item-with-icon
.svg-icon.gem(v-html="icons.gem", @click='showBuyGemsModal("gems")', v-b-tooltip.hover.bottom="$t('gems')")
span {{userGems | roundBigNumber}}
.item-with-icon.gold
.svg-icon(v-html="icons.gold", v-b-tooltip.hover.bottom="$t('gold')")
span {{Math.floor(user.stats.gp * 100) / 100}}
a.item-with-icon(@click="sync", v-b-tooltip.hover.bottom="$t('sync')")
.svg-icon(v-html="icons.sync")
notification-menu.item-with-icon
user-dropdown.item-with-icon
b-nav-toggle(target='nav_collapse')
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/utils.scss';
@media only screen and (max-width: 1280px) {
.nav-link {
padding: .8em 1em !important;
}
}
@media only screen and (max-width: 1200px) {
.navbar-header {
margin-right: 0px;
}
.gryphon {
background-image: url('~assets/images/melior@3x.png');
width: 30px;
height: 30px;
background-size: cover;
color: $white;
margin: 0 auto;
}
}
@media only screen and (max-width: 990px) {
#nav_collapse {
margin-top: 1.3em;
background-color: $purple-200;
}
}
nav.navbar {
background: $purple-100 url(~assets/svg/for-css/bits.svg) right no-repeat;
padding-left: 25px;
padding-right: 12.5px;
height: 56px;
box-shadow: 0 1px 2px 0 rgba($black, 0.24);
}
.navbar-header {
margin-right: 48px;
.logo {
width: 128px;
height: 28px;
}
}
.nav-item {
.nav-link {
font-size: 16px;
color: $white;
font-weight: bold;
line-height: 1.5;
padding: 16px 24px;
transition: none;
}
&:hover {
.nav-link {
color: $white;
background: $purple-200;
}
}
&.active:not(:hover) {
.nav-link {
box-shadow: 0px -4px 0px $purple-300 inset;
}
}
}
// Make the dropdown menu open on hover
.dropdown:hover .dropdown-menu {
display: block;
margin-top: 0; // remove the gap so it doesn't close
}
.dropdown-menu {
background: $purple-200;
border-radius: 0px;
border: none;
box-shadow: none;
padding: 0px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
.dropdown-item {
font-size: 16px;
box-shadow: none;
color: $white;
border: none;
line-height: 1.5;
&.active {
background: $purple-300;
}
&:hover {
background: $purple-300;
&:last-child {
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
}
}
}
.dropdown + .dropdown {
margin-left: 0px;
}
.item-with-icon {
color: $white;
font-size: 16px;
white-space: nowrap;
span {
font-weight: bold;
}
&.gold {
margin-right: 24px;
}
&:hover /deep/ .svg-icon {
color: $white;
}
& /deep/ .svg-icon {
color: $header-color;
vertical-align: bottom;
display: inline-block;
width: 24px;
height: 24px;
margin-right: 12px;
margin-left: 12px;
}
}
.menu-icon {
margin-left: 24px;
}
.gem:hover {
cursor: pointer;
}
</style>
<script>
import bNavToggle from 'bootstrap-vue/lib/components/nav-toggle';
import bCollapse from 'bootstrap-vue/lib/components/collapse';
import { mapState, mapGetters } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import gemIcon from 'assets/svg/gem.svg';
import goldIcon from 'assets/svg/gold.svg';
import syncIcon from 'assets/svg/sync.svg';
import svgHourglasses from 'assets/svg/hourglass.svg';
import logo from 'assets/svg/logo.svg';
import InboxModal from '../userMenu/inbox.vue';
import notificationMenu from './notificationsDropdown';
import creatorIntro from '../creatorIntro';
import profile from '../userMenu/profile';
import userDropdown from './userDropdown';
import bTooltip from 'bootstrap-vue/lib/directives/tooltip';
export default {
components: {
userDropdown,
InboxModal,
notificationMenu,
creatorIntro,
profile,
bNavToggle,
bCollapse,
},
directives: {
bTooltip,
},
data () {
return {
isUserDropdownOpen: false,
icons: Object.freeze({
gem: gemIcon,
gold: goldIcon,
hourglasses: svgHourglasses,
sync: syncIcon,
logo,
}),
};
},
computed: {
...mapGetters({
userGems: 'user:gems',
}),
...mapState({
user: 'user.data',
userHourglasses: 'user.data.purchased.plan.consecutive.trinkets',
groupPlans: 'groupPlans',
}),
},
mounted () {
this.getUserGroupPlans();
},
methods: {
toggleUserDropdown () {
this.isUserDropdownOpen = !this.isUserDropdownOpen;
},
sync () {
return Promise.all([
this.$store.dispatch('user:fetch', {forceLoad: true}),
this.$store.dispatch('tasks:fetchUserTasks', {forceLoad: true}),
]);
},
async getUserGroupPlans () {
this.$store.state.groupPlans = await this.$store.dispatch('guilds:getGroupPlans');
},
openPartyModal () {
this.$root.$emit('show::modal', 'create-party-modal');
},
showBuyGemsModal (startingPage) {
this.$store.state.gemModalOptions.startingPage = startingPage;
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > Toolbar',
});
this.$root.$emit('show::modal', 'buy-gems', {alreadyTracked: true});
},
},
};
</script>
@@ -0,0 +1,27 @@
<template lang="pug" functional>
span.message-count(:class="{'top-count': props.top === true}") {{props.count}}
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';
.message-count {
background-color: $blue-50;
border-radius: 50%;
height: 20px;
width: 20px;
float: right;
color: $white;
text-align: center;
font-weight: bold;
font-size: 12px;
}
.message-count.top-count {
position: absolute;
right: 0.3em;
top: -0.8em;
padding: 0.2em;
background-color: $red-50;
}
</style>
@@ -0,0 +1,285 @@
<template lang="pug">
menu-dropdown.item-notifications(:right="true")
div(slot="dropdown-toggle")
div
message-count(v-if='notificationsCount > 0', :count="notificationsCount", :top="true")
.svg-icon.notifications(v-html="icons.notifications")
div(slot="dropdown-content")
h4.dropdown-item.dropdown-separated(v-if='!hasNoNotifications()') {{ $t('notifications') }}
h4.dropdown-item.toolbar-notifs-no-messages(v-if='hasNoNotifications()') {{ $t('noNotifications') }}
a.dropdown-item(v-if='user.party.quest && user.party.quest.RSVPNeeded')
div {{ $t('invitedTo', {name: quests.quests[user.party.quest.key].text()}) }}
div
button.btn.btn-primary(@click.stop='questAccept(user.party._id)') Accept
button.btn.btn-primary(@click.stop='questReject(user.party._id)') Reject
a.dropdown-item(v-if='user.purchased.plan.mysteryItems.length', @click='go("/inventory/items")')
span.glyphicon.glyphicon-gift
span {{ $t('newSubscriberItem') }}
a.dropdown-item(v-for='(party, index) in user.invitations.parties')
div
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: party.name}) }}
div
button.btn.btn-primary(@click.stop='accept(party, index, "party")') Accept
button.btn.btn-primary(@click.stop='reject(party, index, "party")') Reject
a.dropdown-item(v-if='user.flags.cardReceived', @click='go("/inventory/items")')
span.glyphicon.glyphicon-envelope
span {{ $t('cardReceived') }}
a.dropdown-item(@click.stop='clearCards()')
a.dropdown-item(v-for='(guild, index) in user.invitations.guilds')
div
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: guild.name}) }}
div
button.btn.btn-primary(@click.stop='accept(guild, index, "guild")') Accept
button.btn.btn-primary(@click.stop='reject(guild, index, "guild")') Reject
a.dropdown-item(v-if='user.flags.classSelected && !user.preferences.disableClasses && user.stats.points',
@click='go("/user/profile")')
span.glyphicon.glyphicon-plus-sign
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
a.dropdown-item(v-for='message in userNewMessages')
span(@click='navigateToGroup(message.key)')
span.glyphicon.glyphicon-comment
span {{message.name}}
span.clear-button(@click.stop='clearMessages(message.key)') Clear
a.dropdown-item(v-for='notification in groupNotifications', :key='notification.id')
span(:class="groupApprovalNotificationIcon(notification)")
span {{notification.data.message}}
span.clear-button(@click.stop='viewGroupApprovalNotification(notification)') Clear
</template>
<style lang='scss' scoped>
.clear-button {
margin-left: .5em;
}
</style>
<script>
import axios from 'axios';
import isEmpty from 'lodash/isEmpty';
import map from 'lodash/map';
// import bTooltip from 'bootstrap-vue/lib/directives/tooltip';
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import quests from 'common/script/content/quests';
import notificationsIcon from 'assets/svg/notifications.svg';
import MenuDropdown from '../ui/customMenuDropdown';
import MessageCount from './messageCount';
export default {
components: {
MenuDropdown,
MessageCount,
},
directives: {
// bTooltip,
},
data () {
return {
icons: Object.freeze({
notifications: notificationsIcon,
}),
quests,
};
},
computed: {
...mapState({user: 'user.data'}),
party () {
return {name: ''};
// return this.user.party;
},
userNewMessages () {
// @TODO: For some reason data becomes corrupted. We should fix this on the server
let userNewMessages = [];
for (let key in this.user.newMessages) {
let message = this.user.newMessages[key];
if (message && message.name && message.value) {
message.key = key;
userNewMessages.push(message);
}
}
return userNewMessages;
},
groupNotifications () {
return this.$store.state.groupNotifications;
},
notificationsCount () {
let count = 0;
if (this.user.invitations.parties) {
count += this.user.invitations.parties.length;
}
if (this.user.purchased.plan && this.user.purchased.plan.mysteryItems.length) {
count++;
}
if (this.user.invitations.guilds) {
count += this.user.invitations.guilds.length;
}
if (this.user.flags.classSelected && !this.user.preferences.disableClasses && this.user.stats.points) {
count += this.user.stats.points > 0 ? 1 : 0;
}
if (this.userNewMessages) {
count += Object.keys(this.userNewMessages).length;
}
count += this.groupNotifications.length;
return count;
},
},
methods: {
// @TODO: I hate this function, we can do better with a hashmap
selectNotificationValue (mysteryValue, invitationValue, cardValue,
unallocatedValue, messageValue, noneValue, groupApprovalRequested, groupApproved) {
let user = this.user;
if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) {
return mysteryValue;
} else if (user.invitations.parties && user.invitations.parties.length > 0 || user.invitations.guilds && user.invitations.guilds.length > 0) {
return invitationValue;
} else if (user.flags.cardReceived) {
return cardValue;
} else if (user.flags.classSelected && !(user.preferences && user.preferences.disableClasses) && user.stats.points) {
return unallocatedValue;
} else if (!isEmpty(user.newMessages)) {
return messageValue;
} else if (!isEmpty(this.groupNotifications)) {
let groupNotificationTypes = map(this.groupNotifications, 'type');
if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVAL') !== -1) {
return groupApprovalRequested;
} else if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVED') !== -1) {
return groupApproved;
}
return noneValue;
} else {
return noneValue;
}
},
hasQuestProgress () {
let user = this.user;
if (user.party.quest) {
let userQuest = quests[user.party.quest.key];
if (!userQuest) {
return false;
}
if (userQuest.boss && user.party.quest.progress.up > 0) {
return true;
}
if (userQuest.collect && user.party.quest.progress.collectedItems > 0) {
return true;
}
}
return false;
},
getQuestInfo () {
let user = this.user;
let questInfo = {};
if (user.party.quest) {
let userQuest = quests[user.party.quest.key];
questInfo.title = userQuest.text();
if (userQuest.boss) {
questInfo.body = this.$t('questTaskDamage', { damage: user.party.quest.progress.up.toFixed(1) });
} else if (userQuest.collect) {
questInfo.body = this.$t('questTaskCollection', { items: user.party.quest.progress.collectedItems });
}
}
return questInfo;
},
clearMessages (key) {
this.$store.dispatch('chat:markChatSeen', {groupId: key});
this.$delete(this.user.newMessages, key);
},
clearCards () {
this.$store.dispatch('chat:clearCards');
},
iconClasses () {
return this.selectNotificationValue(
'glyphicon-gift',
'glyphicon-user',
'glyphicon-envelope',
'glyphicon-plus-sign',
'glyphicon-comment',
'glyphicon-comment inactive',
'glyphicon-question-sign',
'glyphicon-ok-sign'
);
},
hasNoNotifications () {
return this.selectNotificationValue(false, false, false, false, false, true, false, false);
},
viewGroupApprovalNotification (notification) {
this.$store.state.groupNotifications = this.groupNotifications.filter(groupNotif => {
return groupNotif.id !== notification.id;
});
axios.post('/api/v3/notifications/read', {
notificationIds: [notification.id],
});
},
groupApprovalNotificationIcon (notification) {
if (notification.type === 'GROUP_TASK_APPROVAL') {
return 'glyphicon glyphicon-question-sign';
} else if (notification.type === 'GROUP_TASK_APPROVED') {
return 'glyphicon glyphicon-ok-sign';
}
},
go (path) {
this.$router.push(path);
},
navigateToGroup (key) {
if (key === this.party._id || key === this.user.party._id) {
this.go('/party');
return;
}
this.$router.push({ name: 'guild', params: { groupId: key }});
},
async reject (group) {
await this.$store.dispatch('guilds:rejectInvite', {groupId: group.id});
// @TODO: User.sync();
},
async accept (group, index, type) {
if (group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
return;
}
if (type === 'party') {
// @TODO: pretty sure mutability is wrong. Need to check React docs
// @TODO mutation to store data should only happen through actions
this.user.invitations.parties.splice(index, 1);
Analytics.updateUser({partyID: group.id});
} else {
this.user.invitations.guilds.splice(index, 1);
}
if (type === 'party') {
this.user.party._id = group.id;
this.$router.push('/party');
} else {
this.user.guilds.push(group.id);
this.$router.push(`/groups/guild/${group.id}`);
}
// @TODO: check for party , type: 'myGuilds'
await this.$store.dispatch('guilds:join', {guildId: group.id});
},
async questAccept (partyId) {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/accept'});
this.user.party.quest = quest;
},
async questReject (partyId) {
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/reject'});
this.user.party.quest = quest;
},
},
};
</script>
@@ -0,0 +1,126 @@
<template lang="pug">
menu-dropdown.item-user(:right="true")
div(slot="dropdown-toggle")
div(v-b-tooltip.hover.bottom="$t('user')")
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages", :top="true")
.svg-icon.user(v-html="icons.user")
.user-dropdown(slot="dropdown-content")
a.dropdown-item.edit-avatar.dropdown-separated(@click='showAvatar()')
h3 {{ user.profile.name }}
span.small-text {{ $t('editAvatar') }}
a.nav-link.dropdown-item.dropdown-separated(@click.prevent='showInbox()')
| {{ $t('messages') }}
message-count(v-if='user.inbox.newMessages > 0', :count="user.inbox.newMessages")
a.dropdown-item(@click='showAvatar("backgrounds", "2017")') {{ $t('backgrounds') }}
a.dropdown-item(@click='showProfile("stats")') {{ $t('stats') }}
a.dropdown-item(@click='showProfile("achievements")') {{ $t('achievements') }}
a.dropdown-item.dropdown-separated(@click='showProfile("profile")') {{ $t('profile') }}
router-link.dropdown-item(:to="{name: 'site'}") {{ $t('settings') }}
router-link.dropdown-item.dropdown-separated(:to="{name: 'subscription'}") {{ $t('subscription') }}
a.nav-link.dropdown-item.dropdown-separated(@click.prevent='logout()') {{ $t('logout') }}
li(v-if='!this.user.purchased.plan.customerId', @click='showBuyGemsModal("subscribe")')
.dropdown-item.text-center
h3.purple {{ $t('needMoreGems') }}
span.small-text {{ $t('needMoreGemsInfo') }}
img.float-left.align-self-end(src='~assets/images/gem-rain.png')
button.btn.btn-primary.btn-lg.learn-button Learn More
img.float-right.align-self-end(src='~assets/images/gold-rain.png')
</template>
<style lang='scss' scoped>
@import '~client/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-button {
margin: 0.75em 0.75em 0.75em 1em;
}
.purple {
color: $purple-200;
}
.small-text {
color: $gray-200;
font-style: normal;
display: block;
white-space: normal;
font-weight: bold;
}
</style>
<script>
import { mapState } from 'client/libs/store';
import * as Analytics from 'client/libs/analytics';
import userIcon from 'assets/svg/user.svg';
import MenuDropdown from '../ui/customMenuDropdown';
import axios from 'axios';
import markPMSRead from 'common/script/ops/markPMSRead';
import MessageCount from './messageCount';
import bTooltip from 'bootstrap-vue/lib/directives/tooltip';
export default {
components: {
MenuDropdown,
MessageCount,
},
directives: {
bTooltip,
},
data () {
return {
icons: Object.freeze({
user: userIcon,
}),
};
},
computed: {
...mapState({user: 'user.data'}),
},
methods: {
showAvatar (startingPage, subpage) {
this.$store.state.avatarEditorOptions.editingUser = true;
this.$store.state.avatarEditorOptions.startingPage = startingPage;
this.$store.state.avatarEditorOptions.subpage = subpage;
this.$root.$emit('show::modal', 'avatar-modal');
},
showInbox () {
markPMSRead(this.user);
axios.post('/api/v3/user/mark-pms-read');
this.$root.$emit('show::modal', 'inbox-modal');
},
showProfile (startingPage) {
this.$store.state.profileUser = this.user;
this.$store.state.profileOptions.startingPage = startingPage;
this.$root.$emit('show::modal', 'profile');
},
showBuyGemsModal (startingPage) {
this.$store.state.gemModalOptions.startingPage = startingPage;
Analytics.track({
hitType: 'event',
eventCategory: 'button',
eventAction: 'click',
eventLabel: 'Gems > User Dropdown',
});
this.$root.$emit('show::modal', 'buy-gems', {alreadyTracked: true});
},
logout () {
this.$store.dispatch('auth:logout');
},
},
};
</script>