Sept 23 fixes (#9074)

* Discover challenges

* Fixed hero loading

* Moved add task button

* Fixed bailey showing

* Added logs for bad sub data

* Fixed blurb editing

* Added confirmation for deleteing message

* Reset invite modals on invite

* fixed group member sorting

* Fixed chat time styles

* Fixed hover on liked

* Fixed like count

* Added reverse

* Fixed editing party

* Added leader conditions

* Added search

* Added loading

* Reset members when leaving party

* Rounded pending

* Fixed overflow on collecting quests

* Added to invite friends

* Hid summary from party

* Fixed button styles

* Fixed button class

* Removed okay button

* Fixed renav for profile modal

* Added subscription back to menu

* Fixed static link

* Added daily due setting

* Added local auth adding

* Fixed centering of text

* Removed message locally

* Added count for new message

* Added style fix for profile pet

* Fixed achievement popovers

* Fixed white boxes

* Added plain color backgrounds

* fixed challenge mutability

* Fixed challenge editing

* Added notation for large numbers

* Add color text to guild sizes

* Removed membership filters from discover challenges

* Added invites to group

* Cmd + enter send message

* Made leader clickable

* Updated group validation

* Added cancelling autocomplete

* Added mention icon

* Added removing member

* Removed extra string
This commit is contained in:
Keith Holliday
2017-09-25 13:02:12 -05:00
committed by GitHub
parent 4759764e61
commit a317b351be
29 changed files with 495 additions and 184 deletions

View File

@@ -27,7 +27,7 @@ div
span.small-text(v-html="$t('inviteFriendsParty')")
br
// TODO link to party creation or party page if partying solo
button.btn.btn-primary(@click='openPartyModal()') {{ $t('startAParty') }}
button.btn.btn-primary(@click='openPartyModal()') {{ partyMembers && partyMembers.length > 1 ? $t('startAParty') : $t('inviteFriends') }}
</template>
<style lang="scss" scoped>

View File

@@ -63,6 +63,7 @@ div
span {{Math.floor(user.stats.gp * 100) / 100}}
notification-menu
a.dropdown.item-with-icon.item-user
span.message-count.top-count(v-if='user.inbox.newMessages > 0') {{user.inbox.newMessages}}
.svg-icon.user(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
a.dropdown-item.edit-avatar.dropdown-separated(@click='showAvatar()')
@@ -75,7 +76,8 @@ div
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.dropdown-separated(:to="{name: 'site'}") {{ $t('settings') }}
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(to="/", @click.prevent='logout()') {{ $t('logout') }}
li(v-if='!this.user.purchased.plan.customerId', @click='showBuyGemsModal("subscribe")')
.dropdown-item.text-center
@@ -295,6 +297,13 @@ div
font-weight: bold;
font-size: 12px;
}
.message-count.top-count {
position: absolute;
right: 0;
top: .5em;
padding: .2em;
}
</style>
<script>

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.row
challenge-modal(:challenge='challenge', :cloning='cloning' v-on:updatedChallenge='updatedChallenge')
challenge-modal(:cloning='cloning' v-on:updatedChallenge='updatedChallenge')
close-challenge-modal(:members='members', :challengeId='challenge._id')
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
@@ -16,7 +16,7 @@
.svg-icon.calendar-icon(v-html="icons.calendarIcon")
| {{$t('endDate')}}
// "endDate": "End Date: <% endDate %>",
span {{challenge.endDate}}
// span {{challenge.endDate}}
.tags
span.tag(v-for='tag in challenge.tags') {{tag}}
.col-4
@@ -28,13 +28,27 @@
.svg-icon.gem-icon(v-html="icons.gemIcon")
| {{challenge.prize}}
.details(v-once) {{$t('prize')}}
.row(v-if='isLeader')
.col-6.offset-6
span
strong View Progress Of
.row.leader-actions(v-if='isLeader')
.col-7.offset-5
span.view-progress
strong {{ $t('viewProgressOf') }}
b-dropdown.create-dropdown(text="Select a Participant")
b-dropdown-item(v-for="member in members", :key="member._id", @click="openMemberProgressModal(member._id)")
| {{ member.profile.name }}
span(v-if='isLeader')
b-dropdown.create-dropdown(:text="$t('create')", :variant="'success'")
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
| {{$t(type)}}
task-modal(
:task="workingTask",
:purpose="taskFormPurpose",
@cancel="cancelTaskModal()",
ref="taskModal",
:challengeId="challengeId",
v-on:taskCreated='taskCreated',
v-on:taskEdited='taskEdited',
@taskDestroyed='taskDestroyed'
)
.row
task-column.col-6(
@@ -50,20 +64,6 @@
button.btn.btn-success(v-once, @click='joinChallenge()') {{$t('joinChallenge')}}
div(v-if='isMember')
button.btn.btn-danger(v-once, @click='leaveChallenge()') {{$t('leaveChallenge')}}
div(v-if='isLeader')
b-dropdown.create-dropdown(:text="$t('create')")
b-dropdown-item(v-for="type in columns", :key="type", @click="createTask(type)")
| {{$t(type)}}
task-modal(
:task="workingTask",
:purpose="taskFormPurpose",
@cancel="cancelTaskModal()",
ref="taskModal",
:challengeId="challengeId",
v-on:taskCreated='taskCreated',
v-on:taskEdited='taskEdited',
@taskDestroyed='taskDestroyed'
)
div(v-if='isLeader')
button.btn.btn-secondary(v-once, @click='edit()') {{$t('editChallenge')}}
div(v-if='isLeader')
@@ -155,6 +155,14 @@
.description-section {
margin-top: 2em;
}
.leader-actions {
margin-top: 1em;
.view-progress {
margin-right: .5em;
}
}
</style>
<style>
@@ -375,6 +383,7 @@ export default {
edit () {
// @TODO: set working challenge
this.cloning = false;
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
this.$root.$emit('show::modal', 'challenge-modal');
},
// @TODO: view members

View File

@@ -130,6 +130,7 @@
</style>
<script>
import clone from 'lodash/clone';
import bModal from 'bootstrap-vue/lib/components/modal';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
@@ -139,7 +140,7 @@ import { TAVERN_ID, MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MAX_SUMMARY_SIZE_FOR_CHAL
import { mapState } from 'client/libs/store';
export default {
props: ['challenge', 'groupId', 'cloning'],
props: ['groupId', 'cloning'],
components: {
bModal,
bDropdown,
@@ -186,19 +187,19 @@ export default {
},
{
label: 'mental_health',
key: 'mental_health ',
key: 'mental_health',
},
{
label: 'getting_organized',
key: 'getting_organized ',
key: 'getting_organized',
},
{
label: 'self_improvement',
key: 'self_improvement ',
key: 'self_improvement',
},
{
label: 'spirituality',
key: 'spirituality ',
key: 'spirituality',
},
{
label: 'time_management',
@@ -250,7 +251,6 @@ export default {
_id: TAVERN_ID,
});
this.resetWorkingChallenge();
this.setUpWorkingChallenge();
},
watch: {
@@ -321,9 +321,14 @@ export default {
return false;
}
},
challenge () {
return this.$store.state.challengeOptions.workingChallenge;
},
},
methods: {
setUpWorkingChallenge () {
this.resetWorkingChallenge();
if (!this.challenge) return;
this.workingChallenge = Object.assign({}, this.workingChallenge, this.challenge);
@@ -357,6 +362,8 @@ export default {
shortName: '',
todos: [],
};
this.$store.state.workingChallenge = {};
},
async createChallenge () {
// @TODO: improve error handling, add it to updateChallenge, make errors translatable. Suggestion: `<% fieldName %> is required` where possible, where `fieldName` is inserted as the translatable string that's used for the field header.
@@ -385,9 +392,11 @@ export default {
name: catName,
});
});
this.workingChallenge.categories = serverCategories;
let challenge = await this.$store.dispatch('challenges:createChallenge', {challenge: this.workingChallenge});
let challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories;
let challenge = await this.$store.dispatch('challenges:createChallenge', {challenge: challengeDetails});
// @TODO: When to remove from guild instead?
this.user.balance -= this.workingChallenge.prize / 4;
@@ -403,18 +412,21 @@ export default {
let categoryKeys = this.workingChallenge.categories;
let serverCategories = [];
categoryKeys.forEach(key => {
let catName = this.categoriesHashByKey[key];
let newKey = key.trim();
let catName = this.categoriesHashByKey[newKey];
serverCategories.push({
slug: key,
slug: newKey,
name: catName,
});
});
this.workingChallenge.categories = serverCategories;
let challengeDetails = clone(this.workingChallenge);
challengeDetails.categories = serverCategories;
this.$emit('updatedChallenge', {
challenge: this.workingChallenge,
challenge: challengeDetails,
});
this.$store.dispatch('challenges:updateChallenge', {challenge: this.workingChallenge});
this.$store.dispatch('challenges:updateChallenge', {challenge: challengeDetails});
this.resetWorkingChallenge();
this.$root.$emit('hide::modal', 'challenge-modal');
},

View File

@@ -15,7 +15,7 @@
input.custom-control-input(type="checkbox", :value='group.key' v-model="categoryFilters")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t(group.label) }}
.form-group
.form-group(v-if='$route.name !== "findChallenges"')
h3 Membership
.form-check(
v-for="group in roleOptions",

View File

@@ -48,6 +48,10 @@ export default {
},
watch: {
text (newText) {
if (!newText[newText.length - 1] || newText[newText.length - 1] === ' ') {
this.searchActive = false;
}
if (newText[newText.length - 1] !== '@') return;
this.searchActive = true;
this.currentSearchPosition = newText.length - 1;

View File

@@ -10,7 +10,7 @@
//.hr(v-if='displayDivider(msg)')
.hr-middle(v-once) {{ msg.timestamp }}
.row(v-if='user._id !== msg.uuid')
.col-2
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid]',
:member="cachedProfileData[msg.uuid]",
@@ -18,8 +18,9 @@
:hideClassBadge='true',
@click.native="showMemberModal(msg.uuid)",
)
.card.col-10
.message-hidden(v-if='msg.flagCount > 0 && user.contributor.admin') Message Hidden
.card(:class='inbox ? "col-8" : "col-10"')
.mentioned-icon(v-if='isUserMentioned(msg)')
.message-hidden(v-if='msg.flagCount > 0 && user.contributor.admin') Message Hidden - {{ msg.flagCount }} Flags
.card-block
h3.leader(
:class='userLevelStyle(cachedProfileData[msg.uuid])'
@@ -27,7 +28,7 @@
)
| {{msg.user}}
.svg-icon(v-html="icons[`tier${cachedProfileData[msg.uuid].contributor.level}`]", v-if='cachedProfileData[msg.uuid] && cachedProfileData[msg.uuid].contributor && cachedProfileData[msg.uuid].contributor.level')
p {{msg.timestamp | timeAgo}}
p.time {{msg.timestamp | timeAgo}}
.text(v-markdown='msg.text')
hr
.action(@click='like(msg, index)', v-if='msg.likes', :class='{active: msg.likes[user._id]}')
@@ -43,13 +44,15 @@
span.action(v-if='msg.uuid === user._id || inbox', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right(v-if='likeCount(msg) > 0')
span.action.float-right.liked(v-if='likeCount(msg) > 0')
.svg-icon(v-html="icons.liked")
| + {{ likeCount(msg) }}
// @TODO can we avoid duplicating all this code? Cannot we just push everything
// to the right if the user is the author?
// Maybe we just create two sub components instead
.row(v-if='user._id === msg.uuid')
.card.col-10
.card(:class='inbox ? "col-8" : "col-10"')
.mentioned-icon(v-if='isUserMentioned(msg)')
.message-hidden(v-if='msg.flagCount > 0 && user.contributor.admin') Message Hidden - {{ msg.flagCount }} Flags
.card-block
h3.leader(
@@ -58,7 +61,7 @@
)
| {{msg.user}}
.svg-icon(v-html="icons[`tier${cachedProfileData[msg.uuid].contributor.level}`]", v-if='cachedProfileData[msg.uuid] && cachedProfileData[msg.uuid].contributor && cachedProfileData[msg.uuid].contributor.level')
p {{msg.timestamp | timeAgo}}
p.time {{msg.timestamp | timeAgo}}
.text(v-markdown='msg.text')
hr
.action(@click='like(msg, index)', v-if='msg.likes', :class='{active: msg.likes[user._id]}')
@@ -74,10 +77,10 @@
span.action(v-if='msg.uuid === user._id', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right(v-if='likeCount(msg) > 0')
span.action.float-right.liked(v-if='likeCount(msg) > 0')
.svg-icon(v-html="icons.liked")
| + {{ likeCount(msg) }}
.col-2
div(:class='inbox ? "col-4" : "col-2"')
avatar(
v-if='cachedProfileData[msg.uuid]',
:member="cachedProfileData[msg.uuid]",
@@ -134,6 +137,26 @@
}
// End of tier colors
.leader {
margin-bottom: 0;
}
.time {
font-size: 12px;
color: #878190;
}
.mentioned-icon {
width: 16px;
height: 16px;
border-radius: 50%;
background-color: #bda8ff;
box-shadow: 0 1px 1px 0 rgba(26, 24, 29, 0.12);
position: absolute;
right: -.5em;
top: -.5em;
}
h3 { // this is the user name
cursor: pointer;
@@ -173,6 +196,7 @@
.text {
font-size: 14px;
color: #4e4a57;
text-align: left !important;
}
.action {
@@ -185,6 +209,10 @@
cursor: pointer;
}
.liked:hover {
cursor: default;
}
.action .svg-icon {
margin-right: .2em;
width: 16px;
@@ -302,6 +330,27 @@ export default {
},
},
methods: {
isUserMentioned (message) {
let user = this.user;
if (message.hasOwnProperty('highlight')) return message.highlight;
message.highlight = false;
let messagetext = message.text.toLowerCase();
let username = user.profile.name;
let mentioned = messagetext.indexOf(username.toLowerCase());
let pattern = `${username}([^\w]|$){1}`;
if (mentioned === -1) return message.highlight;
let preceedingchar = messagetext.substring(mentioned - 1, mentioned);
if (mentioned === 0 || preceedingchar.trim() === '' || preceedingchar === '@') {
let regex = new RegExp(pattern, 'i');
message.highlight = regex.test(messagetext);
}
return message.highlight;
},
canViewFlag (message) {
if (message.uuid === this.user._id) return true;
if (!message.flagCount || message.flagCount === 0) return true;
@@ -345,7 +394,13 @@ export default {
},
likeCount (message) {
if (!message.likes) return 0;
return Object.keys(message.likes).length;
let likeCount = 0;
for (let key in message.likes) {
let like = message.likes[key];
if (like) likeCount += 1;
}
return likeCount;
},
async like (messageToLike, index) {
let message = cloneDeep(messageToLike);
@@ -374,10 +429,13 @@ export default {
this.$root.$emit('show::modal', 'report-flag');
},
async remove (message, index) {
if (!confirm(this.$t('areYouSureDeleteMessage'))) return;
this.chat.splice(index, 1);
if (this.inbox) {
axios.delete(`/api/v3/user/messages/${message.id}`);
this.$delete(this.user.inbox.messages, message.id);
return;
}

View File

@@ -228,8 +228,9 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.row.text-center.set-title
strong {{backgroundShopSets[0].text}}
.row.incentive-background-row
.col-12(v-if='showPlainBackgroundBlurb(backgroundShopSets[0].identifier, backgroundShopSets[0].items)') {{ $t('incentiveBackgroundsUnlockedWithCheckins') }}
.col-2(v-for='bg in backgroundShopSets[0].items',
@click='buy("background." + bg.key)',
@click='unlock("background." + bg.key)',
:popover-title='bg.text',
:popover='bg.notes',
popover-trigger='mouseenter')
@@ -248,7 +249,6 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
.row(v-for='set in sets', v-if='activeSubPage === key')
.col-8.offset-2.text-center.set-title
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='!user.purchased.background[bg.key] ? backgroundSelected(bg) : unlock("background." + bg.key)',
:popover-title='bg.text',

View File

@@ -8,7 +8,7 @@
.col-6.title-details
h1 {{group.name}}
strong.float-left(v-once) {{$t('groupLeader')}}
span.float-left(v-if='group.leader.profile') : {{group.leader.profile.name}}
span.leader.float-left(v-if='group.leader.profile', @click='showMemberProfile(group.leader)') : {{group.leader.profile.name}}
.col-6
.row.icon-row
.col-4.offset-4(v-bind:class="{ 'offset-8': isParty }")
@@ -16,7 +16,7 @@
.svg-icon.shield(v-html="icons.goldGuildBadgeIcon", v-if='group.memberCount > 1000')
.svg-icon.shield(v-html="icons.silverGuildBadgeIcon", v-if='group.memberCount > 100 && group.memberCount < 999')
.svg-icon.shield(v-html="icons.bronzeGuildBadgeIcon", v-if='group.memberCount < 100')
span.number {{group.memberCount}}
span.number {{ group.memberCount | abbrNum }}
div(v-once) {{ $t('members') }}
.col-4(v-if='!isParty')
.item-with-icon
@@ -30,8 +30,11 @@
.row.new-message-row
textarea(:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')", v-model='newMessage', @keydown='updateCarretPosition')
autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat')
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
button.btn.btn-secondary.float-left(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }}
.row
.col-6
button.btn.btn-secondary.float-left.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.float-left(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
@@ -75,7 +78,7 @@
.svg-icon(v-html="icons.questIcon")
h4(v-once) {{ $t('youAreNotOnQuest') }}
p(v-once) {{ $t('questDescription') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()") {{ $t('startAQuest') }}
button.btn.btn-secondary(v-once, @click="openStartQuestModal()", v-if='isLeader') {{ $t('startAQuest') }}
.row.quest-active-section(v-if='isParty && onPendingQuest && !onActiveQuest')
h2 Pending quest
button.btn.btn-secondary(v-once, @click="questForceStart()") {{ $t('begin') }}
@@ -110,7 +113,7 @@
| {{parseFloat(group.quest.progress.hp).toFixed(2)}} / {{parseFloat(questData.boss.hp).toFixed(2)}}
.col-6
// @TODO: Why do we not sync quset progress on the group doc? Each user could have different progress
span.float-right {{user.party.quest.progress.up || 0}} pending damage
span.float-right {{parseFloat(user.party.quest.progress.up).toFixed(1) || 0}} pending damage
.row.rage-bar-row(v-if='questData.boss.rage')
.col-12
.grey-progress-bar
@@ -119,9 +122,9 @@
.col-6
span.float-left
| Rage {{questData.boss.rage.value}}
button.btn.btn-secondary(v-once, @click="questAbort()") {{ $t('abort') }}
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='isLeader') {{ $t('abort') }}
.section-header
.section-header(v-if='!isParty')
.row
.col-10
h3(v-once) {{ $t('guildSummary') }}
@@ -164,7 +167,7 @@
.section(v-if="sections.challenges")
group-challenges(:groupId='searchId')
div.text-center
button.btn.btn-primary(class='btn-danger', v-if='isMember', @click='clickLeave()') {{ $t('leave') }}
button.btn.btn-danger(v-if='isMember', @click='clickLeave()') {{ $t('leave') }}
</template>
<style lang="scss" scoped>
@@ -174,6 +177,10 @@
color: $purple-200;
}
.leader:hover {
cursor: pointer;
}
.button-container {
margin-bottom: 1em;
@@ -294,7 +301,7 @@
z-index: 10;
position: absolute;
right: 1em;
bottom: 3em;
bottom: 1em;
}
}
@@ -411,7 +418,7 @@
.collect-progress-bar {
background-color: #24cc8f;
height: 15px;
max-width: 100%;
}
.hr {
@@ -649,6 +656,11 @@ export default {
document.body.removeChild(div);
},
updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;
}
let text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
@@ -661,6 +673,8 @@ export default {
this.$root.$emit('show::modal', 'members-modal');
},
async sendMessage () {
if (!this.newMessage) return;
let response = await this.$store.dispatch('chat:postChat', {
group: this.group,
message: this.newMessage,
@@ -685,6 +699,7 @@ export default {
}
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.searchId});
if (this.isParty) {
this.$store.state.party.data = group;
this.group = this.$store.state.party.data;
@@ -749,14 +764,10 @@ export default {
if (this.isParty) {
data.type = 'party';
Analytics.updateUser({partySize: null, partyID: null});
this.$store.state.party.members = [];
}
await this.$store.dispatch('guilds:leave', data);
// @TODO: Implement
// User.sync().then(function () {
// $rootScope.hardRedirect('/party');
// });
},
upgradeGroup () {
this.$store.state.upgradingGroup = this.group;
@@ -794,6 +805,12 @@ export default {
}
// $rootScope.$state.go('options.inventory.quests');
},
async showMemberProfile (leader) {
let heroDetails = await this.$store.dispatch('members:fetchMember', { memberId: leader._id });
this.$store.state.profileUser = heroDetails.data.data;
this.$store.state.profileOptions.startingPage = 'profile';
this.$root.$emit('show::modal', 'profile');
},
async questCancel () {
if (!confirm(this.$t('sureCancel'))) return;
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});

View File

@@ -373,7 +373,7 @@ export default {
async submit () {
if (this.$store.state.user.data.balance < 1 && !this.workingGroup.id) {
// @TODO: Add proper notifications
alert('Not enough gems');
alert(this.$t('notEnoughGems'));
return;
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"});
// @TODO when modal is implemented, enable analytics
@@ -385,27 +385,16 @@ export default {
}); */
}
if (!this.workingGroup.name || !this.workingGroup.description) {
// @TODO: Add proper notifications - split this out into two, make errors translatable. Suggestion: `<% fieldName %> is required` for all errors where possible, where `fieldName` is inserted as the translatable string that's used for the field header.
alert('Enter a name and description');
return;
}
let errors = [];
if (!this.workingGroup.summary) {
// @TODO: Add proper notifications. Summary is mandatory for only public guilds (not tavern, private guilds, parties)
alert('Enter a summary');
return;
}
if (!this.workingGroup.name) errors.push(this.$t('nameRequired'));
if (!this.workingGroup.summary) errors.push(this.$t('summaryRequired'));
if (this.workingGroup.summary.length > MAX_SUMMARY_SIZE_FOR_GUILDS) errors.push(this.$t('summaryTooLong'));
if (!this.workingGroup.description) errors.push(this.$t('descriptionRequired'));
if (!this.isParty && (!this.workingGroup.categories || this.workingGroup.categories.length === 0)) errors.push(this.$t('categoiresRequired'));
if (this.workingGroup.summary.length > MAX_SUMMARY_SIZE_FOR_GUILDS) {
// @TODO: Add proper notifications. Summary is mandatory for only public guilds (not tavern, private guilds, parties)
alert('Summary is too long');
return;
}
if (!this.workingGroup.categories || this.workingGroup.categories.length === 0) {
// @TODO: Add proper notifications
alert('One or more categories must be selected');
if (errors.length > 0) {
alert(errors.join('\n'));
return;
}

View File

@@ -13,7 +13,7 @@ b-modal#invite-modal(:title="$t('inviteFriends')", size='lg')
input.form-control(type='text', v-model='user.uuid')
tr
td
button.btn.btn-xs.pull-right(@click='addUuid()')
button.btn.btn-primary.pull-right(@click='addUuid()')
i.glyphicon.glyphicon-plus
| +
tr
@@ -36,7 +36,7 @@ b-modal#invite-modal(:title="$t('inviteFriends')", size='lg')
input.form-control(type='email', v-model='email.email')
tr
td(colspan=2)
a.btn.btn-xs.pull-right(@click='addEmail()')
button.btn.btn-primary.pull-right(@click='addEmail()')
i.glyphicon.glyphicon-plus
| +
tr
@@ -132,6 +132,9 @@ export default {
this.text(this.$t(invitationString));
this.invitees = [];
this.emails = [];
// @TODO: This function didn't make it over this.resetInvitees();
// @TODO: Sync group invites?
@@ -140,7 +143,7 @@ export default {
// } else {
// this.$router.push(`/groups/guilds/${this.group._id}`);
// }
this.$root.$emit('hide:modal', 'invite-modal');
this.$root.$emit('hide::modal', 'invite-modal');
// @TODO: error?
// _resetInvitees();
},

View File

@@ -1,7 +1,8 @@
<template lang="pug">
// @TODO: Move this to a member directory
div
b-modal#members-modal(:title="$t('createGuild')", size='md')
remove-member-modal(:member-to-remove='memberToRemove', :group-id='this.groupId' @member-removed='memberRemoved')
b-modal#members-modal(:title="$t('createGuild')", size='md', :hide-footer='true')
.header-wrap(slot="modal-header")
.row
.col-6
@@ -16,36 +17,47 @@ div
span.dropdown-label {{ $t('sortBy') }}
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(v-for='sortOption in sortOptions', @click='sort(sortOption.value)', :key='sortOption.value') {{sortOption.text}}
.row(v-for='member in sortedMembers')
.col-11.no-padding-left
member-details(:member='member')
.col-1.actions
b-dropdown(right=true)
.svg-icon.inline.dots(slot='button-content', v-html="icons.dots")
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon", v-if='isLeader')
span.text {{$t('removeMember')}}
b-dropdown-item(@click='sendMessage(member._id)')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.messageIcon")
span.text {{$t('sendMessage')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('promoteToLeader')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('addManager')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon")
span.text {{$t('removeManager2')}}
.row(v-if='groupId === "challenge"')
.col-12.text-center
button.btn.btn-secondary(@click='loadMoreMembers()') {{ $t('loadMore') }}
.row.gradient(v-if='members.length > 3')
.row(v-if='invites.length > 0')
.col-6.offset-3.nav
.nav-item(@click='viewMembers()', :class="{active: selectedPage === 'members'}") {{ $t('members') }}
.nav-item(@click='viewInvites()', :class="{active: selectedPage === 'invites'}") {{ $t('invites') }}
div(v-if='selectedPage === "members"')
.row(v-for='(member, index) in sortedMembers')
.col-11.no-padding-left
member-details(:member='member')
.col-1.actions
b-dropdown(right=true)
.svg-icon.inline.dots(slot='button-content', v-html="icons.dots")
b-dropdown-item(@click='removeMember(member, index)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon", v-if='isLeader')
span.text {{$t('removeMember')}}
b-dropdown-item(@click='sendMessage(member._id)')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.messageIcon")
span.text {{$t('sendMessage')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('promoteToLeader')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('addManager')}}
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon")
span.text {{$t('removeManager2')}}
.row(v-if='groupId === "challenge"')
.col-12.text-center
button.btn.btn-secondary(@click='loadMoreMembers()') {{ $t('loadMore') }}
.row.gradient(v-if='members.length > 3')
div(v-if='selectedPage === "invites"')
.row(v-for='member in invites')
.col-11.no-padding-left
member-details(:member='member')
.modal-footer
button.btn.btn-primary(@click='close()') {{ $t('close') }}
</template>
<style lang='scss'>
@@ -130,10 +142,29 @@ div
.dropdown-icon-item .svg-icon {
width: 20px;
}
.nav {
font-weight: bold;
margin-bottom: .5em;
margin-top: .5em;
}
.nav-item {
display: inline-block;
font-size: 16px;
margin: 0 auto;
padding: .5em;
color: #878190;
}
.nav-item:hover, .nav-item.active {
color: #4f2a93;
border-bottom: 2px solid #4f2a93;
cursor: pointer;
}
</style>
<script>
// @TODO: Move this under members directory
import sortBy from 'lodash/sortBy';
import bModal from 'bootstrap-vue/lib/components/modal';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
@@ -141,6 +172,7 @@ import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import { mapState } from 'client/libs/store';
import privateMessageModal from 'client/components/private-message-modal';
import removeMemberModal from 'client/components/members/removeMemberModal';
import MemberDetails from '../memberDetails';
import removeIcon from 'assets/members/remove.svg';
import messageIcon from 'assets/members/message.svg';
@@ -155,12 +187,15 @@ export default {
bDropdownItem,
MemberDetails,
privateMessageModal,
removeMemberModal,
},
data () {
return {
sortOption: '',
selectedPage: 'members',
members: [],
memberToRemove: '',
invites: [],
memberToRemove: {},
sortOptions: [
{
value: 'level',
@@ -212,6 +247,13 @@ export default {
},
sortedMembers () {
let sortedMembers = this.members;
if (this.searchTerm) {
sortedMembers = sortedMembers.filter(member => {
return member.profile.name.toLowerCase().indexOf(this.searchTerm.toLowerCase) !== -1;
});
}
if (!this.sortOption) return sortedMembers;
sortedMembers = sortBy(this.members, [(member) => {
@@ -227,7 +269,7 @@ export default {
}
}]);
return this.members;
return sortedMembers;
},
},
watch: {
@@ -252,6 +294,12 @@ export default {
includeAllPublicFields: true,
});
this.members = members;
let invites = await this.$store.dispatch('members:getGroupInvites', {
groupId,
includeAllPublicFields: true,
});
this.invites = invites;
}
if (this.$store.state.memberModalOptions.viewingMembers.length > 0) {
@@ -277,24 +325,15 @@ export default {
this.$root.$emit('show::modal', 'members-modal');
},
async removeMember (member) {
async removeMember (member, index) {
this.memberToRemove = member;
this.memberToRemove.index = index;
this.$root.$emit('show::modal', 'remove-member');
},
async confirmRemoveMember (confirmation) {
if (!confirmation) {
this.memberToRemove = '';
return;
}
await this.$store.dispatch('members:removeMember', {
memberId: this.memberToRemove._id,
groupId: this.group._id,
message: this.removeMessage,
});
this.memberToRemove = '';
this.removeMessage = '';
memberRemoved () {
this.members.splice(this.memberToRemove.index, 1);
this.group.memberCount -= 1;
this.memberToRemove = {};
},
async quickReply (uid) {
this.memberToReply = uid;
@@ -330,6 +369,12 @@ export default {
this.members = this.members.concat(newMembers);
},
viewMembers () {
this.selectedPage = 'members';
},
viewInvites () {
this.selectedPage = 'invites';
},
},
};
</script>

View File

@@ -4,11 +4,11 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
.card-block
.row
.col-md-2.badge-column
.shield-wrap
.shield-wrap(:class="{gold: guild.memberCount > 1000, silver: guild.memberCount > 100 && guild.memberCount < 999}")
.svg-icon.shield(v-html="icons.goldGuildBadge", v-if='guild.memberCount > 1000')
.svg-icon.shield(v-html="icons.silverGuildBadgeIcon", v-if='guild.memberCount > 100 && guild.memberCount < 999')
.svg-icon.shield(v-html="icons.bronzeGuildBadgeIcon", v-if='guild.memberCount < 100')
.member-count {{guild.memberCount}}
.member-count {{ guild.memberCount | abbrNum }}
.col-md-10
.row
.col-md-8
@@ -76,6 +76,14 @@ router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
width: 70px;
}
.gold {
color: #fdbb5a;
}
.silver {
color: #c2c2c2;
}
.badge-column {
display: flex;
align-items: center;

View File

@@ -12,7 +12,7 @@
div(v-if='questData')
questDialogContent(:item="questData")
div.text-center
button.btn.btn-primary(@click='questInit()', :disabled="!Boolean(selectedQuest)") {{$t('inviteToPartyOrQuest')}}
button.btn.btn-primary(@click='questInit()', :disabled="!Boolean(selectedQuest) || loading") {{$t('inviteToPartyOrQuest')}}
div.text-center
p {{$t('inviteInformation')}}
.side-panel(v-if='questData')
@@ -122,6 +122,7 @@ export default {
},
data () {
return {
loading: false,
selectedQuest: {},
icons: Object.freeze({
copy: copyIcon,
@@ -157,6 +158,8 @@ export default {
},
async questInit () {
this.loading = true;
Analytics.updateUser({
partyID: this.group._id,
partySize: this.group.memberCount,
@@ -170,6 +173,8 @@ export default {
if (this.$store.state.party.data) this.$store.state.party.data.quest = quest;
this.loading = false;
this.$root.$emit('hide::modal', 'start-quest-modal');
},
},

View File

@@ -12,8 +12,13 @@
.row
textarea(:placeholder="$t('tavernCommunityGuidelinesPlaceholder')", v-model='newMessage', :class='{"user-entry": newMessage}', @keydown='updateCarretPosition')
autocomplete(:text='newMessage', v-on:select="selectedAutocomplete", :coords='coords', :chat='group.chat')
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
button.btn.btn-secondary.float-left(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
.row
.col-6
button.btn.btn-secondary.send-chat.float-left(v-once, @click='sendMessage()') {{ $t('send') }}
.col-6
button.btn.btn-secondary.float-right.fetch(v-once, @click='fetchRecentMessages()') {{ $t('fetchRecentMessages') }}
button.btn.btn-secondary.float-right(v-once, @click='reverseChat()') {{ $t('reverseChat') }}
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
div.col-8(v-once, v-html="$t('communityGuidelinesIntro')")
@@ -523,6 +528,11 @@ export default {
document.body.removeChild(div);
},
updateCarretPosition (eventUpdate) {
if (eventUpdate.metaKey && eventUpdate.keyCode === 13) {
this.sendMessage();
return;
}
let text = eventUpdate.target;
this.getCoord(eventUpdate, text);
},
@@ -551,6 +561,9 @@ export default {
async fetchRecentMessages () {
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
},
reverseChat () {
this.group.chat.reverse();
},
},
};
</script>

View File

@@ -186,8 +186,9 @@ export default {
window.scrollTo(0, 200);
this.loadHero(id, index);
},
clickMember (hero) {
this.$store.state.profileUser = hero;
async clickMember (hero) {
let heroDetails = await this.$store.dispatch('members:fetchMember', { memberId: hero._id });
this.$store.state.profileUser = heroDetails.data.data;
this.$store.state.profileOptions.startingPage = 'profile';
this.$root.$emit('show::modal', 'profile');
},

View File

@@ -0,0 +1,53 @@
<template lang="pug">
b-modal#remove-member(:title="$t('removeMember')", size='md', :hide-footer="true")
.text-center
h2.col-12 {{ $t('sureKick') }}
.col-12.removing-member(v-if='memberToRemove.profile') {{memberToRemove.profile.name}}
.modal-body
textarea.form-control(type='text',
rows='5',
:placeholder="$t('optionalMessage')",
v-model='removeMessage')
.modal-footer
button.pull-left.btn.btn-danger(@click='confirmRemoveMember()') {{ $t('yesRemove') }}
button.btn.btn-default(@click='close()') {{ $t('cancel') }}
</template>
<style scoped>
.removing-member {
color: #878190;
margin-bottom: .5em;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
export default {
props: ['memberToRemove', 'groupId'],
components: {
bModal,
},
data () {
return {
removeMessage: '',
};
},
methods: {
async confirmRemoveMember () {
await this.$store.dispatch('members:removeMember', {
memberId: this.memberToRemove._id,
groupId: this.groupId,
message: this.removeMessage,
});
this.removeMessage = '';
this.$emit('member-removed', this.memberToRemove);
this.close();
},
close () {
this.$root.$emit('hide::modal', 'remove-member');
},
},
};
</script>

View File

@@ -34,7 +34,8 @@
.form-horizontal(v-if='user.flags.classSelected && !user.preferences.disableClasses')
h5 {{ $t('characterBuild') }}
h6(v-once) {{ $t('class') + ': ' }}
span {{ classText }}&nbsp;
// @TODO: what is classText
span(v-if='classText') {{ classText }}&nbsp;
button.btn.btn-danger.btn-xs(@click='changeClass(null)', v-once) {{ $t('changeClass') }}
small.cost 3
span.Pet_Currency_Gem1x.inline-gems
@@ -117,21 +118,20 @@
button.btn.btn-primary(v-if='!user.auth[network.key].id', @click='socialLogin(network.key, user)') {{ $t('registerWithSocial', {network: network.name}) }}
button.btn.btn-primary(disabled='disabled', v-if='!hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('registeredWithSocial', {network: network.name}) }}
button.btn.btn-danger(@click='deleteSocialAuth(network.key)', v-if='hasBackupAuthOption(network.key) && user.auth[network.key].id') {{ $t('detachSocial', {network: network.name}) }}
// hr
// TODO
// div(v-if='!user.auth.local.username')
hr
div(v-if='!user.auth.local.username')
p {{ $t('addLocalAuth') }}
form(ng-submit='http("post", "/api/v3/user/auth/local/register", localAuth, "addedLocalAuth")', name='localAuth', novalidate)
.form(name='localAuth', novalidate)
//-.alert.alert-danger(ng-messages='changeUsername.$error && changeUsername.submitted') {{ $t('fillAll') }}
.form-group
input.form-control(type='text', placeholder="$t('username')", v-model='localAuth.username', required)
input.form-control(type='text', :placeholder="$t('username')", v-model='localAuth.username', required)
.form-group
input.form-control(type='text', placeholder="$t('email')", v-model='localAuth.email', required)
input.form-control(type='text', :placeholder="$t('email')", v-model='localAuth.email', required)
.form-group
input.form-control(type='password', placeholder="$t('password')", v-model='localAuth.password', required)
input.form-control(type='password', :placeholder="$t('password')", v-model='localAuth.password', required)
.form-group
input.form-control(type='password', placeholder="$t('confirmPass')", v-model='localAuth.confirmPassword', required)
button.btn.btn-primary(type='submit', ng-disabled='localAuth.$invalid', value="$t('submit')")
input.form-control(type='password', :placeholder="$t('confirmPass')", v-model='localAuth.confirmPassword', required)
button.btn.btn-primary(type='submit', @click='addLocalAuth()') {{ $t('submit') }}
.usersettings(v-if='user.auth.local.username')
p {{ $t('username') }}
@@ -231,6 +231,12 @@ export default {
usernameUpdates: {},
emailUpdates: {},
passwordUpdates: {},
localAuth: {
username: '',
email: '',
password: '',
confirmPassword: '',
},
};
},
mounted () {
@@ -290,6 +296,7 @@ export default {
// Guide.goto('intro', 0, true);
},
showBailey () {
this.user.flags.newStuff = true;
this.$root.$emit('show::modal', 'new-stuff');
},
hasBackupAuthOption (networkKeyToCheck) {
@@ -380,6 +387,9 @@ export default {
alert(e.message);
}
},
addLocalAuth () {
axios.post('/api/v3/user/auth/local/register', this.localAuth, 'addedLocalAuth');
},
},
};
</script>

View File

@@ -163,6 +163,16 @@ export default {
}]);
},
purchasedPlanIdInfo () {
if (!this.subscriptionBlocks[this.user.purchased.plan.planId]) {
// @TODO: find which subs are in the common
console.log(this.subscriptionBlocks[this.user.purchased.plan.planId]); // eslint-disable-line
return {
price: 0,
months: 0,
plan: '',
};
}
return {
price: this.subscriptionBlocks[this.user.purchased.plan.planId].price,
months: this.subscriptionBlocks[this.user.purchased.plan.planId].months,

View File

@@ -1,9 +1,7 @@
<template lang="pug">
nav.navbar.navbar-inverse.fixed-top.navbar-toggleable-sm
.navbar-header
router-link.nav-item(
to='/static/home',
)
router-link.nav-item(:to='!isUserLoggedIn ? "/static/home" : "/"')
.logo.svg-icon(v-html='icons.logo')
.collapse.navbar-collapse
ul.navbar-nav.mr-auto(v-if='$route.name !== "home"')

View File

@@ -6,7 +6,7 @@
.filters.d-flex.justify-content-end
.filter.small-text(
v-for="filter in types[type].filters",
:class="{active: activeFilter.label === filter.label}",
:class="{active: activeFilters[type].label === filter.label}",
@click="activateFilter(type, filter)",
) {{ $t(filter.label) }}
.tasks-list(ref="taskList", v-sortable='', @onsort='sorted')
@@ -226,9 +226,14 @@ export default {
reward: rewardIcon,
});
let activeFilters = {};
for (let type in types) {
activeFilters[type] = types[type].filters.find(f => f.default === true);
}
return {
types,
activeFilter: types[this.type].filters.find(f => f.default === true),
activeFilters,
icons,
openedCompletedTodos: false,
@@ -252,7 +257,7 @@ export default {
return inAppRewards(this.user);
},
hasRewardsList () {
return this.isUser === true && this.type === 'reward' && this.activeFilter.label !== 'custom';
return this.isUser === true && this.type === 'reward' && this.activeFilters[this.type].label !== 'custom';
},
initialColumnDescription () {
// Show the column description in the middle only if there are no elements (tasks or in app items)
@@ -262,6 +267,12 @@ export default {
return this.tasks[`${this.type}s`].length === 0;
},
dailyDueDefaultView () {
if (this.user.preferences.dailyDueDefaultView) {
this.activateFilter('daily', this.types.daily.filters[1]);
}
return this.user.preferences.dailyDueDefaultView;
},
},
watch: {
taskList: {
@@ -270,6 +281,11 @@ export default {
}, 250),
deep: true,
},
dailyDueDefaultView () {
if (this.user.preferences.dailyDueDefaultView) {
this.activateFilter('daily', this.types.daily.filters[1]);
}
},
},
mounted () {
this.setColumnBackgroundVisibility();
@@ -301,7 +317,7 @@ export default {
if (type === 'todo' && filter.label === 'complete2') {
this.loadCompletedTodos();
}
this.activeFilter = filter;
this.activeFilters[type] = filter;
},
setColumnBackgroundVisibility () {
this.$nextTick(() => {
@@ -330,7 +346,7 @@ export default {
},
filterTask (task) {
// View
if (!this.activeFilter.filter(task)) return false;
if (!this.activeFilters[task.type].filter(task)) return false;
// Tags
const selectedTags = this.selectedTags;

View File

@@ -36,7 +36,7 @@
.svg-icon.envelope(v-html="icons.messageIcon")
h4(v-once) Nothing Here Yet
p(v-once) Select a conversation on the left
chat-message.container-fluid.message-scroll(:chat.sync='activeChat', :inbox='true', ref="chatscroll")
chat-message.message-scroll(:chat.sync='activeChat', :inbox='true', ref="chatscroll")
// @TODO: Implement new message header here when we fix the above

View File

@@ -20,9 +20,9 @@ div
member-details(:member="user")
.row
.col-6.offset-3.text-center.nav
.nav-item(@click='selectedPage = "profile"', :class="{active: selectedPage === 'profile'}") {{ $t('profile') }}
.nav-item(@click='selectedPage = "stats"', :class="{active: selectedPage === 'stats'}") {{ $t('stats') }}
.nav-item(@click='selectedPage = "achievements"', :class="{active: selectedPage === 'achievements'}") {{ $t('achievements') }}
.nav-item(@click='selectPage("profile")', :class="{active: selectedPage === 'profile'}") {{ $t('profile') }}
.nav-item(@click='selectPage("stats")', :class="{active: selectedPage === 'stats'}") {{ $t('stats') }}
.nav-item(@click='selectPage("achievements")', :class="{active: selectedPage === 'achievements'}") {{ $t('achievements') }}
#userProfile.standard-page(v-show='selectedPage === "profile"', v-if='user.profile')
.row
.col-8
@@ -94,9 +94,9 @@ div
.row(v-for='(category, key) in achievements')
h2.col-12.text-center {{ $t(key+'Achievs') }}
.col-3.text-center(v-for='(achievement, key) in category.achievements')
.box.achievement-container(:id='key', :class='{"achievement-unearned": !achievement.earned}')
.box.achievement-container(:id='key + "-achievement"', :class='{"achievement-unearned": !achievement.earned}')
b-popover(
:target="'#' + key",
:target="'#' + key + '-achievement'",
triggers="hover",
placement="top",
)
@@ -121,11 +121,11 @@ div
span {{ value }}
#stats.standard-page(v-show='selectedPage === "stats"', v-if='user.preferences')
.row
.col-6 {{$t('equipment')}}
h2.text-center
.col-6
h2.text-center {{$t('equipment')}}
.well
.col-4.item-wrapper
.box(:class='{white: equippedItems.eyewear}')
.box(:class='{white: equippedItems.eyewear && equippedItems.eyewear.indexOf("base_0") === -1}')
div(:class="`shop_${equippedItems.eyewear}`")
h3 {{$t('eyewear')}}
.col-4.item-wrapper
@@ -133,11 +133,11 @@ div
div(:class="`shop_${equippedItems.head}`")
h3 {{$t('headGear')}}
.col-4.item-wrapper
.box(:class='{white: equippedItems.headAccessory}')
.box(:class='{white: equippedItems.headAccessory && equippedItems.headAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${equippedItems.headAccessory}`")
h3 {{$t('headAccess')}}
.col-4.item-wrapper
.box(:class='{white: equippedItems.backAccessory}')
.box(:class='{white: equippedItems.backAccessory && equippedItems.backAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${equippedItems.backAccessory}`")
h3 {{$t('backAccess')}}
.col-4.item-wrapper
@@ -145,7 +145,7 @@ div
div(:class="`shop_${equippedItems.armor}`")
h3 {{$t('armor')}}
.col-4.item-wrapper
.box(:class='{white: equippedItems.bodyAccessory}')
.box(:class='{white: equippedItems.bodyAccessory && equippedItems.bodyAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${equippedItems.bodyAccessory}`")
h3 {{$t('bodyAccess')}}
.col-4.item-wrapper
@@ -161,7 +161,7 @@ div
h2.text-center {{$t('costume')}}
.well
.col-4.item-wrapper
.box(:class='{white: costumeItems.eyewear}')
.box(:class='{white: costumeItems.eyewear && costumeItems.eyewear.indexOf("base_0") === -1}')
div(:class="`shop_${costumeItems.eyewear}`")
h3 {{$t('eyewear')}}
.col-4.item-wrapper
@@ -169,11 +169,11 @@ div
div(:class="`shop_${costumeItems.head}`")
h3 {{$t('headGear')}}
.col-4.item-wrapper
.box(:class='{white: costumeItems.headAccessory}')
.box(:class='{white: costumeItems.headAccessory && costumeItems.headAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${costumeItems.headAccessory}`")
h3 {{$t('headAccess')}}
.col-4.item-wrapper
.box(:class='{white: costumeItems.backAccessory}')
.box(:class='{white: costumeItems.backAccessory && costumeItems.backAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${costumeItems.backAccessory}`")
h3 {{$t('backAccess')}}
.col-4.item-wrapper
@@ -181,7 +181,7 @@ div
div(:class="`shop_${costumeItems.armor}`")
h3 {{$t('armor')}}
.col-4.item-wrapper
.box(:class='{white: costumeItems.bodyAccessory}')
.box(:class='{white: costumeItems.bodyAccessory && costumeItems.bodyAccessory.indexOf("base_0") === -1}')
div(:class="`shop_${costumeItems.bodyAccessory}`")
h3 {{$t('bodyAccess')}}
.col-4.item-wrapper
@@ -189,8 +189,8 @@ div
div(:class="`shop_${costumeItems.weapon}`")
h3 {{$t('mainHand')}}
.col-4.item-wrapper
.box(:class='{white: user.preferences.background}')
div(:class="user.preferences.background")
.box(:class='{white: user.preferences.background}', style="overflow:hidden")
div(:class="'background_' + user.preferences.background")
h3 {{$t('background')}}
.col-4.item-wrapper
.box(:class='{white: costumeItems.shield && costumeItems.shield.indexOf("base_0") === -1}')
@@ -326,7 +326,7 @@ div
}
.pet, .mount {
margin-top: -1.6em;
margin-top: -1.8em !important;
}
.header {
@@ -660,6 +660,7 @@ export default {
this.editingProfile.name = user.profile.name;
this.editingProfile.imageUrl = user.profile.imageUrl;
this.editingProfile.blurb = user.profile.blurb;
if (!user.achievements.quests) user.achievements.quests = {};
if (!user.achievements.challenges) user.achievements.challenges = {};
@@ -700,6 +701,11 @@ export default {
},
},
methods: {
selectPage (page) {
this.selectedPage = page;
// @TODO: rename this property?
this.$store.state.profileOptions.startingPage = page;
},
sendMessage () {
this.$store.state.userIdToMessage = this.user._id;
this.$root.$emit('show::modal', 'private-message');

View File

@@ -1,6 +1,32 @@
import intersection from 'lodash/intersection';
export default {
filters: {
// https://stackoverflow.com/questions/2685911/is-there-a-way-to-round-numbers-into-a-reader-friendly-format-e-g-1-1k
abbrNum: (number) => {
let decPlaces = 2;
decPlaces = Math.pow(10, decPlaces);
let abbrev = ['k', 'm', 'b', 't'];
for (let i = abbrev.length - 1; i >= 0; i--) {
let size = Math.pow(10, (i + 1) * 3);
if (size <= number) {
number = Math.round(number * decPlaces / size) / decPlaces;
if (number === 1000 && i < abbrev.length - 1) {
number = 1;
i++;
}
number += abbrev[i];
break;
}
}
return number;
},
},
methods: {
isMemberOfGroup (user, group) {
if (group._id === this.$store.state.constants.TAVERN_ID) return true;

View File

@@ -21,8 +21,11 @@ export async function fetchMember (store, payload) {
export async function getGroupInvites (store, payload) {
let url = `${apiV3Prefix}/groups/${payload.groupId}/invites`;
if (payload.includeAllPublicFields) {
url += '?includeAllPublicFields=true';
}
let response = await axios.get(url);
return response;
return response.data.data;
}
export async function getChallengeMembers (store, payload) {

View File

@@ -95,6 +95,7 @@ export default function () {
challengeOptions: {
cloning: false,
tasksToClone: {},
workingChallenge: {},
},
editingGroup: {}, // @TODO move to local state
// content data, frozen to prevent Vue from modifying it since it's static and never changes

View File

@@ -89,7 +89,7 @@
"joinedChallenge": "Joined a Challenge",
"joinedChallengeText": "This user put themselves to the test by joining a Challenge!",
"myChallenges": "My Challenges",
"findChallenges": "Find Challenges",
"findChallenges": "Discover Challenges",
"noChallengeTitle": "You don't have any Challenges.",
"challengeDescription1": "Challenges are community events in which players compete and earn prizes by completing a group of related tasks.",
"challengeDescription2": "Find recommended Challenges based on your interests, browse Habitica's public Challenges, or create your own Challenges.",
@@ -123,5 +123,6 @@
"summaryTooLong": "Summary is too long",
"descriptionRequired": "Description is required",
"locationRequired": "Location of challenge is required ('Add to')",
"categoiresRequired": "One or more categories must be selected"
"categoiresRequired": "One or more categories must be selected",
"viewProgressOf": "View Progress Of"
}

View File

@@ -382,5 +382,8 @@
"questOwnerRewards": "Quest Owner Rewards",
"updateParty": "Update Party",
"upgrade": "Upgrade",
"selectPartyMember": "Select a Party Member"
"selectPartyMember": "Select a Party Member",
"areYouSureDeleteMessage": "Are you sure you want to delete this message?",
"reverseChat": "Reverse Chat",
"invites": "Invites"
}

View File

@@ -170,6 +170,8 @@ api.getMemberAchievements = {
};
// Return a request handler for getMembersForGroup / getInvitesForGroup / getMembersForChallenge
// @TODO: This violates the Liskov substitution principle. We should create factory functions. See Webhooks for a good example
function _getMembersForItem (type) {
// check for allowed `type`
if (['group-members', 'group-invites', 'challenge-members'].indexOf(type) === -1) {
@@ -243,9 +245,18 @@ function _getMembersForItem (type) {
} else if (type === 'group-invites') {
if (group.type === 'guild') { // eslint-disable-line no-lonely-if
query['invitations.guilds.id'] = group._id;
if (req.query.includeAllPublicFields === 'true') {
fields = memberFields;
addComputedStats = true;
}
} else {
query['invitations.party.id'] = group._id; // group._id and not groupId because groupId could be === 'party'
// @TODO invitations are now stored like this: `'invitations.parties': []` Probably need a database index for it.
if (req.query.includeAllPublicFields === 'true') {
fields = memberFields;
addComputedStats = true;
}
}
}