Client fixed sept 4 (#9019)

* Fixed background purchasing

* Added challenge export

* Fixed is leader filter

* Fixed staff icon

* Add block to profile modal

* Added initial send gems modal

* Added modal stack

* Fixed lint issues

* Updated notification styles

* Updated level up styles

* Fixed many achievement styles

* Fixed notification navigate to same route with different param

* Added mark chat seen and remove new messages

* Added scroll to notifications

* Updated hall loading
This commit is contained in:
Keith Holliday 2017-09-05 12:34:00 -06:00 committed by GitHub
parent d051bdf2c9
commit 36a933d0c4
33 changed files with 606 additions and 238 deletions

View file

@ -162,6 +162,42 @@ export default {
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
});
}
// Manage modals
this.$root.$on('show::modal', (modalId, data) => {
if (data && data.fromRoot) return;
// Get last modal on stack and hide
let modalStackLength = this.$store.state.modalStack.length;
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
// Add new modal to the stack
this.$store.state.modalStack.push(modalId);
// Hide the previous top modal
if (modalOnTop) this.$root.$emit('hide::modal', modalOnTop, {fromRoot: true});
});
// @TODO: This part is hacky and could be solved with two options:
// 1 - Find a way to pass fromRoot to hidden
// 2 - Enforce that all modals use the hide::modal event
this.$root.$on('hidden::modal', (modalId) => {
let modalStackLength = this.$store.state.modalStack.length;
let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
// Don't remove modal if hid was called from main app
// @TODO: I'd reather use this, but I don't know how to pass data to hidden event
// if (data && data.fromRoot) return;
if (modalId === modalSecondToTop) return;
// Remove modal from stack
this.$store.state.modalStack.pop();
// Recalculate and show the last modal if there is one
modalStackLength = this.$store.state.modalStack.length;
modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
if (modalOnTop) this.$root.$emit('show::modal', modalOnTop, {fromRoot: true});
});
},
methods: {
resetItemToBuy ($event) {

View file

@ -1,7 +1,7 @@
<template lang="pug">
div(:class='achievementClass')
//- +generatedAvatar({sleep: false})
avatar(:member='user')
avatar(:member='user', :avatarOnly='true', :withBackground='true')
</template>
<script>

View file

@ -1,27 +1,78 @@
<template lang="pug">
.modal-footer(style='margin-top:0', ng-init='loadWidgets()')
.container-fluid
.row.text-center
.col-4
a.twitter-share-button(:href='twitterLink') {{ $t('tweet') }}
.col-4
.fb-share-button(:data-href='achievementLink', data-layout='button')
.col-4
a.tumblr-share-button(:data-href='achievementLink', data-notes='none')
.container-fluid.share-buttons
.row
.col-12.text-center
a.twitter-share-button.share-button(:href='twitterLink', target='_blank')
.social-icon.twitter.svg-icon(v-html='icons.twitter')
| {{ $t('tweet') }}
a.fb-share-button.share-button(:href='facebookLink', target='_blank')
.social-icon.facebook.svg-icon(v-html='icons.facebook')
| {{ $t('share') }}
// @TODO: Still want this? .col-4
a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')
</template>
<style scoped>
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
</style>
<script>
// @TODO:
let BASE_URL = 'https://habitica.com';
import twitter from 'assets/svg/twitter.svg';
import facebook from 'assets/svg/facebook.svg';
export default {
data () {
let tweet = this.$t('achievementShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
tweet,
achievementLink: `${BASE_URL}/social/achievement`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/achievement&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/achievement`,
};
},
};

View file

@ -1,8 +1,9 @@
<template lang="pug">
b-modal#contributor(:title="$t('modalContribAchievement')", size='lg', :hide-footer="true")
b-modal#contributor(:title="$t('modalContribAchievement')", size='md', :hide-footer="true")
.modal-body
.col-6.offset-3.text-center
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
| {{ $t('contribModal', {name: user.profile.name, level: user.contributor.level}) }}
a(:href="$t('conRewardsURL')", target='_blank') {{ $t('contribLink') }}
br
@ -13,7 +14,8 @@
<style scoped>
.avatar {
margin-left: 4em;
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}

View file

@ -1,21 +1,20 @@
<template lang="pug">
b-modal#death(:title="$t('lostAllHealth')", size='lg', :hide-footer="true")
.info
.row
.col-6
.hero-stats
.meter-label(:tooltip="$t('health')")
span.glyphicon.glyphicon-heart
.meter.health(:tooltip='Math.round(user.stats.hp * 100) / 100')
.bar(:style='barStyle')
// span.meter-text.value
| {{Math.ceil(user.stats.hp)}} / {{maxHealth}}
avatar(:member='user', :sleep='true')
// @TOOD: Sleep +generatedAvatar({sleep:true})
span(class='knockout')
.col-6
h4.dont-despair {{ $t('dontDespair') }}
p.death-penalty {{ $t('deathPenaltyDetails') }}
b-modal#death(:title="$t('lostAllHealth')", size='md', :hide-footer="true")
.row
.col-12
.hero-stats
.meter-label(:tooltip="$t('health')")
span.glyphicon.glyphicon-heart
.meter.health(:tooltip='Math.round(user.stats.hp * 100) / 100')
.bar(:style='barStyle')
// span.meter-text.value
| {{Math.ceil(user.stats.hp)}} / {{maxHealth}}
avatar(:member='user', :sleep='true', :avatarOnly='true', :withBackground='true')
// @TOOD: Sleep +generatedAvatar({sleep:true})
span(class='knockout')
.col-6.offset-3
h4.dont-despair {{ $t('dontDespair') }}
p.death-penalty {{ $t('deathPenaltyDetails') }}
.modal-footer
.col-12.text-center
button.btn.btn-danger(@click='revive()') {{ $t('refillHealthTryAgain') }}
@ -23,8 +22,11 @@
</template>
<style scoped>
.info {
height: 220px;
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>

View file

@ -1,17 +1,20 @@
<template lang="pug">
b-modal#invited-friend(:title="$t('modalAchievement')", size='lg', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('friends',0)
achievement-avatar.avatar
p {{ $t('invitedFriendText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
b-modal#invited-friend(:title="$t('modalAchievement')", size='md', :hide-footer="true")
.modal-body
.col-12
// @TODO: +achievementAvatar('friends',0)
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('invitedFriendText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
margin-left: 10.2em;
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}

View file

@ -1,17 +1,20 @@
<template lang="pug">
b-modal#joined-challenge(:title="$t('modalAchievement')", size='lg', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('challenge',0)
achievement-avatar.avatar
p {{ $t('joinedChallengeText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
b-modal#joined-challenge(:title="$t('modalAchievement')", size='md', :hide-footer="true")
.modal-body
.col-12
// @TODO: +achievementAvatar('challenge',0)
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('joinedChallengeText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
margin-left: 10.2em;
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}

View file

@ -1,17 +1,20 @@
<template lang="pug">
b-modal#joined-guild(:title="$t('modalAchievement')", size='lg', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('guild',0)
achievement-avatar.avatar
p {{ $t('joinedGuildText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
b-modal#joined-guild(:title="$t('modalAchievement')", size='md', :hide-footer="true")
.modal-body
.col-12
// @TODO: +achievementAvatar('guild',0)
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('joinedGuildText') }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
margin-left: 10.2em;
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}

View file

@ -1,47 +1,43 @@
<template lang="pug">
b-modal#level-up(:title="$t('levelUpShare')", size='sm', :hide-footer="true")
b-modal#level-up(:title="$t('levelUpShare')", size='sm', :hide-footer="true", :hide-header="true")
.modal-body.text-center
h3 {{ $t('gainedLevel') }}
h2 {{ $t('reachedLevel', {level: user.stats.lvl}) }}
avatar.avatar(:member='user')
h4.avatar-level(:class='userLevelStyle(user)') {{ $t('level') + ' ' + user.stats.lvl }}
h4(v-html="$t('leveledUp', {level: user.stats.lvl})")
p {{ $t('fullyHealed') }}
p.text {{ $t('levelup') }}
button.btn.btn-primary(@click='close()') {{ $t('onwards') }}
br
div(v-if='showAllocation')
a.btn.btn-default(@click='statsAllocationBoxIsOpen = !statsAllocationBoxIsOpen')
| {{statsAllocationBoxIsOpen ? $t('hideQuickAllocation') : $t('showQuickAllocation')}}
p &nbsp;
#stat-allocation(v-if='statsAllocationBoxIsOpen')
p(v-if='user.stats.lvl >= 100') {{ $t('noMoreAllocate') }}
p(v-if='user.stats.points || user.stats.lvl < 100')
strong.inline
| {{user.stats.points}}&nbsp;
strong.hint(popover-trigger='mouseenter',
popover-placement='right', :popover="$t('quickAllocationLevelPopover')") {{ $t('unallocated') }}
// @TODO: +statAllocation
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
.checkbox
// @TODO: Keep this? .checkbox
input(type='checkbox', v-model='user.preferences.suppressModals.levelUp', @change='changeLevelupSuppress()')
label(style='display:inline-block') {{ $t('dontShowAgain') }}
.modal-footer
.container-fluid
.row
.col-4
a.twitter-share-button(:href='twitterLink') {{ $t('tweet') }}
.col-4
.fb-share-button(:data-href='socialLevelLink', data-layout='button')
.col-4
a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')
.container-fluid.share-buttons
.row
.col-12.text-center
a.twitter-share-button.share-button(:href='twitterLink', target='_blank')
.social-icon.twitter.svg-icon(v-html='icons.twitter')
| {{ $t('tweet') }}
a.fb-share-button.share-button(:href='facebookLink', target='_blank')
.social-icon.facebook.svg-icon(v-html='icons.facebook')
| {{ $t('share') }}
// @TODO: Still want this? .col-4
a.tumblr-share-button(:data-href='socialLevelLink', data-notes='none')
</template>
<style lang="scss">
#level-up {
h2 {
color: #4f2a93;
}
.modal-content {
min-width: 28em;
}
.modal-body {
padding-top: 1em;
padding-bottom: 0;
}
@ -61,6 +57,53 @@
margin: 0;
width: 0;
}
.text {
font-size: 14px;
text-align: center;
color: #686274;
margin-top: 1em;
min-height: 0px;
}
.share-buttons {
margin-top: 1em;
margin-bottom: 1em;
}
.share-button {
display: inline-block;
width: 77px;
padding: .5em;
border-radius: 2px;
text-align: center;
color: #fff;
}
.fb-share-button {
background-color: #2995cd;
}
.twitter-share-button {
margin-right: .5em;
background-color: #3bcad7;
}
.social-icon {
width: 16px;
display: inline-block;
vertical-align: bottom;
margin-right: .5em;
}
.social-icon.facebook svg {
width: 7.5px;
margin-bottom: .2em;
}
.social-icon.twitter {
margin-bottom: .2em;
}
}
</style>
@ -77,8 +120,10 @@ import Avatar from '../avatar';
import { mapState } from 'client/libs/store';
import {maxHealth} from '../../../common/script/index';
import styleHelper from 'client/mixins/styleHelper';
import twitter from 'assets/svg/twitter.svg';
import facebook from 'assets/svg/facebook.svg';
let BASE_URL = '@TODO';
let BASE_URL = 'https://habitica.com';
export default {
mixins: [styleHelper],
@ -89,11 +134,16 @@ export default {
data () {
let tweet = this.$t('levelUpShare');
return {
icons: Object.freeze({
twitter,
facebook,
}),
statsAllocationBoxIsOpen: true,
maxHealth,
tweet,
socialLevelLink: `${BASE_URL}/social/level-up`,
twitterLink: `https://twitter.com/intent/tweet?text=${tweet}&via=habitica&url=${BASE_URL}/social/level-up&count=none`,
facebookLink: `https://www.facebook.com/sharer/sharer.php?text=${tweet}&u=${BASE_URL}/social/level-up`,
};
},
mounted () {

View file

@ -9,7 +9,7 @@
span.meter-text.value
| {{healthLeft}}
.col-12
avatar(:member='user')
avatar(:member='user', :avatarOnly='true', :withBackground='true')
.col-12
p {{ $t('losingHealthWarning2') }}
h4 {{ $t('toRegainHealth') }}
@ -46,6 +46,13 @@
.modal-footer {
margin-top: 0em;
}
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>

View file

@ -13,7 +13,6 @@
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
import Avatar from '../avatar';
import { mapState } from 'client/libs/store';
import percent from '../../../common/script/libs/percent';
import { maxHealth } from '../../../common/script/index';
@ -21,7 +20,6 @@ import { maxHealth } from '../../../common/script/index';
export default {
components: {
bModal,
Avatar,
},
data () {
return {

View file

@ -48,7 +48,6 @@
import bModal from 'bootstrap-vue/lib/components/modal';
import quests from 'common/script/content/quests';
import Avatar from '../avatar';
import { mapState } from 'client/libs/store';
import revive from '../../../common/script/ops/revive';
import percent from '../../../common/script/libs/percent';
@ -57,7 +56,6 @@ import {maxHealth} from '../../../common/script/index';
export default {
components: {
bModal,
Avatar,
},
data () {
return {

View file

@ -1,17 +1,28 @@
<template lang="pug">
b-modal#rebirth(:title="$t('modalAchievement')", size='md', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('sun',0)
achievement-avatar
div(v-if='user.achievements.rebirthLevel < 100')
| {{ $t('rebirthAchievement', {number: user.achievements.rebirths, level: user.achievements.rebirthLevel}) }}
div(v-if='user.achievements.rebirthLevel >= 100')
| {{ $t('rebirthAchievement100', {number: user.achievements.rebirths}) }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
.modal-body
.col-12
// @TODO: +achievementAvatar('sun',0)
achievement-avatar.avatar
.col-6.offset-3.text-center
div(v-if='user.achievements.rebirthLevel < 100')
| {{ $t('rebirthAchievement', {number: user.achievements.rebirths, level: user.achievements.rebirthLevel}) }}
div(v-if='user.achievements.rebirthLevel >= 100')
| {{ $t('rebirthAchievement100', {number: user.achievements.rebirths}) }}
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import achievementFooter from './achievementFooter';

View file

@ -1,20 +1,31 @@
<template lang="pug">
b-modal#streak(:title="$t('streakAchievement')", size='md', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('thermometer',2.5)
achievement-avatar
h3(v-if='user.achievements.streak === 1') {{ $t('firstStreakAchievement') }}
h3(v-if='user.achievements.streak > 1') {{ $t('streakAchievementCount', {streaks: user.achievements.streak}) }}
p {{ $t('twentyOneDays') }}
p {{ $t('dontBreakStreak') }}
br
button.btn.btn-primary(@click='close()') {{ $t('dontStop') }}
.checkbox
input(type='checkbox', v-model='user.preferences.suppressModals.streak', @change='suppressModals')
label {{ $t('dontShowAgain') }}
.modal-body
.col-12
// @TODO: +achievementAvatar('thermometer',2.5)
achievement-avatar.avatar
.col-6.offset-3.text-center
h3(v-if='user.achievements.streak === 1') {{ $t('firstStreakAchievement') }}
h3(v-if='user.achievements.streak > 1') {{ $t('streakAchievementCount', {streaks: user.achievements.streak}) }}
p {{ $t('twentyOneDays') }}
p {{ $t('dontBreakStreak') }}
br
button.btn.btn-primary(@click='close()') {{ $t('dontStop') }}
.checkbox
input(type='checkbox', v-model='user.preferences.suppressModals.streak', @change='suppressModals')
label {{ $t('dontShowAgain') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import achievementFooter from './achievementFooter';

View file

@ -1,32 +1,34 @@
<template lang="pug">
b-modal#ultimate-gear(:title="$t('modalAchievement')", size='md', :hide-footer="true")
.modal-body.text-center
// @TODO: +achievementAvatar('armor',2.5)
achievement-avatar
p {{ $t('gearAchievement') }}
br
table.multi-achievement
tr
td(v-if='user.achievements.ultimateGearSets.healer').multi-achievement
.achievement-ultimate-healer2x.multi-achievement
| {{ $t('healer') }}
td(v-if='user.achievements.ultimateGearSets.wizard').multi-achievement
.achievement-ultimate-mage2x.multi-achievement
| {{ $t('mage') }}
td(v-if='user.achievements.ultimateGearSets.rogue').multi-achievement
.achievement-ultimate-rogue2x.multi-achievement
| {{ $t('rogue') }}
td(v-if='user.achievements.ultimateGearSets.warrior').multi-achievement
.achievement-ultimate-warrior2x.multi-achievement
| {{ $t('warrior') }}
br
div(v-if='!(user.achievements.ultimateGearSets.healer && user.achievements.ultimateGearSets.wizard && user.achievements.ultimateGearSets.rogue && user.achievements.ultimateGearSets.warrior)')
p(v-html="$t('moreGearAchievements')")
.modal-body
.col-12
// @TODO: +achievementAvatar('armor',2.5)
achievement-avatar.avatar
.col-12.text-center
p {{ $t('gearAchievement') }}
br
.shop_armoire
p(v-html='$t("armoireUnlocked")')
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
table.multi-achievement
tr
td(v-if='user.achievements.ultimateGearSets.healer').multi-achievement
.achievement-ultimate-healer2x.multi-achievement
| {{ $t('healer') }}
td(v-if='user.achievements.ultimateGearSets.wizard').multi-achievement
.achievement-ultimate-mage2x.multi-achievement
| {{ $t('mage') }}
td(v-if='user.achievements.ultimateGearSets.rogue').multi-achievement
.achievement-ultimate-rogue2x.multi-achievement
| {{ $t('rogue') }}
td(v-if='user.achievements.ultimateGearSets.warrior').multi-achievement
.achievement-ultimate-warrior2x.multi-achievement
| {{ $t('warrior') }}
br
div(v-if='!(user.achievements.ultimateGearSets.healer && user.achievements.ultimateGearSets.wizard && user.achievements.ultimateGearSets.rogue && user.achievements.ultimateGearSets.warrior)')
p(v-html="$t('moreGearAchievements')")
br
.shop_armoire
p(v-html='$t("armoireUnlocked")')
br
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
@ -34,6 +36,13 @@
.shop_armoire {
margin: 0 auto;
}
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>

View file

@ -7,7 +7,7 @@
.achievement-karaoke-2x
.col-4
// @TODO: +generatedAvatar({sleep: false})
avatar(:member='user', :avatar-only='true')
avatar.avatar(:member='user', :avatar-only='true')
.col-4
.achievement-karaoke-2x
p {{ $t('congratulations') }}
@ -29,7 +29,10 @@
}
.avatar {
margin-left: 0em;
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>

View file

@ -34,7 +34,7 @@
li
a(href='/static/community-guidelines') {{ $t('communityGuidelines') }}
li
a(href='/hall') {{ $t('hall') }}
router-link(to='/hall/contributors') {{ $t('hall') }}
li
router-link(to='/groups/a29da26b-37de-4a71-b0c6-48e72a900dac') {{ $t('reportBug') }}
li

View file

@ -66,6 +66,8 @@
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
div(v-if='isLeader')
button.btn.btn-danger(v-once, @click='closeChallenge()') {{$t('endChallenge')}}
div(v-if='isLeader')
button.btn.btn-secondary(v-once, @click='exportChallengeCsv()') {{$t('exportChallengeCsv')}}
.description-section
h2 {{$t('challengeSummary')}}
p {{challenge.summary}}
@ -305,6 +307,12 @@ export default {
this.progressMemberId = memberId;
this.$root.$emit('show::modal', 'challenge-member-modal');
},
async exportChallengeCsv () {
// let response = await this.$store.dispatch('challenges:exportChallengeCsv', {
// challengeId: this.challengeId,
// });
window.location = `/api/v3/challenges/${this.challengeId}/export/csv`;
},
},
};
</script>

View file

@ -203,8 +203,8 @@ import tier5 from 'assets/svg/tier-5.svg';
import tier6 from 'assets/svg/tier-6.svg';
import tier7 from 'assets/svg/tier-7.svg';
import tier8 from 'assets/svg/tier-mod.svg';
import tier9 from 'assets/svg/tier-npc.svg';
import tier10 from 'assets/svg/tier-staff.svg';
import tier9 from 'assets/svg/tier-staff.svg';
import tier10 from 'assets/svg/tier-npc.svg';
export default {
props: ['chat', 'groupId', 'groupName', 'inbox'],

View file

@ -176,7 +176,7 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
strong {{backgroundShopSets[0].text}}
.row.incentive-background-row
.col-2(v-for='bg in backgroundShopSets[0].items',
@click='unlock("background." + bg.key)',
@click='buy("background." + bg.key)',
:popover-title='bg.text',
:popover='bg.notes',
popover-trigger='mouseenter')
@ -197,7 +197,7 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
strong {{set.text}}
.col-12(v-if='showPlainBackgroundBlurb(set.identifier, set.items)') {{ $t('incentiveBackgroundsUnlockedWithCheckins') }}
.col-4.text-center.customize-option.background-button(v-for='bg in set.items',
@click='backgroundSelected(bg)',
@click='!user.purchased.background[bg.key] ? backgroundSelected(bg) : unlock("background." + bg.key)',
:popover-title='bg.text',
:popover='bg.notes',
popover-trigger='mouseenter')
@ -868,6 +868,9 @@ export default {
},
mounted () {
if (this.editing) this.modalPage = 2;
// Buy modal is global, so we listen at root. I'd like to not
this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
},
data () {
let backgroundShopSets = getBackgroundShopSets();
@ -1066,6 +1069,9 @@ export default {
backgroundSelected (bg) {
this.$root.$emit('buyModal::showItem', bg);
},
backgroundPurchased () {
this.backgroundUpdate = new Date();
},
},
};
</script>

View file

@ -542,20 +542,17 @@ export default {
},
},
mounted () {
this.searchId = this.groupId;
if (this.isParty) {
this.searchId = 'party';
// @TODO: Set up from old client. Decide what we need and what we don't
// Check Desktop notifs
// Mark Chat seen
// Load invites
}
this.fetchGuild();
if (!this.searchId) this.searchId = this.groupId;
this.$root.$on('updatedGroup', group => {
let updatedGroup = extend(this.group, group);
this.$set(this.group, updatedGroup);
});
this.load();
if (this.user.newMessages[this.searchId]) {
this.$store.dispatch('chat:markChatSeen', {groupId: this.searchId});
}
},
beforeRouteUpdate (to, from, next) {
this.searchId = to.params.groupId;
next();
},
watch: {
// call again the method if the route changes (when this route is already active)
@ -570,6 +567,21 @@ export default {
},
},
methods: {
load () {
if (this.isParty) {
this.searchId = 'party';
// @TODO: Set up from old client. Decide what we need and what we don't
// Check Desktop notifs
// Mark Chat seen
// Load invites
}
this.fetchGuild();
this.$root.$on('updatedGroup', group => {
let updatedGroup = extend(this.group, group);
this.$set(this.group, updatedGroup);
});
},
// @TODO: abstract autocomplete
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {

View file

@ -1,69 +1,73 @@
<template lang="pug">
.row
.row.standard-page
small.muted(v-html="$t('blurbHallContributors')")
.well(v-if='user.contributor.admin')
h2 {{ $t('rewardUser') }}
form(submit='loadHero(heroID)') // @TODO: make click
.form-group
input.form-control(type='text', v-model='heroID', placeholder="$t('UUID')")
.form-group
input.btn.btn-default(type='submit')
| {{ $t('loadUser') }}
form(v-if='hero && hero.profile', submit='saveHero(hero)') // @TODO: make click
a(v-click='clickMember(hero._id, true)')
h3 {{hero.profile.name}}
.form-group
input.form-control(type='text', v-model='hero.contributor.text', placeholder {{ $t('contribTitle') }})
.form-group
label {{ $t('contribLevel') }}
input.form-control(type='number', v-model='hero.contributor.level')
small {{ $t('contribHallText') }}
|&nbsp;
a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear') {{ $t('moreDetails') }}
|,&nbsp;
a(target='_blank', href='https://github.com/HabitRPG/habitica/issues/3801') {{ $t('moreDetails2') }}
.form-group
textarea.form-control(cols=5, placeholder {{ $t('contributions') }}, v-model='hero.contributor.contributions')
//include ../../shared/formattiv-help
hr
.form-group
label {{ $t('balance') }}
input.form-control(type='number', step="any", v-model='hero.balance')
small {{ '`user.balance`' + this.$t('notGems') }}
accordion
accordion-group(heading='Items')
h4 Update Item
.form-group.well
input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',v-model='hero.itemPath')
small.muted Enter the <strong>item path</strong>. E.g., <code>items.pets.BearCub-Zombie</code> or <code>items.gear.owned.head_special_0</code> or <code>items.gear.equipped.head</code>. You can find all the item paths below.
br
input.form-control(type='text',placeholder='Value (eg, 5)',v-model='hero.itemVal')
small.muted Enter the <strong>item value</strong>. E.g., <code>5</code> or <code>false</code> or <code>head_warrior_3</code>. All values are listed in the All Item Paths section below.
accordion
accordion-group(heading='All Item Paths')
pre {{allItemPaths}}
accordion-group(heading='Current Items')
pre {{toJson(hero.items, true)}}
accordion-group(heading='Auth')
h4 Auth
pre {{toJson(hero.auth)}}
.form-group
.checkbox
label
input(type='checkbox', v-model='hero.flags.chatRevoked')
| Chat Privileges Revoked
.form-group
.checkbox
label
input(type='checkbox', v-model='hero.auth.blocked')
| Blocked
.row
.form.col-6(v-if='!hero.profile')
.form-group
input.form-control(type='text', v-model='heroID', :placeholder="$t('UUID')")
.form-group
button.btn.btn-default(@click='loadHero(heroID)')
| {{ $t('loadUser') }}
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
.form-group
input.form-control.btn.btn-primary(type='submit')
| {{ $t('save') }}
.row
.form.col-6(v-if='hero && hero.profile', submit='saveHero(hero)')
a(@click='clickMember(hero._id, true)')
h3 {{hero.profile.name}}
.form-group
input.form-control(type='text', v-model='hero.contributor.text', :placeholder="$t('contribTitle')")
.form-group
label {{ $t('contribLevel') }}
input.form-control(type='number', v-model='hero.contributor.level')
small {{ $t('contribHallText') }}
|&nbsp;
a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear') {{ $t('moreDetails') }}
|,&nbsp;
a(target='_blank', href='https://github.com/HabitRPG/habitica/issues/3801') {{ $t('moreDetails2') }}
.form-group
textarea.form-control(cols=5, :placeholder="$t('contributions')", v-model='hero.contributor.contributions')
//include ../../shared/formattiv-help
hr
.form-group
label {{ $t('balance') }}
input.form-control(type='number', step="any", v-model='hero.balance')
small {{ '`user.balance`' + this.$t('notGems') }}
.accordion
.accordion-group(heading='Items')
h4 Update Item
.form-group.well
input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',v-model='hero.itemPath')
small.muted Enter the <strong>item path</strong>. E.g., <code>items.pets.BearCub-Zombie</code> or <code>items.gear.owned.head_special_0</code> or <code>items.gear.equipped.head</code>. You can find all the item paths below.
br
input.form-control(type='text',placeholder='Value (eg, 5)',v-model='hero.itemVal')
small.muted Enter the <strong>item value</strong>. E.g., <code>5</code> or <code>false</code> or <code>head_warrior_3</code>. All values are listed in the All Item Paths section below.
.accordion
.accordion-group(heading='All Item Paths')
pre {{allItemPaths}}
.accordion-group(heading='Current Items')
pre {{hero.items}}
.accordion-group(heading='Auth')
h4 Auth
pre {{hero.auth}}
.form-group
.checkbox
label
input(type='checkbox', v-if='hero.flags', v-model='hero.flags.chatRevoked')
| Chat Privileges Revoked
.form-group
.checkbox
label
input(type='checkbox', v-model='hero.auth.blocked')
| Blocked
// h4 Backer Status
// Add backer stuff like tier, disable adds, etcs
.form-group
button.form-control.btn.btn-primary
| {{ $t('save') }}
.table-responsive
table.table.table-striped
@ -75,25 +79,26 @@
th {{ $t('title') }}
th {{ $t('contributions') }}
tbody
tr(v-for='(hero, $index) in heroes')
tr(v-for='(hero, index) in heroes')
td
span(v-if='hero.contributor && hero.contributor.admin', :popover="$t('gamemaster')", popover-trigger='mouseenter', popover-placement='right')
a.label.label-default(v-class='userLevelStyle(hero)', v-click='clickMember(hero._id, true)')
a.label.label-default(:class='userLevelStyle(hero)', @click='clickMember(hero._id, true)')
| {{hero.profile.name}}&nbsp;
span(v-class='userAdminGlyphiconStyle(hero)')
//- span(v-class='userAdminGlyphiconStyle(hero)')
span(v-if='!hero.contributor || !hero.contributor.admin')
a.label.label-default(v-if='hero.profile', v-class='userLevelStyle(hero)', v-click='clickMember(hero._id, true)') {{hero.profile.name}}
td(v-if='user.contributor.admin', v-click='populateContributorInput(hero._id, $index)').btn-link {{hero._id}}
a.label.label-default(v-if='hero.profile', v-class='userLevelStyle(hero)', @click='clickMember(hero._id, true)') {{hero.profile.name}}
td(v-if='user.contributor.admin', @click='populateContributorInput(hero._id, index)').btn-link {{hero._id}}
td {{hero.contributor.level}}
td {{hero.contributor.text}}
td
markdown(text='hero.contributor.contributions', target='_blank')
div(v-markdown='hero.contributor.contributions', target='_blank')
</template>
<script>
// import keys from 'lodash/keys';
import each from 'lodash/each';
import markdownDirective from 'client/directives/markdown';
import { mapState } from 'client/libs/store';
import quests from 'common/script/content/quests';
import { mountInfo, petInfo } from 'common/script/content/stable';
@ -117,6 +122,9 @@ export default {
gear,
};
},
directives: {
markdown: markdownDirective,
},
async mounted () {
this.heroes = await this.$store.dispatch('hall:getHeroes');
},
@ -153,7 +161,6 @@ export default {
},
async loadHero (uuid, heroIndex) {
this.currentHeroIndex = heroIndex;
if (!heroIndex) return;
let hero = await this.$store.dispatch('hall:getHero', { uuid });
this.hero = hero;
},

View file

@ -37,10 +37,11 @@ div.item-with-icon.item-notifications.dropdown
@click='go("/user/profile")')
span.glyphicon.glyphicon-plus-sign
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
a.dropdown-item(v-for='(message, key) in user.newMessages', v-if='message.value', @click='navigateToGroup(key)')
span.glyphicon.glyphicon-comment
span {{message.name}}
span.clear-button(@click='clearMessages(k)', :popover="$t('clear')",
a.dropdown-item(v-for='(message, key) in user.newMessages', v-if='message.value')
span(@click='navigateToGroup(key)')
span.glyphicon.glyphicon-comment
span {{message.name}}
span.clear-button(@click='clearMessages(key)', :popover="$t('clear')",
popover-placement='right', popover-trigger='mouseenter', popover-append-to-body='true') Clear
a.dropdown-item(v-for='(notification, index) in user.groupNotifications', @click='viewGroupApprovalNotification(notification, index, true)')
span(:class="groupApprovalNotificationIcon(notification)")
@ -79,6 +80,11 @@ div.item-with-icon.item-notifications.dropdown
margin-top: .2em;
}
.user-dropdown {
max-height: 350px;
overflow: scroll;
}
/* @TODO: Move to shared css */
.dropdown:hover .dropdown-menu {
display: block;
@ -211,8 +217,9 @@ export default {
}
return questInfo;
},
clearMessages () {
this.$store.dispatch('chat:markChatSeen');
clearMessages (key) {
this.$store.dispatch('chat:markChatSeen', {groupId: key});
this.$delete(this.user.newMessages, key);
},
clearCards () {
this.$store.dispatch('chat:clearCards');
@ -278,7 +285,8 @@ export default {
this.go('/party');
return;
}
this.go(`/groups/guild/${key}`);
this.$router.push({ name: 'guild', params: { groupId: key }});
},
async reject (group) {
await this.$store.dispatch('guilds:rejectInvite', {groupId: group.id});

View file

@ -0,0 +1,105 @@
<template lang="pug">
b-modal#send-gems(:title="title", :hide-footer="true", size='lg')
.modal-body(v-if='userReceivingGems', )
.panel.panel-default(:class="gift.type === 'gems' ? 'panel-primary' : 'transparent'", @click='gift.type = "gems"')
.panel-heading
.pull-right
span(v-if='gift.gems.fromBalance') {{ $t('sendGiftGemsBalance', {number: userLoggedIn.balance * 4}) }}
span(v-if='!gift.gems.fromBalance') {{ $t('sendGiftCost', {cost: gift.gems.amount / 4}) }}
| {{ $t('gemsPopoverTitle') }}
.panel-body
.row
.col-md-6
.form-group
input.form-control(type='number', placeholder='Number of Gems',
min='0', :max='gift.gems.fromBalance ? userLoggedIn.balance * 4 : 0',
v-model='amount')
.col-md-6
.btn-group
a.btn.btn-default(:class="{active: gift.gems.fromBalance}", @click="gift.gems.fromBalance = true") {{ $t('sendGiftFromBalance') }}
a.btn.btn-default(:class="{active: !gift.gems.fromBalance}", @click="gift.gems.fromBalance = false") {{ $t('sendGiftPurchase') }}
.row
.col-md-12
p.small.muted {{ $t('gemGiftsAreOptional', assistanceEmailObject) }}
.panel.panel-default(:class="gift.type=='subscription' ? 'panel-primary' : 'transparent'", @click='gift.type = "subscription"')
.panel-heading {{ $t('subscription') }}
.panel-body
.form-group
.radio(v-for='block in subscriptionBlocks', v-if="block.target !== 'group' && block.canSubscribe === true")
label
input(type="radio", name="subRadio", :value="block.key", v-model='gift.subscription.key')
| {{ $t('sendGiftSubscription', {price: block.price, months: block.months}) }}
textarea.form-control(rows='3', v-model='gift.message', :placeholder="$t('sendGiftMessagePlaceholder')")
//include ../formatting-help
.modal-footer
button.btn.btn-primary(v-if='fromBal', ng-click='sendGift(profile._id)') {{ $t("send") }}
button.btn.btn-primary(v-if='!fromBal', ng-click='Payments.showStripe({gift:gift, uuid:profile._id})') {{ $t('card') }}
button.btn.btn-warning(v-if='!fromBal', ng-click='Payments.payPalPayment({gift: gift, giftedTo: profile._id})') PayPal
button.btn.btn-success(v-if='!fromBal', ng-click="Payments.amazonPayments.init({type: 'single', gift: gift, giftedTo: profile._id})") Amazon Payments
button.btn.btn-default(@click='close()') {{$t('cancel')}}
</template>
<script>
import toArray from 'lodash/toArray';
import omitBy from 'lodash/omitBy';
import orderBy from 'lodash/orderBy';
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapState } from 'client/libs/store';
import planGemLimits from '../../../common/script/libs/planGemLimits';
import subscriptionBlocksContent from 'common/script/content/subscriptionBlocks';
// @TODO: EMAILS.TECH_ASSISTANCE_EMAIL
let TECH_ASSISTANCE_EMAIL = 'admin@habitica.com';
export default {
props: ['userReceivingGems'],
components: {
bModal,
},
data () {
return {
planGemLimits,
amount: 0,
gift: {
type: 'gems',
gems: {
amount: 0,
fromBalance: true,
},
subscription: {key: ''},
message: '',
},
assistanceEmailObject: {
hrefTechAssistanceEmail: `<a href="mailto:${TECH_ASSISTANCE_EMAIL}">${TECH_ASSISTANCE_EMAIL}</a>`,
},
};
},
computed: {
...mapState({userLoggedIn: 'user.data'}),
subscriptionBlocks () {
let subscriptionBlocks = toArray(subscriptionBlocksContent);
subscriptionBlocks = omitBy(subscriptionBlocks, (block) => {
return block.discount === true;
});
subscriptionBlocks = orderBy(subscriptionBlocks, ['months']);
return subscriptionBlocks;
},
fromBal () {
return this.gift.type === 'gems' && this.gift.gems.fromBalance;
},
title () {
if (!this.userReceivingGems) return '';
return this.$t('sendGiftHeading', {name: this.userReceivingGems.profile.name});
},
},
methods: {
close () {
this.$root.$emit('hide::modal', 'send-gems');
},
},
};
</script>

View file

@ -66,9 +66,8 @@
:currencyNeeded="getPriceClass()",
:amountNeeded="item.value"
).float-right
</template>
<style lang="scss">
@import '~client/assets/scss/colors.scss';

View file

@ -4,7 +4,7 @@ transition(name="fade")
.row(v-if='notification.type === "error"')
.text.col-12
div(v-html='notification.text')
.row(v-if='notification.type !== "info" && notification.type !== "error" && notification.type !== "drop"')
.row(v-if='["hp", "gp", "xp", "mp"].indexOf(notification.type) !== -1')
.text.col-7.offset-1
div
| {{message}}
@ -14,10 +14,10 @@ transition(name="fade")
div.svg-icon(v-html="icons.star", v-if='notification.type === "xp"')
div.svg-icon(v-html="icons.mana", v-if='notification.type === "mp"')
div(v-html='notification.text')
.row(v-if='notification.type === "info"')
.row(v-if='["info", "success", "crit", "lvl"].indexOf(notification.type) !== -1')
.text.col-12
div(v-html='notification.text')
.row(v-if='notification.type !== "info" && notification.type !== "error" && notification.type === "drop"')
.row(v-if='notification.type === "drop"')
.col-2
.icon-item
div(:class='notification.icon')

View file

@ -4,6 +4,15 @@ div
.row
.col-6.offset-3
button.btn.btn-secondary(@click='sendMessage()') Message
button.btn.btn-secondary(v-if='userLoggedIn.inbox.blocks.indexOf(user._id) === -1', :tooltip="$t('unblock')",
@click="blockUser()", tooltip-placement='right')
span.glyphicon.glyphicon-plus
| {{$t('block')}}
button.btn.btn-secondary(v-if='user._id !== this.userLoggedIn._id && userLoggedIn.inbox.blocks.indexOf(user._id) !== -1',
:tooltip="$t('unblock')", @click="unblockUser()", tooltip-placement='right')
span.glyphicon.glyphicon-ban-circle
| {{$t('unblock')}}
button.btn.btn-secondary(@click='openSendGemsModal()') {{ $t('sendGems') }}
.row
.col-6.offset-3.text-center.nav
.nav-item(@click='selectedPage = "profile"', :class="{active: selectedPage === 'profile'}") Profile
@ -255,6 +264,7 @@ div
button.btn.btn-primary(popover-trigger='mouseenter', popover-placement='right',
:popover='$t(statInfo.allocatepop)') +
private-message-modal(:userIdToMessage='userIdToMessage')
send-gems-modal(:userReceivingGems='userReceivingGems')
</template>
<style lang="scss" scoped>
@ -322,6 +332,7 @@ div
</style>
<script>
import axios from 'axios';
import bModal from 'bootstrap-vue/lib/components/modal';
import each from 'lodash/each';
import { mapState } from 'client/libs/store';
@ -333,6 +344,7 @@ import autoAllocate from '../../../common/script/fns/autoAllocate';
import allocate from '../../../common/script/ops/allocate';
import privateMessageModal from 'client/components/private-message-modal';
import sendGemsModal from 'client/components/payments/sendGemsModal';
import markdown from 'client/directives/markdown';
import achievementsLib from '../../../common/script/libs/achievements';
// @TODO: EMAILS.COMMUNITY_MANAGER_EMAIL
@ -348,10 +360,12 @@ export default {
components: {
bModal,
privateMessageModal,
sendGemsModal,
},
data () {
return {
userIdToMessage: '',
userReceivingGems: '',
editing: false,
editingProfile: {
name: '',
@ -536,6 +550,19 @@ export default {
allocateNow () {
autoAllocate(this.user);
},
blockUser () {
this.userLoggedIn.inbox.blocks.push(this.user._id);
axios.post(`/api/v3/user/block/${this.user._id}`);
},
unblockUser () {
let index = this.userLoggedIn.inbox.blocks.indexOf(this.user._id);
this.userLoggedIn.inbox.blocks.splice(index, 1);
axios.post(`/api/v3/user/block/${this.user._id}`);
},
openSendGemsModal () {
this.userReceivingGems = this.user;
this.$root.$emit('show::modal', 'send-gems');
},
},
};
</script>

View file

@ -19,7 +19,7 @@ export default {
return false;
},
isLeaderOfGroup (user, group) {
return user._id === group.leader._id;
return user._id === group.leader || user._id === group.leader._id;
},
filterGuild (group, filters, search, user) {
let passedSearch = true;

View file

@ -52,7 +52,7 @@ export async function clearFlagCount (store, payload) {
}
export async function markChatSeen (store, payload) {
if (store.user.newMessages) delete store.user.newMessages[payload.groupId];
if (store.state.user.newMessages) delete store.state.user.newMessages[payload.groupId];
let url = `/api/v3/groups/${payload.groupId}/chat/seen`;
let response = await axios.post(url);
return response.data.data;

View file

@ -104,6 +104,7 @@ export default function () {
profileUser: {},
upgradingGroup: {},
notificationStore: [],
modalStack: [],
},
});

View file

@ -0,0 +1,6 @@
{
"share": "Share",
"onwards": "Onwards!",
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
"reachedLevel": "You Reached Level <%= level %>"
}

View file

@ -114,5 +114,6 @@
"shortNamePlaceholder": "What short tag should be used to identify your Challenge?",
"updateChallenge": "Update Challenge",
"haveNoChallenges": "You don't have any Challenges",
"loadMore": "Load More"
"loadMore": "Load More",
"exportChallengeCsv": "Export Challenge"
}

View file

@ -2,6 +2,7 @@
"subscription": "Subscription",
"subscriptions": "Subscriptions",
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
"sendGems": "Send Gems",
"buyGemsGold": "Buy Gems with Gold",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of 20 Gold per Gem. His monthly shipments are initially capped at 25 Gems per month, but for every 3 consecutive months that you are subscribed, this cap increases by 5 Gems, up to a maximum of 50 Gems per month!",
"mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",