old client structure
This commit is contained in:
@@ -0,0 +1,313 @@
|
||||
<template lang="pug">
|
||||
.row.chat-row
|
||||
.col-12
|
||||
h3.float-left.label(:class="{accepted: communityGuidelinesAccepted }") {{ label }}
|
||||
div.float-right(v-markdown='$t("markdownFormattingHelp")')
|
||||
|
||||
.row(v-if="communityGuidelinesAccepted")
|
||||
textarea(:placeholder='placeholder',
|
||||
v-model='newMessage',
|
||||
ref='user-entry',
|
||||
:class='{"user-entry": newMessage}',
|
||||
@keydown='updateCarretPosition',
|
||||
@keyup.ctrl.enter='sendMessageShortcut()',
|
||||
@keydown.tab='handleTab($event)',
|
||||
@keydown.up='selectPreviousAutocomplete($event)',
|
||||
@keydown.down='selectNextAutocomplete($event)',
|
||||
@keydown.enter='selectAutocomplete($event)',
|
||||
@keydown.esc='handleEscape($event)',
|
||||
@paste='disableMessageSendShortcut()',
|
||||
maxlength='3000'
|
||||
)
|
||||
span {{ currentLength }} / 3000
|
||||
autocomplete(
|
||||
ref='autocomplete',
|
||||
:text='newMessage',
|
||||
v-on:select="selectedAutocomplete",
|
||||
:textbox='textbox',
|
||||
:coords='coords',
|
||||
:caretPosition = 'caretPosition',
|
||||
:chat='group.chat')
|
||||
|
||||
community-guidelines
|
||||
|
||||
.row.chat-actions
|
||||
.col-6.chat-receive-actions
|
||||
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') }}
|
||||
.col-6.chat-send-actions
|
||||
button.btn.btn-primary.send-chat.float-right(:disabled="!communityGuidelinesAccepted", @click='sendMessage()') {{ $t('send') }}
|
||||
|
||||
slot(
|
||||
name="additionRow",
|
||||
)
|
||||
|
||||
.row
|
||||
.hr.col-12
|
||||
chat-message(:chat.sync='group.chat', :group-type='group.type', :group-id='group._id', :group-name='group.name')
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import debounce from 'lodash/debounce';
|
||||
|
||||
import autocomplete from '../chat/autoComplete';
|
||||
import communityGuidelines from './communityGuidelines';
|
||||
import chatMessage from '../chat/chatMessages';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
|
||||
export default {
|
||||
props: ['label', 'group', 'placeholder'],
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
components: {
|
||||
autocomplete,
|
||||
communityGuidelines,
|
||||
chatMessage,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
newMessage: '',
|
||||
sending: false,
|
||||
caretPosition: 0,
|
||||
chat: {
|
||||
submitDisable: false,
|
||||
submitTimeout: null,
|
||||
},
|
||||
coords: {
|
||||
TOP: 0,
|
||||
LEFT: 0,
|
||||
},
|
||||
textbox: this.$refs,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
currentLength () {
|
||||
return this.newMessage.length;
|
||||
},
|
||||
communityGuidelinesAccepted () {
|
||||
return this.user.flags.communityGuidelinesAccepted;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
|
||||
getCoord (e, text) {
|
||||
this.caretPosition = text.selectionEnd;
|
||||
let div = document.createElement('div');
|
||||
let span = document.createElement('span');
|
||||
let copyStyle = getComputedStyle(text);
|
||||
|
||||
[].forEach.call(copyStyle, (prop) => {
|
||||
div.style[prop] = copyStyle[prop];
|
||||
});
|
||||
|
||||
div.style.position = 'absolute';
|
||||
document.body.appendChild(div);
|
||||
div.textContent = text.value.substr(0, this.caretPosition);
|
||||
span.textContent = text.value.substr(this.caretPosition) || '.';
|
||||
div.appendChild(span);
|
||||
this.coords = {
|
||||
TOP: span.offsetTop,
|
||||
LEFT: span.offsetLeft,
|
||||
};
|
||||
document.body.removeChild(div);
|
||||
},
|
||||
updateCarretPosition: debounce(function updateCarretPosition (eventUpdate) {
|
||||
this._updateCarretPosition(eventUpdate);
|
||||
}, 250),
|
||||
_updateCarretPosition (eventUpdate) {
|
||||
let text = eventUpdate.target;
|
||||
this.getCoord(eventUpdate, text);
|
||||
},
|
||||
async sendMessageShortcut () {
|
||||
// If the user recently pasted in the text field, don't submit
|
||||
if (!this.chat.submitDisable) {
|
||||
this.sendMessage();
|
||||
}
|
||||
},
|
||||
async sendMessage () {
|
||||
if (this.sending) return;
|
||||
this.sending = true;
|
||||
let response;
|
||||
|
||||
try {
|
||||
response = await this.$store.dispatch('chat:postChat', {
|
||||
group: this.group,
|
||||
message: this.newMessage,
|
||||
});
|
||||
} catch (e) {
|
||||
// catch exception to allow function to continue
|
||||
}
|
||||
|
||||
if (response) {
|
||||
this.group.chat.unshift(response.message);
|
||||
this.newMessage = '';
|
||||
}
|
||||
|
||||
this.sending = false;
|
||||
|
||||
// @TODO: I would like to not reload everytime we send. Why are we reloading?
|
||||
// The response has all the necessary data...
|
||||
let chat = await this.$store.dispatch('chat:getChat', {groupId: this.group._id});
|
||||
this.group.chat = chat;
|
||||
},
|
||||
|
||||
disableMessageSendShortcut () {
|
||||
// Some users were experiencing accidental sending of messages after pasting
|
||||
// So, after pasting, disable the shortcut for a second.
|
||||
this.chat.submitDisable = true;
|
||||
|
||||
if (this.chat.submitTimeout) {
|
||||
// If someone pastes during the disabled period, prevent early re-enable
|
||||
clearTimeout(this.chat.submitTimeout);
|
||||
this.chat.submitTimeout = null;
|
||||
}
|
||||
|
||||
this.chat.submitTimeout = window.setTimeout(() => {
|
||||
this.chat.submitTimeout = null;
|
||||
this.chat.submitDisable = false;
|
||||
}, 500);
|
||||
},
|
||||
|
||||
handleTab (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
e.preventDefault();
|
||||
if (e.shiftKey) {
|
||||
this.$refs.autocomplete.selectPrevious();
|
||||
} else {
|
||||
this.$refs.autocomplete.selectNext();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
handleEscape (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.cancel();
|
||||
}
|
||||
},
|
||||
|
||||
selectNextAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.selectNext();
|
||||
}
|
||||
},
|
||||
|
||||
selectPreviousAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.selectPrevious();
|
||||
}
|
||||
},
|
||||
|
||||
selectAutocomplete (e) {
|
||||
if (this.$refs.autocomplete.searchActive) {
|
||||
e.preventDefault();
|
||||
this.$refs.autocomplete.makeSelection();
|
||||
}
|
||||
},
|
||||
|
||||
selectedAutocomplete (newText) {
|
||||
this.newMessage = newText;
|
||||
},
|
||||
|
||||
fetchRecentMessages () {
|
||||
this.$emit('fetchRecentMessages');
|
||||
},
|
||||
reverseChat () {
|
||||
this.group.chat.reverse();
|
||||
},
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
// Reset chat
|
||||
this.newMessage = '';
|
||||
this.coords = {
|
||||
TOP: 0,
|
||||
LEFT: 0,
|
||||
};
|
||||
|
||||
next();
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/variables.scss';
|
||||
|
||||
.chat-actions {
|
||||
margin-top: 1em;
|
||||
|
||||
.chat-receive-actions {
|
||||
padding-left: 0;
|
||||
|
||||
button {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-send-actions {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-row {
|
||||
position: relative;
|
||||
|
||||
.label:not(.accepted) {
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
.row {
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 150px;
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
border: solid 1px $gray-400;
|
||||
font-style: italic;
|
||||
line-height: 1.43;
|
||||
color: $gray-300;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.user-entry {
|
||||
font-style: normal;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
text-align: center;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.hr-middle {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto Condensed';
|
||||
line-height: 1.5;
|
||||
text-align: center;
|
||||
color: $gray-200;
|
||||
background-color: $gray-700;
|
||||
padding: .2em;
|
||||
margin-top: .2em;
|
||||
display: inline-block;
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template lang="pug">
|
||||
.row.community-guidelines(v-if='!communityGuidelinesAccepted')
|
||||
div.col.col-sm-12.col-xl-8(v-once, v-html="$t('communityGuidelinesIntro')")
|
||||
|
||||
div.col-md-auto.col-md-12.col-xl-4
|
||||
button.btn.btn-info.btn-follow-guidelines(@click='acceptCommunityGuidelines()', v-once) {{ $t('acceptCommunityGuidelines') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.community-guidelines {
|
||||
background-color: rgba(135, 129, 144, 0.84);
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
color: $white;
|
||||
width: 100%;
|
||||
// width: calc(100% - 24px);
|
||||
border-radius: 4px;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.col {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: $white;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
button {
|
||||
margin: 20px 12px;
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.btn-follow-guidelines {
|
||||
white-space: pre-line;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
communityGuidelinesAccepted () {
|
||||
return this.user.flags.communityGuidelinesAccepted;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
acceptCommunityGuidelines () {
|
||||
this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,194 @@
|
||||
<template lang="pug">
|
||||
b-modal#create-party-modal(size='lg', hide-footer=true)
|
||||
.header-wrap(slot="modal-header")
|
||||
.quest_screen
|
||||
.row.heading
|
||||
.col-12.text-center.pr-5.pl-5
|
||||
h2(v-once) {{ $t('playInPartyTitle') }}
|
||||
p.mb-4(v-once) {{ $t('playInPartyDescription') }}
|
||||
button.btn.btn-primary(v-once, @click='createParty()') {{ $t('createParty') }}
|
||||
.row.grey-row
|
||||
.col-12.text-center
|
||||
.join-party
|
||||
h2(v-once) {{ $t('wantToJoinPartyTitle') }}
|
||||
p(v-html='$t("wantToJoinPartyDescription")')
|
||||
.form-group(@click='copyUsername')
|
||||
.d-flex.align-items-center
|
||||
label.mr-3(v-once) {{ $t('username') }}
|
||||
.flex-grow-1
|
||||
.input-group-prepend.input-group-text @
|
||||
.text {{ user.auth.local.username }}
|
||||
.svg-icon.copy-icon(v-html='icons.copy')
|
||||
.small(v-once) {{ $t('copy') }}
|
||||
</template>
|
||||
|
||||
<style>
|
||||
#create-party-modal .modal-body {
|
||||
padding: 0rem 0.75rem;
|
||||
}
|
||||
|
||||
#create-party-modal .modal-dialog {
|
||||
width: 35.75rem;
|
||||
}
|
||||
|
||||
#create-party-modal .modal-header {
|
||||
padding: 0;
|
||||
border-bottom: 0px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.copy-icon {
|
||||
width: 1rem;
|
||||
}
|
||||
|
||||
.form-control[readonly] {
|
||||
background-color: $white;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
background-color: $gray-600;
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-500;
|
||||
width: 90%;
|
||||
margin: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.grey-row {
|
||||
background-color: $gray-700;
|
||||
color: #4e4a57;
|
||||
padding: 2em;
|
||||
border-radius: 0px 0px 2px 2px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $gray-100;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
padding: 0;
|
||||
color: #4e4a57;
|
||||
width: 100%;
|
||||
|
||||
.quest_screen {
|
||||
background-image: url('~client/assets/images/group@3x.png');
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 246px;
|
||||
margin-bottom: 1.5rem;
|
||||
border-radius: 2px 2px 0 0;
|
||||
image-rendering: optimizequality;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: $purple-200;
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
margin-top: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
input.form-control {
|
||||
border: 0px;
|
||||
padding-left: 0.25rem;
|
||||
}
|
||||
|
||||
.input-group-prepend {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.input-group-text {
|
||||
background-color: $white;
|
||||
border: 0px;
|
||||
border-radius: 0px;
|
||||
color: $gray-300;
|
||||
padding: 0rem 0.1rem 0rem 0.75rem;
|
||||
}
|
||||
|
||||
.join-party {
|
||||
background-image: url('~client/assets/images/party.png');
|
||||
background-size: cover;
|
||||
width: 203px;
|
||||
height: 66px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
label {
|
||||
color: $gray-100;
|
||||
font-weight: bold;
|
||||
margin-bottom: 0rem;
|
||||
margin-left: 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal-dialog .text {
|
||||
min-height: 1rem;
|
||||
margin: 0.75rem auto 0.75rem 0.25rem;
|
||||
}
|
||||
|
||||
.small {
|
||||
color: $gray-200;
|
||||
margin: auto 0.5rem auto 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
async createParty () {
|
||||
let group = {
|
||||
type: 'party',
|
||||
};
|
||||
group.name = this.$t('possessiveParty', {name: this.user.profile.name});
|
||||
let party = await this.$store.dispatch('guilds:create', {group});
|
||||
this.$store.state.party.data = party;
|
||||
this.user.party._id = party._id;
|
||||
|
||||
Analytics.updateUser({
|
||||
partyID: party._id,
|
||||
partySize: 1,
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::hide::modal', 'create-party-modal');
|
||||
this.$router.push('/party');
|
||||
},
|
||||
copyUsername () {
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(this.user.auth.local.username);
|
||||
} else {
|
||||
let copyText = document.createElement('textarea');
|
||||
copyText.value = this.user.auth.local.username;
|
||||
document.body.appendChild(copyText);
|
||||
copyText.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(copyText);
|
||||
}
|
||||
this.text(this.$t('usernameCopied'));
|
||||
},
|
||||
},
|
||||
mixins: [notifications],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,212 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
sidebar(@search="updateSearch", @filter="updateFilters")
|
||||
|
||||
.standard-page
|
||||
.header.row
|
||||
.col-8
|
||||
h1.page-header.float-left(v-once) {{ $t('publicGuilds') }}
|
||||
.col-4
|
||||
// @TODO: Add when we implement recent activity .float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t('sort')", right=true)
|
||||
b-dropdown-item(v-for='sortOption in sortOptions', :key="sortOption.value", @click='sort(sortOption.value)') {{sortOption.text}}
|
||||
button.btn.btn-secondary.create-group-button.float-right(@click='createGroup()')
|
||||
.svg-icon.positive-icon(v-html="icons.positiveIcon")
|
||||
span(v-once) {{$t('createGuild2')}}
|
||||
|
||||
.row
|
||||
.no-guilds.text-center.col-md-6.offset-md-3(v-if='!loading && filteredGuilds.length === 0')
|
||||
h2(v-once) {{$t('noGuildsMatchFilters')}}
|
||||
|
||||
.row
|
||||
.col-md-12
|
||||
public-guild-item(v-for="guild in filteredGuilds", :key='guild._id', :guild="guild", :display-leave='true')
|
||||
mugen-scroll(
|
||||
:handler="fetchGuilds",
|
||||
:should-handle="!loading && !this.hasLoadedAllGuilds",
|
||||
:handle-on-mount="true",
|
||||
v-show="loading",
|
||||
)
|
||||
span(v-once) {{ $t('loading') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.sort-select {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
.positive-icon {
|
||||
color: $green-10;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.no-guilds {
|
||||
color: $gray-200;
|
||||
margin-top: 10em;
|
||||
|
||||
h2 {
|
||||
color: $gray-200;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import MugenScroll from 'vue-mugen-scroll';
|
||||
import PublicGuildItem from './publicGuildItem';
|
||||
import Sidebar from './sidebar';
|
||||
import groupUtilities from 'client/mixins/groupsUtilities';
|
||||
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
function _mapCategories (guilds) {
|
||||
guilds.forEach((guild) => {
|
||||
if (!guild.categories) return;
|
||||
guild.categorySlugs = guild.categories.map(cat => {
|
||||
if (!cat) return;
|
||||
return cat.slug;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
mixins: [groupUtilities],
|
||||
components: { PublicGuildItem, MugenScroll, Sidebar },
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
positiveIcon,
|
||||
}),
|
||||
loading: false,
|
||||
hasLoadedAllGuilds: false,
|
||||
lastPageLoaded: 0,
|
||||
search: '',
|
||||
filters: {},
|
||||
sort: 'none',
|
||||
sortOptions: [
|
||||
{
|
||||
text: this.$t('none'),
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
text: this.$t('memberCount'),
|
||||
value: 'member_count',
|
||||
},
|
||||
{
|
||||
text: this.$t('recentActivity'),
|
||||
value: 'recent_activity',
|
||||
},
|
||||
],
|
||||
guilds: [],
|
||||
queryFilters: {
|
||||
minMemberCount: 0,
|
||||
maxMemberCount: 0,
|
||||
leader: false,
|
||||
member: false,
|
||||
categories: '',
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
filteredGuilds () {
|
||||
let search = this.search;
|
||||
let filters = this.filters;
|
||||
let user = this.$store.state.user.data;
|
||||
let filterGuild = this.filterGuild;
|
||||
// @TODO: Move this to the server
|
||||
return this.guilds.filter((guild) => {
|
||||
return filterGuild(guild, filters, search, user);
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async updateSearch (eventData) {
|
||||
// this.search = eventData.searchTerm; @TODO: Probably don't need this anymore
|
||||
|
||||
// Reset the page when filters are updated but not the other queries
|
||||
this.lastPageLoaded = 0;
|
||||
this.queryFilters.page = this.lastPageLoaded;
|
||||
|
||||
this.queryFilters.search = eventData.searchTerm;
|
||||
|
||||
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
|
||||
_mapCategories(guilds);
|
||||
this.guilds = guilds;
|
||||
},
|
||||
async updateFilters (eventData) {
|
||||
// this.filters = eventData; @TODO: Probably don't need this anymore
|
||||
|
||||
// Reset all filters
|
||||
this.queryFilters = {
|
||||
minMemberCount: 0,
|
||||
maxMemberCount: 0,
|
||||
leader: false,
|
||||
member: false,
|
||||
categories: '',
|
||||
};
|
||||
|
||||
// Reset the page when filters are updated
|
||||
this.lastPageLoaded = 0;
|
||||
this.hasLoadedAllGuilds = false;
|
||||
this.queryFilters.page = this.lastPageLoaded;
|
||||
|
||||
this.queryFilters.categories = eventData.categories.join(',');
|
||||
|
||||
// Role filters
|
||||
let filteringRole = eventData.roles && eventData.roles.length > 0;
|
||||
if (filteringRole && eventData.roles.indexOf('member') !== -1) {
|
||||
this.queryFilters.member = true;
|
||||
}
|
||||
|
||||
if (filteringRole && eventData.roles.indexOf('guild_leader') !== -1) {
|
||||
this.queryFilters.leader = true;
|
||||
}
|
||||
|
||||
// Size filters
|
||||
if (eventData.guildSize && eventData.guildSize.indexOf('gold_tier') !== -1) {
|
||||
this.queryFilters.minMemberCount = 1000;
|
||||
this.queryFilters.maxMemberCount = 0; // No max
|
||||
}
|
||||
|
||||
if (eventData.guildSize && eventData.guildSize.indexOf('silver_tier') !== -1) {
|
||||
this.queryFilters.minMemberCount = 100;
|
||||
this.queryFilters.maxMemberCount = 999;
|
||||
}
|
||||
|
||||
if (eventData.guildSize && eventData.guildSize.indexOf('bronze_tier') !== -1) {
|
||||
this.queryFilters.minMemberCount = 0; // No Min
|
||||
this.queryFilters.maxMemberCount = 99;
|
||||
}
|
||||
|
||||
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
|
||||
_mapCategories(guilds);
|
||||
this.guilds = guilds;
|
||||
},
|
||||
async fetchGuilds () {
|
||||
// We have the data cached
|
||||
if (this.lastPageLoaded === 0 && this.guilds.length > 0) {
|
||||
this.lastPageLoaded += 1;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
this.queryFilters.page = this.lastPageLoaded;
|
||||
let guilds = await this.$store.dispatch('guilds:getPublicGuilds', this.queryFilters);
|
||||
if (guilds.length === 0) this.hasLoadedAllGuilds = true;
|
||||
|
||||
_mapCategories(guilds);
|
||||
this.guilds.push(...guilds);
|
||||
|
||||
this.lastPageLoaded++;
|
||||
this.loading = false;
|
||||
},
|
||||
createGroup () {
|
||||
this.$store.state.editingGroup = {};
|
||||
this.$root.$emit('bv::show::modal', 'guild-form');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,529 @@
|
||||
<template lang="pug">
|
||||
.row(v-if="group._id")
|
||||
group-form-modal(v-if='isParty')
|
||||
start-quest-modal(:group='this.group')
|
||||
quest-details-modal(:group='this.group')
|
||||
participant-list-modal(:group='this.group')
|
||||
group-gems-modal
|
||||
.col-12.col-sm-8.standard-page
|
||||
.row
|
||||
.col-12.col-md-6.title-details
|
||||
h1 {{group.name}}
|
||||
div
|
||||
span.mr-1.ml-0
|
||||
strong(v-once) {{$t('groupLeader')}}:
|
||||
user-link.mx-1(:user="group.leader")
|
||||
.col-12.col-md-6
|
||||
.row.icon-row
|
||||
.col-4.offset-4(v-bind:class="{ 'offset-8': isParty }")
|
||||
.item-with-icon(@click="showMemberModal()")
|
||||
.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 | abbrNum }}
|
||||
div.member-list.label(v-once) {{ $t('memberList') }}
|
||||
.col-4(v-if='!isParty')
|
||||
.item-with-icon(@click='showGroupGems()')
|
||||
.svg-icon.gem(v-html="icons.gem")
|
||||
span.number {{group.balance * 4}}
|
||||
div.label(v-once) {{ $t('guildBank') }}
|
||||
chat(
|
||||
:label="$t('chat')",
|
||||
:group="group",
|
||||
:placeholder="!isParty ? $t('chatPlaceholder') : $t('partyChatPlaceholder')",
|
||||
@fetchRecentMessages="fetchRecentMessages()"
|
||||
)
|
||||
template(slot="additionRow")
|
||||
.row(v-if='showNoNotificationsMessage')
|
||||
.col-12.no-notifications
|
||||
| {{$t('groupNoNotifications')}}
|
||||
.col-12.col-sm-4.sidebar
|
||||
.row(:class='{"guild-background": !isParty}')
|
||||
.col-12.buttons-wrapper
|
||||
.button-container
|
||||
button.btn.btn-success(class='btn-success', v-if='isLeader && !group.purchased.active', @click='upgradeGroup()')
|
||||
| {{ $t('upgrade') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(b-btn, @click="updateGuild", v-once, v-if='isLeader || isAdmin') {{ $t('edit') }}
|
||||
.button-container
|
||||
button.btn.btn-success(class='btn-success', v-if='!isMember', @click='join()') {{ $t('join') }}
|
||||
.button-container
|
||||
button.btn.btn-primary(v-once, @click='showInviteModal()') {{$t('invite')}}
|
||||
// @TODO: hide the invitation button if there's an active group plan and the player is not the leader
|
||||
.button-container
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}} // Suggest making the button visible to the leader too - useful for them to test how the feature works or to send a note to themself. -- Alys
|
||||
.button-container
|
||||
// @TODO: V2 button.btn.btn-primary(v-once, v-if='isMember && !isParty') {{$t('donateGems')}} // Suggest removing the isMember restriction - it's okay if non-members donate to a public guild. Also probably allow it for parties if parties can buy imagery. -- Alys
|
||||
div
|
||||
quest-sidebar-section(:group='group', v-if='isParty')
|
||||
sidebar-section(:title="$t('guildSummary')", v-if='!isParty')
|
||||
p(v-markdown='group.summary')
|
||||
sidebar-section(:title="$t('groupDescription')")
|
||||
p(v-markdown='group.description')
|
||||
sidebar-section(
|
||||
:title="$t('challenges')",
|
||||
:tooltip="$t('challengeDetails')"
|
||||
)
|
||||
group-challenges(:groupId='searchId')
|
||||
div.text-center
|
||||
button.btn.btn-danger(v-if='isMember', @click='clickLeave()') {{ isParty ? $t('leaveParty') : $t('leaveGroup') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
@media (min-width: 1300px) {
|
||||
.standard-page {
|
||||
max-width: calc(100% - 430px);
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
max-width: 430px !important;
|
||||
}
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
margin-bottom: 1em;
|
||||
|
||||
button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
min-width: 80px;
|
||||
max-width: 120px;
|
||||
height: 76px;
|
||||
|
||||
.svg-icon.shield, .svg-icon.gem {
|
||||
width: 28px;
|
||||
height: auto;
|
||||
margin: 0 auto;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $gray-600;
|
||||
padding-bottom: 2em;
|
||||
|
||||
}
|
||||
|
||||
.buttons-wrapper {
|
||||
padding-top: 2.8em;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin: 2em 0;
|
||||
padding: 1em;
|
||||
|
||||
h3.leader {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.text {
|
||||
font-size: 16px;
|
||||
line-height: 1.43;
|
||||
color: $gray-50;
|
||||
}
|
||||
}
|
||||
|
||||
.guild-background {
|
||||
background-image: url('~assets/images/groups/grassy-meadow-backdrop.png');
|
||||
height: 246px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
width: 100%;
|
||||
background-color: $white;
|
||||
border: solid 1px $gray-400;
|
||||
font-size: 16px;
|
||||
line-height: 1.43;
|
||||
color: $gray-300;
|
||||
padding: .5em;
|
||||
}
|
||||
|
||||
textarea::-webkit-input-placeholder { /* Chrome/Opera/Safari */
|
||||
font-style: italic;
|
||||
}
|
||||
textarea::-moz-placeholder { /* Firefox 19+ */
|
||||
font-style: italic;
|
||||
}
|
||||
textarea:-ms-input-placeholder { /* IE 10+ */
|
||||
font-style: italic;
|
||||
}
|
||||
textarea:-moz-placeholder { /* Firefox 18- */
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.title-details {
|
||||
padding-top: 1em;
|
||||
padding-left: 1em;
|
||||
}
|
||||
|
||||
.icon-row {
|
||||
margin-top: 1em;
|
||||
|
||||
.number {
|
||||
font-size: 22px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-row {
|
||||
margin-top: 2em;
|
||||
|
||||
.new-message-row {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.chat-actions {
|
||||
margin-top: 1em;
|
||||
|
||||
.chat-receive-actions {
|
||||
padding-left: 0;
|
||||
|
||||
button {
|
||||
margin-bottom: 1em;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-send-actions {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span.action {
|
||||
font-size: 14px;
|
||||
line-height: 1.33;
|
||||
color: $gray-200;
|
||||
font-weight: 500;
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
span.action .icon {
|
||||
margin-right: .3em;
|
||||
}
|
||||
|
||||
.hr {
|
||||
width: 100%;
|
||||
height: 20px;
|
||||
border-bottom: 1px solid $gray-500;
|
||||
text-align: center;
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
.no-notifications {
|
||||
border-radius: 4px;
|
||||
border: solid 1px $orange-10;
|
||||
padding: 1em;
|
||||
margin-top: 3em;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
color: $orange-50;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// @TODO: Break this down into components
|
||||
|
||||
import extend from 'lodash/extend';
|
||||
import groupUtilities from 'client/mixins/groupsUtilities';
|
||||
import styleHelper from 'client/mixins/styleHelper';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
import membersModal from './membersModal';
|
||||
import startQuestModal from './startQuestModal';
|
||||
import questDetailsModal from './questDetailsModal';
|
||||
import participantListModal from './participantListModal';
|
||||
import groupFormModal from './groupFormModal';
|
||||
import groupChallenges from '../challenges/groupChallenges';
|
||||
import groupGemsModal from 'client/components/groups/groupGemsModal';
|
||||
import questSidebarSection from 'client/components/groups/questSidebarSection';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import chat from './chat';
|
||||
import sidebarSection from '../sidebarSection';
|
||||
import userLink from '../userLink';
|
||||
|
||||
import deleteIcon from 'assets/svg/delete.svg';
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import likeIcon from 'assets/svg/like.svg';
|
||||
import likedIcon from 'assets/svg/liked.svg';
|
||||
import reportIcon from 'assets/svg/report.svg';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import questIcon from 'assets/svg/quest.svg';
|
||||
import questBackground from 'assets/svg/quest-background-border.svg';
|
||||
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge-small.svg';
|
||||
import silverGuildBadgeIcon from 'assets/svg/silver-guild-badge-small.svg';
|
||||
import bronzeGuildBadgeIcon from 'assets/svg/bronze-guild-badge-small.svg';
|
||||
|
||||
export default {
|
||||
mixins: [groupUtilities, styleHelper],
|
||||
props: ['groupId'],
|
||||
components: {
|
||||
membersModal,
|
||||
startQuestModal,
|
||||
groupFormModal,
|
||||
groupChallenges,
|
||||
questDetailsModal,
|
||||
participantListModal,
|
||||
groupGemsModal,
|
||||
questSidebarSection,
|
||||
sidebarSection,
|
||||
userLink,
|
||||
chat,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
searchId: '',
|
||||
group: {},
|
||||
icons: Object.freeze({
|
||||
like: likeIcon,
|
||||
copy: copyIcon,
|
||||
report: reportIcon,
|
||||
delete: deleteIcon,
|
||||
gem: gemIcon,
|
||||
liked: likedIcon,
|
||||
questIcon,
|
||||
questBackground,
|
||||
goldGuildBadgeIcon,
|
||||
silverGuildBadgeIcon,
|
||||
bronzeGuildBadgeIcon,
|
||||
}),
|
||||
members: [],
|
||||
selectedQuest: {},
|
||||
chat: {
|
||||
submitDisable: false,
|
||||
submitTimeout: null,
|
||||
},
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
partyStore () {
|
||||
return this.$store.state.party;
|
||||
},
|
||||
isParty () {
|
||||
return this.$route.path.startsWith('/party');
|
||||
},
|
||||
isLeader () {
|
||||
return this.user._id === this.group.leader._id;
|
||||
},
|
||||
isAdmin () {
|
||||
return Boolean(this.user.contributor.admin);
|
||||
},
|
||||
isMember () {
|
||||
return this.isMemberOfGroup(this.user, this.group);
|
||||
},
|
||||
showNoNotificationsMessage () {
|
||||
return this.group.memberCount > this.$store.state.constants.LARGE_GROUP_COUNT_MESSAGE_CUTOFF;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
if (this.isParty) this.searchId = 'party';
|
||||
if (!this.searchId) this.searchId = this.groupId;
|
||||
this.load();
|
||||
},
|
||||
beforeRouteUpdate (to, from, next) {
|
||||
this.$set(this, 'searchId', to.params.groupId);
|
||||
|
||||
next();
|
||||
},
|
||||
watch: {
|
||||
// call again the method if the route changes (when this route is already active)
|
||||
$route: 'fetchGuild',
|
||||
partyStore () {
|
||||
if (this.$store.state.party._id) {
|
||||
this.group = this.$store.state.party;
|
||||
} else {
|
||||
this.group = null;
|
||||
this.$router.push('/');
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
acceptCommunityGuidelines () {
|
||||
this.$store.dispatch('user:set', {'flags.communityGuidelinesAccepted': true});
|
||||
},
|
||||
async 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
|
||||
// Load invites
|
||||
}
|
||||
await this.fetchGuild();
|
||||
// Fetch group members on load
|
||||
this.members = await this.loadMembers({
|
||||
groupId: this.group._id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
this.$root.$on('updatedGroup', group => {
|
||||
let updatedGroup = extend(this.group, group);
|
||||
this.$set(this.group, updatedGroup);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Method for loading members of a group, with optional parameters for
|
||||
* modifying requests.
|
||||
*
|
||||
* @param {Object} payload Used for modifying requests for members
|
||||
*/
|
||||
loadMembers (payload = null) {
|
||||
// Remove unnecessary data
|
||||
if (payload && payload.challengeId) {
|
||||
delete payload.challengeId;
|
||||
}
|
||||
|
||||
return this.$store.dispatch('members:getGroupMembers', payload);
|
||||
},
|
||||
showMemberModal () {
|
||||
this.$root.$emit('habitica:show-member-modal', {
|
||||
groupId: this.group._id,
|
||||
group: this.group,
|
||||
memberCount: this.group.memberCount,
|
||||
viewingMembers: this.members,
|
||||
fetchMoreMembers: this.loadMembers,
|
||||
});
|
||||
},
|
||||
fetchRecentMessages () {
|
||||
this.fetchGuild();
|
||||
},
|
||||
updateGuild () {
|
||||
this.$store.state.editingGroup = this.group;
|
||||
this.$root.$emit('bv::show::modal', 'guild-form');
|
||||
},
|
||||
showInviteModal () {
|
||||
this.$root.$emit('inviteModal::inviteToGroup', this.group); // This event listener is initiated in ../header/index.vue
|
||||
},
|
||||
async fetchGuild () {
|
||||
if (this.searchId === 'party' && !this.user.party._id) {
|
||||
this.$root.$emit('bv::show::modal', 'create-party-modal');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.isParty) {
|
||||
await this.$store.dispatch('party:getParty', true);
|
||||
this.group = this.$store.state.party.data;
|
||||
this.checkForAchievements();
|
||||
} else {
|
||||
const group = await this.$store.dispatch('guilds:getGroup', {groupId: this.searchId});
|
||||
this.$set(this, 'group', group);
|
||||
}
|
||||
|
||||
const groupId = this.searchId === 'party' ? this.user.party._id : this.searchId;
|
||||
if (this.hasUnreadMessages(groupId)) {
|
||||
// Delay by 1sec to make sure it returns after other requests that don't have the notification marked as read
|
||||
setTimeout(() => {
|
||||
this.$store.dispatch('chat:markChatSeen', {groupId});
|
||||
this.$delete(this.user.newMessages, groupId);
|
||||
}, 1000);
|
||||
}
|
||||
},
|
||||
hasUnreadMessages (groupId) {
|
||||
if (this.user.newMessages[groupId]) return true;
|
||||
|
||||
return this.user.notifications.some(n => {
|
||||
return n.type === 'NEW_CHAT_MESSAGE' && n.data.group.id === groupId;
|
||||
});
|
||||
},
|
||||
checkForAchievements () {
|
||||
// Checks if user's party has reached 2 players for the first time.
|
||||
if (!this.user.achievements.partyUp && this.group.memberCount >= 2) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyUp':true});
|
||||
// Achievement.displayAchievement('partyUp');
|
||||
}
|
||||
|
||||
// Checks if user's party has reached 4 players for the first time.
|
||||
if (!this.user.achievements.partyOn && this.group.memberCount >= 4) {
|
||||
// @TODO
|
||||
// User.set({'achievements.partyOn':true});
|
||||
// Achievement.displayAchievement('partyOn');
|
||||
}
|
||||
},
|
||||
async join () {
|
||||
if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
|
||||
return;
|
||||
}
|
||||
await this.$store.dispatch('guilds:join', {groupId: this.group._id, type: 'guild'});
|
||||
},
|
||||
clickLeave () {
|
||||
Analytics.track({
|
||||
hitType: 'event',
|
||||
eventCategory: 'button',
|
||||
eventAction: 'click',
|
||||
eventLabel: 'Leave Party',
|
||||
});
|
||||
|
||||
// @TODO: Get challenges and ask to keep or remove
|
||||
if (!confirm('Are you sure you want to leave?')) return;
|
||||
let keep = true;
|
||||
this.leave(keep);
|
||||
},
|
||||
async leave (keepTasks) {
|
||||
let keepChallenges = 'remain-in-challenges';
|
||||
|
||||
let data = {
|
||||
groupId: this.group._id,
|
||||
keep: keepTasks,
|
||||
keepChallenges,
|
||||
};
|
||||
|
||||
if (this.isParty) {
|
||||
data.type = 'party';
|
||||
Analytics.updateUser({partySize: null, partyID: null});
|
||||
this.$store.state.partyMembers = [];
|
||||
}
|
||||
|
||||
await this.$store.dispatch('guilds:leave', data);
|
||||
|
||||
if (this.isParty) {
|
||||
this.$router.push({name: 'tasks'});
|
||||
}
|
||||
},
|
||||
upgradeGroup () {
|
||||
this.$store.state.upgradingGroup = this.group;
|
||||
this.$router.push('/group-plans');
|
||||
},
|
||||
showGroupGems () {
|
||||
this.$root.$emit('bv::show::modal', 'group-gems-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,454 @@
|
||||
<template lang="pug">
|
||||
b-modal#guild-form(:title="title", :hide-footer="true", size='lg')
|
||||
form(@submit.stop.prevent="submit")
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('name')}} *
|
||||
b-form-input(type="text", :placeholder="$t('newGuildPlaceholder')", v-model="workingGroup.name")
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('privacySettings')}} *
|
||||
br
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input#onlyLeaderCreatesChallenges(type="checkbox", v-model="workingGroup.onlyLeaderCreatesChallenges")
|
||||
label.custom-control-label(v-once, for="onlyLeaderCreatesChallenges") {{ $t('onlyLeaderCreatesChallenges') }}
|
||||
#groupPrivateDescription1.icon(:title="$t('privateDescription')")
|
||||
.svg-icon(v-html='icons.information')
|
||||
b-tooltip(
|
||||
:title="$t('onlyLeaderCreatesChallengesDetail')",
|
||||
target="groupPrivateDescription1",
|
||||
)
|
||||
|
||||
// br
|
||||
// @TODO Implement in V2 .custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", v-model="workingGroup.guildLeaderCantBeMessaged")
|
||||
label.custom-control-label(v-once) {{ $t('guildLeaderCantBeMessaged') }}
|
||||
// "guildLeaderCantBeMessaged": "Leader can not be messaged directly",
|
||||
// @TODO discuss the impact of this with moderators before implementing
|
||||
|
||||
br
|
||||
.custom-control.custom-checkbox(v-if='!isParty && !this.workingGroup.id')
|
||||
input.custom-control-input#privateGuild(type="checkbox", v-model="workingGroup.privateGuild")
|
||||
label.custom-control-label(v-once, for="privateGuild") {{ $t('privateGuild') }}
|
||||
#groupPrivateDescription2.icon(:title="$t('privateDescription')")
|
||||
.svg-icon(v-html='icons.information')
|
||||
b-tooltip(
|
||||
:title="$t('privateDescription')",
|
||||
target="groupPrivateDescription2",
|
||||
)
|
||||
|
||||
// br
|
||||
// @TODO: Implement in v2 .custom-control.custom-checkbox(v-if='!creatingParty')
|
||||
input.custom-control-input(type="checkbox", v-model="workingGroup.allowGuildInvitationsFromNonMembers")
|
||||
label.custom-control-label(v-once) {{ $t('allowGuildInvitationsFromNonMembers') }}
|
||||
// "allowGuildInvitationsFromNonMembers": "Allow Guild invitations from non-members",
|
||||
|
||||
.form-group(v-if='!isParty')
|
||||
label
|
||||
strong(v-once) {{$t('guildSummary')}} *
|
||||
div.summary-count {{ $t('charactersRemaining', {characters: charactersRemaining}) }}
|
||||
textarea.form-control.summary-textarea(:placeholder="isParty ? $t('partyDescriptionPlaceholder') : $t('guildSummaryPlaceholder')", v-model="workingGroup.summary")
|
||||
// @TODO: need summary only for PUBLIC GUILDS, not for tavern, private guilds, or party
|
||||
|
||||
.form-group
|
||||
label
|
||||
strong(v-once) {{$t('groupDescription')}} *
|
||||
a.float-right(v-markdown='$t("markdownFormattingHelp")')
|
||||
textarea.form-control.description-textarea(type="text", textarea, :placeholder="isParty ? $t('partyDescriptionPlaceholder') : $t('guildDescriptionPlaceholder')", v-model="workingGroup.description")
|
||||
|
||||
.form-group(v-if='creatingParty && !workingGroup.id')
|
||||
span
|
||||
toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers')
|
||||
|
||||
.form-group(style='position: relative;', v-if='!creatingParty && !isParty')
|
||||
label
|
||||
strong(v-once) {{$t('categories')}} *
|
||||
div.category-wrap(@click.prevent="toggleCategorySelect")
|
||||
span.category-select(v-if='workingGroup.categories.length === 0') {{$t('none')}}
|
||||
.category-label(v-for='category in workingGroup.categories') {{$t(categoriesHashByKey[category])}}
|
||||
.category-box(v-if="showCategorySelect")
|
||||
.form-check(
|
||||
v-for="group in categoryOptions",
|
||||
:key="group.key",
|
||||
v-if='group.key !== "habitica_official" || user.contributor.admin'
|
||||
)
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(:id="`category-${group.key}`", type="checkbox", :value="group.key", v-model="workingGroup.categories")
|
||||
label.custom-control-label(v-once, :for="`category-${group.key}`") {{ $t(group.label) }}
|
||||
button.btn.btn-primary(@click.prevent="toggleCategorySelect") {{$t('close')}}
|
||||
// @TODO: need categories only for PUBLIC GUILDS, not for tavern, private guilds, or party
|
||||
|
||||
.form-group(v-if='inviteMembers && !workingGroup.id')
|
||||
label
|
||||
strong(v-once) Invite via Email or User ID
|
||||
p(v-once) {{$t('inviteMembersHowTo')}} *
|
||||
|
||||
div
|
||||
div(v-for='(member, index) in membersToInvite')
|
||||
input(type='text', v-model='member.value')
|
||||
button(@click.prevent='removeMemberToInvite(index)') Remove
|
||||
div
|
||||
input(type='text', placeholder='Email address or User ID', v-model='newMemberToInvite.value')
|
||||
button(@click.prevent='addMemberToInvite()') Add
|
||||
|
||||
.form-group.text-center
|
||||
div.item-with-icon(v-if='!this.workingGroup.id')
|
||||
.svg-icon(v-html="icons.gem")
|
||||
span.count 4
|
||||
button.btn.btn-primary.btn-md(v-if='!workingGroup.id', :disabled='!workingGroup.name || !workingGroup.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }}
|
||||
button.btn.btn-primary.btn-md(v-if='workingGroup.id', :disabled='!workingGroup.name || !workingGroup.description') {{ isParty ? $t('updateParty') : $t('updateGuild') }}
|
||||
.gem-description(v-once, v-if='!this.workingGroup.id') {{ $t('guildGemCostInfo') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.custom-control-input {
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.summary-count, .gem-description {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
margin-top: 1em;
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.summary-count {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.gem-description {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.summary-textarea {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.description-textarea {
|
||||
height: 220px;
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
display: inline-block;
|
||||
|
||||
img {
|
||||
height: 20px;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.count {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-right: 1em;
|
||||
color: $green-10;
|
||||
}
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: .5em;
|
||||
display: inline-block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import toggleSwitch from 'client/components/ui/toggleSwitch';
|
||||
import groupMemberSearchDropdown from 'client/components/members/groupMemberSearchDropdown';
|
||||
import markdownDirective from 'client/directives/markdown';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import informationIcon from 'assets/svg/information.svg';
|
||||
|
||||
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from '../../../common/script/constants';
|
||||
|
||||
// @TODO: Not sure the best way to pass party creating status
|
||||
// Since we need the modal in the header, passing props doesn't work
|
||||
// because we can't import the create group in the index of groups
|
||||
// I think state is the correct spot, but maybe we should separate into
|
||||
// two modals?
|
||||
|
||||
export default {
|
||||
components: {
|
||||
toggleSwitch,
|
||||
groupMemberSearchDropdown,
|
||||
},
|
||||
directives: {
|
||||
markdown: markdownDirective,
|
||||
},
|
||||
data () {
|
||||
let data = {
|
||||
workingGroup: {
|
||||
id: '',
|
||||
name: '',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
summary: '',
|
||||
description: '',
|
||||
categories: [],
|
||||
onlyLeaderCreatesChallenges: true,
|
||||
guildLeaderCantBeMessaged: true,
|
||||
privateGuild: true,
|
||||
allowGuildInvitationsFromNonMembers: true,
|
||||
},
|
||||
categoryOptions: [
|
||||
{
|
||||
label: 'habitica_official',
|
||||
key: 'habitica_official',
|
||||
},
|
||||
{
|
||||
label: 'academics',
|
||||
key: 'academics',
|
||||
},
|
||||
{
|
||||
label: 'advocacy_causes',
|
||||
key: 'advocacy_causes',
|
||||
},
|
||||
{
|
||||
label: 'creativity',
|
||||
key: 'creativity',
|
||||
},
|
||||
{
|
||||
label: 'entertainment',
|
||||
key: 'entertainment',
|
||||
},
|
||||
{
|
||||
label: 'finance',
|
||||
key: 'finance',
|
||||
},
|
||||
{
|
||||
label: 'health_fitness',
|
||||
key: 'health_fitness',
|
||||
},
|
||||
{
|
||||
label: 'hobbies_occupations',
|
||||
key: 'hobbies_occupations',
|
||||
},
|
||||
{
|
||||
label: 'location_based',
|
||||
key: 'location_based',
|
||||
},
|
||||
{
|
||||
label: 'mental_health',
|
||||
key: 'mental_health',
|
||||
},
|
||||
{
|
||||
label: 'getting_organized',
|
||||
key: 'getting_organized',
|
||||
},
|
||||
{
|
||||
label: 'recovery_support_groups',
|
||||
key: 'recovery_support_groups',
|
||||
},
|
||||
{
|
||||
label: 'spirituality',
|
||||
key: 'spirituality',
|
||||
},
|
||||
{
|
||||
label: 'time_management',
|
||||
key: 'time_management',
|
||||
},
|
||||
],
|
||||
showCategorySelect: false,
|
||||
members: [],
|
||||
inviteMembers: false,
|
||||
newMemberToInvite: {
|
||||
value: '',
|
||||
type: '',
|
||||
},
|
||||
membersToInvite: [],
|
||||
};
|
||||
|
||||
let hashedCategories = {};
|
||||
data.categoryOptions.forEach((category) => {
|
||||
hashedCategories[category.key] = category.label;
|
||||
});
|
||||
data.categoriesHashByKey = hashedCategories;
|
||||
|
||||
data.icons = Object.freeze({
|
||||
gem: gemIcon,
|
||||
information: informationIcon,
|
||||
});
|
||||
|
||||
return data;
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
editingGroup () {
|
||||
return this.$store.state.editingGroup;
|
||||
},
|
||||
charactersRemaining () {
|
||||
let currentLength = this.workingGroup.summary ? this.workingGroup.summary.length : 0;
|
||||
return MAX_SUMMARY_SIZE_FOR_GUILDS - currentLength;
|
||||
},
|
||||
title () {
|
||||
if (this.creatingParty) return this.$t('createParty');
|
||||
if (!this.workingGroup._id && !this.workingGroup.id) return this.$t('createGuild');
|
||||
if (this.isParty) return this.$t('updateParty');
|
||||
return this.$t('updateGuild');
|
||||
},
|
||||
creatingParty () {
|
||||
return this.$store.state.groupFormOptions.createParty;
|
||||
},
|
||||
isParty () {
|
||||
return this.workingGroup.type === 'party';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
editingGroup () {
|
||||
let editingGroup = this.editingGroup;
|
||||
|
||||
if (!editingGroup._id) {
|
||||
this.resetWorkingGroup();
|
||||
return;
|
||||
}
|
||||
|
||||
this.workingGroup.name = editingGroup.name;
|
||||
this.workingGroup.type = editingGroup.type;
|
||||
|
||||
this.workingGroup.privateGuild = true;
|
||||
if (editingGroup.privacy === 'public') {
|
||||
this.workingGroup.privateGuild = false;
|
||||
}
|
||||
|
||||
if (editingGroup.categories) {
|
||||
editingGroup.categories.forEach(category => {
|
||||
this.workingGroup.categories.push(category.slug);
|
||||
});
|
||||
}
|
||||
|
||||
if (editingGroup.summary) this.workingGroup.summary = editingGroup.summary;
|
||||
if (editingGroup.description) this.workingGroup.description = editingGroup.description;
|
||||
if (editingGroup._id) this.workingGroup.id = editingGroup._id;
|
||||
|
||||
this.workingGroup.onlyLeaderCreatesChallenges = editingGroup.leaderOnly.challenges;
|
||||
|
||||
this.workingGroup.leader = editingGroup.leader;
|
||||
|
||||
if (editingGroup._id) this.getMembers();
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async getMembers () {
|
||||
if (!this.workingGroup.id) return;
|
||||
let members = await this.$store.dispatch('members:getGroupMembers', {
|
||||
groupId: this.workingGroup.id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
this.members = members;
|
||||
},
|
||||
addMemberToInvite () {
|
||||
// @TODO: determine type
|
||||
this.membersToInvite.push(this.newMemberToInvite);
|
||||
this.newMemberToInvite = {
|
||||
value: '',
|
||||
type: '',
|
||||
};
|
||||
},
|
||||
removeMemberToInvite (index) {
|
||||
this.membersToInvite.splice(index, 1);
|
||||
},
|
||||
toggleCategorySelect () {
|
||||
this.showCategorySelect = !this.showCategorySelect;
|
||||
},
|
||||
async submit () {
|
||||
if (this.$store.state.user.data.balance < 1 && !this.workingGroup.id) {
|
||||
// @TODO: Add proper notifications
|
||||
alert(this.$t('notEnoughGems'));
|
||||
return;
|
||||
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Gems > Create Group"});
|
||||
}
|
||||
|
||||
let errors = [];
|
||||
|
||||
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 (errors.length > 0) {
|
||||
alert(errors.join('\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
// @TODO: Add proper notifications
|
||||
if (!this.workingGroup.id && !confirm(this.$t('confirmGuild'))) return;
|
||||
|
||||
if (!this.workingGroup.privateGuild) {
|
||||
this.workingGroup.privacy = 'public';
|
||||
}
|
||||
|
||||
this.workingGroup.leaderOnly = {
|
||||
challenges: this.workingGroup.onlyLeaderCreatesChallenges,
|
||||
};
|
||||
|
||||
let categoryKeys = this.workingGroup.categories;
|
||||
let serverCategories = [];
|
||||
categoryKeys.forEach(key => {
|
||||
let catName = this.categoriesHashByKey[key];
|
||||
serverCategories.push({
|
||||
slug: key,
|
||||
name: catName,
|
||||
});
|
||||
});
|
||||
this.workingGroup.categories = serverCategories;
|
||||
|
||||
let groupData = Object.assign({}, this.workingGroup);
|
||||
|
||||
let newgroup;
|
||||
if (groupData.id) {
|
||||
await this.$store.dispatch('guilds:update', {group: groupData});
|
||||
this.$root.$emit('updatedGroup', this.workingGroup);
|
||||
// @TODO: this doesn't work because of the async resource
|
||||
// if (updatedGroup.type === 'party') this.$store.state.party = {data: updatedGroup};
|
||||
} else {
|
||||
newgroup = await this.$store.dispatch('guilds:create', {group: groupData});
|
||||
this.$store.state.user.data.balance -= 1;
|
||||
}
|
||||
|
||||
this.$store.state.editingGroup = {};
|
||||
|
||||
this.workingGroup = {
|
||||
name: '',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
description: '',
|
||||
categories: [],
|
||||
onlyLeaderCreatesChallenges: true,
|
||||
guildLeaderCantBeMessaged: true,
|
||||
privateGuild: true,
|
||||
allowGuildInvitationsFromNonMembers: true,
|
||||
};
|
||||
|
||||
if (newgroup && newgroup._id) {
|
||||
this.$router.push(`/groups/guild/${newgroup._id}`);
|
||||
}
|
||||
this.$root.$emit('bv::hide::modal', 'guild-form');
|
||||
},
|
||||
resetWorkingGroup () {
|
||||
this.workingGroup = {
|
||||
id: '',
|
||||
name: '',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
summary: '',
|
||||
description: '',
|
||||
categories: [],
|
||||
onlyLeaderCreatesChallenges: true,
|
||||
guildLeaderCantBeMessaged: true,
|
||||
privateGuild: true,
|
||||
allowGuildInvitationsFromNonMembers: true,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,27 @@
|
||||
<template lang="pug">
|
||||
b-modal#group-gems-modal(:title="$t('groupGems')", size='md', :hide-footer="true")
|
||||
.modal-body
|
||||
.row
|
||||
.col-6.offset-3
|
||||
h3 {{ $t('groupGemsDesc') }}
|
||||
.modal-footer
|
||||
.col-12.text-center
|
||||
button.btn.btn-primary(@click='close()') {{$t('close')}}
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.modal-body {
|
||||
margin-top: 1em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'group-gems-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,382 @@
|
||||
<template lang="pug">
|
||||
// @TODO: Move to group plans folder
|
||||
div
|
||||
div
|
||||
.header
|
||||
h1.text-center Need more for your Group?
|
||||
.row
|
||||
.col-8.offset-2.text-center
|
||||
h2.sub-text {{ $t('groupBenefitsDescription') }}
|
||||
.container.benefits
|
||||
.row
|
||||
.col-4
|
||||
.box
|
||||
img.box1(src='~client/assets/images/group-plans/group-14@3x.png')
|
||||
hr
|
||||
h2 {{ $t('teamBasedTasks') }}
|
||||
p Set up an easily-viewed shared task list for the group. Assign tasks to your fellow group members, or let them claim their own tasks to make it clear what everyone is working on!
|
||||
|
||||
.col-4
|
||||
.box
|
||||
img.box2(src='~client/assets/images/group-plans/group-12@3x.png')
|
||||
hr
|
||||
h2 Group Management Controls
|
||||
p Use task approvals to verify that a task that was really completed, add Group Managers to share responsibilities, and enjoy a private group chat for all team members.
|
||||
|
||||
.col-4
|
||||
.box
|
||||
img.box3(src='~client/assets/images/group-plans/group-13@3x.png')
|
||||
hr
|
||||
h2 In-Game Benefits
|
||||
p Group members get an exclusive Jackalope Mount, as well as full subscription benefits, including special monthly equipment sets and the ability to buy gems with gold.
|
||||
|
||||
#upgrading-group.container.payment-options(v-if='upgradingGroup._id')
|
||||
h1.text-center.purple-header Are you ready to upgrade?
|
||||
.row
|
||||
.col-12.text-center
|
||||
.purple-box
|
||||
.amount-section
|
||||
.dollar $
|
||||
.number 9
|
||||
.name Group Owner Subscription
|
||||
.plus
|
||||
.svg-icon(v-html="icons.positiveIcon")
|
||||
.amount-section
|
||||
.dollar $
|
||||
.number 3
|
||||
.name Each Individual Group Member
|
||||
|
||||
.box.payment-providers
|
||||
h3 Choose your payment method
|
||||
.payments-column
|
||||
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
|
||||
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
|
||||
| {{ $t('card') }}
|
||||
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
|
||||
.container.col-6.offset-3.create-option(v-if='!upgradingGroup._id')
|
||||
.row
|
||||
h1.col-12.text-center.purple-header Create your Group today!
|
||||
.row
|
||||
.col-12.text-center
|
||||
button.btn.btn-primary.create-group(@click='launchModal("create")') Create Your New Group
|
||||
.row.pricing
|
||||
.col-5
|
||||
.dollar $
|
||||
.number 9
|
||||
.name
|
||||
div Group Owner
|
||||
div Subscription
|
||||
.col-1
|
||||
.plus +
|
||||
.col-6
|
||||
.dollar $
|
||||
.number 3
|
||||
.name
|
||||
div Each Additional
|
||||
div Member
|
||||
|
||||
b-modal#group-plan-modal(title="Select Payment", size='md', hide-footer=true)
|
||||
.col-12(v-if='activePage === PAGES.CREATE_GROUP')
|
||||
.form-group
|
||||
label.control-label(for='new-group-name') Name
|
||||
input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder="Name", v-model='newGroup.name')
|
||||
.form-group
|
||||
label(for='new-group-description') {{ $t('description') }}
|
||||
textarea.form-control#new-group-description.option-content(cols='3', :placeholder="$t('description')", v-model='newGroup.description')
|
||||
.form-group(v-if='type === "guild"')
|
||||
.custom-control.custom-radio
|
||||
input.custom-control-input(type='radio', name='new-group-privacy', value='private', v-model='newGroup.privacy')
|
||||
label.custom-control-label {{ $t('inviteOnly') }}
|
||||
.form-group
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(type='checkbox', v-model='newGroup.leaderOnly.challenges' id='create-group-leaderOnlyChallenges-checkbox')
|
||||
label.custom-control-label(for='create-group-leaderOnlyChallenges-checkbox') {{ $t('leaderOnlyChallenges') }}
|
||||
.form-group(v-if='type === "party"')
|
||||
button.btn.btn-secondary.form-control(@click='createGroup()', :value="$t('createGroupPlan')")
|
||||
.form-group
|
||||
button.btn.btn-primary.btn-lg.btn-block(@click="createGroup()", :disabled="!newGroupIsReady") {{ $t('createGroupPlan') }}
|
||||
.col-12(v-if='activePage === PAGES.PAY')
|
||||
.text-center
|
||||
h3 Choose your payment method
|
||||
.payments-column.mx-auto
|
||||
button.purchase.btn.btn-primary.payment-button.payment-item(@click='pay(PAYMENTS.STRIPE)')
|
||||
.svg-icon.credit-card-icon(v-html="icons.creditCardIcon")
|
||||
| {{ $t('card') }}
|
||||
amazon-button.payment-item(:amazon-data="pay(PAYMENTS.AMAZON)")
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
#upgrading-group {
|
||||
.amount-section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dollar {
|
||||
position: absolute;
|
||||
left: -1em;
|
||||
top: 1em;
|
||||
}
|
||||
|
||||
.purple-box {
|
||||
color: #bda8ff;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.plus .svg-icon{
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.payment-providers {
|
||||
width: 350px;
|
||||
}
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #432874;
|
||||
background: linear-gradient(180deg, #4F2A93 0%, #432874 100%);
|
||||
color: #fff;
|
||||
padding: 2em;
|
||||
height: 340px;
|
||||
margin-bottom: 2em;
|
||||
margin-left: -12px;
|
||||
margin-right: -12px;
|
||||
|
||||
h1 {
|
||||
font-size: 48px;
|
||||
line-height: 1.16;
|
||||
margin-top: 12px;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
h2.sub-text {
|
||||
color: #D5C8FF;
|
||||
font-size: 24px;
|
||||
font-weight: 400;
|
||||
line-height: 1.33;
|
||||
}
|
||||
}
|
||||
|
||||
.benefits {
|
||||
margin-top: -10em;
|
||||
|
||||
.box {
|
||||
height: 416px;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #6133b4;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
border-radius: 2px;
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
padding: 2em;
|
||||
text-align: center;
|
||||
display: inline-block !important;
|
||||
vertical-align: bottom;
|
||||
margin-right: 1em;
|
||||
|
||||
img {
|
||||
margin: 0 auto;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
img.box1 {
|
||||
width: 266px;
|
||||
}
|
||||
|
||||
img.box2 {
|
||||
margin-top: 3.5em;
|
||||
width: 262px;
|
||||
margin-bottom: 3.7em;
|
||||
}
|
||||
|
||||
img.box3 {
|
||||
width: 225px;
|
||||
margin-bottom: 3.0em;
|
||||
}
|
||||
|
||||
button.create-group {
|
||||
width: 330px;
|
||||
height: 96px;
|
||||
}
|
||||
|
||||
.purple-header {
|
||||
color: #6133b4;
|
||||
font-size: 48px;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.pricing {
|
||||
margin-top: 2em;
|
||||
margin-bottom: 4em;
|
||||
|
||||
.dollar, .number, .name {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
.plus {
|
||||
font-size: 34px;
|
||||
color: #a5a1ac;
|
||||
}
|
||||
|
||||
.dollar {
|
||||
margin-bottom: 1.5em;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.name {
|
||||
font-size: 24px;
|
||||
margin-bottom: .8em;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 72px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
.payment-options {
|
||||
margin-bottom: 4em;
|
||||
|
||||
.purple-box {
|
||||
background-color: #4f2a93;
|
||||
color: #fff;
|
||||
padding: .5em;
|
||||
border-radius: 8px;
|
||||
width: 200px;
|
||||
height: 215px;
|
||||
|
||||
.dollar {
|
||||
}
|
||||
|
||||
.number {
|
||||
font-size: 60px;
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 100px;
|
||||
margin-left: .3em;
|
||||
}
|
||||
|
||||
.plus {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.box, .purple-box {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import paymentsMixin from '../../mixins/payments';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
import creditCardIcon from 'assets/svg/credit-card-icon.svg';
|
||||
import amazonButton from 'client/components/payments/amazonButton';
|
||||
|
||||
export default {
|
||||
mixins: [paymentsMixin],
|
||||
components: {
|
||||
amazonButton,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
amazonPayments: {},
|
||||
icons: Object.freeze({
|
||||
creditCardIcon,
|
||||
positiveIcon,
|
||||
}),
|
||||
PAGES: {
|
||||
CREATE_GROUP: 'create-group',
|
||||
UPGRADE_GROUP: 'upgrade-group',
|
||||
PAY: 'pay',
|
||||
},
|
||||
PAYMENTS: {
|
||||
AMAZON: 'amazon',
|
||||
STRIPE: 'stripe',
|
||||
},
|
||||
paymentMethod: '',
|
||||
newGroup: {
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
name: '',
|
||||
leaderOnly: {
|
||||
challenges: false,
|
||||
},
|
||||
},
|
||||
activePage: '',
|
||||
type: 'guild', // Guild or Party @TODO enum this
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.activePage = this.PAGES.BENEFITS;
|
||||
},
|
||||
computed: {
|
||||
newGroupIsReady () {
|
||||
return Boolean(this.newGroup.name);
|
||||
},
|
||||
upgradingGroup () {
|
||||
return this.$store.state.upgradingGroup;
|
||||
},
|
||||
// @TODO: can we move this to payment mixin?
|
||||
...mapState({user: 'user.data'}),
|
||||
},
|
||||
methods: {
|
||||
launchModal () {
|
||||
this.changePage(this.PAGES.CREATE_GROUP);
|
||||
this.$root.$emit('bv::show::modal', 'group-plan-modal');
|
||||
},
|
||||
changePage (page) {
|
||||
this.activePage = page;
|
||||
window.scrollTo(0, 0);
|
||||
},
|
||||
createGroup () {
|
||||
this.changePage(this.PAGES.PAY);
|
||||
},
|
||||
pay (paymentMethod) {
|
||||
const subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
||||
const paymentData = {
|
||||
subscription: subscriptionKey,
|
||||
coupon: null,
|
||||
};
|
||||
|
||||
if (this.upgradingGroup && this.upgradingGroup._id) {
|
||||
paymentData.groupId = this.upgradingGroup._id;
|
||||
paymentData.group = this.upgradingGroup;
|
||||
} else {
|
||||
paymentData.groupToCreate = this.newGroup;
|
||||
}
|
||||
|
||||
this.paymentMethod = paymentMethod;
|
||||
if (this.paymentMethod === this.PAYMENTS.STRIPE) {
|
||||
this.showStripe(paymentData);
|
||||
} else if (this.paymentMethod === this.PAYMENTS.AMAZON) {
|
||||
paymentData.type = 'subscription';
|
||||
return paymentData;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,24 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
secondary-menu.col-12
|
||||
router-link.nav-link(:to="{name: 'tavern'}", exact, :class="{'active': $route.name === 'tavern'}") {{ $t('tavern') }}
|
||||
router-link.nav-link(:to="{name: 'myGuilds'}", :class="{'active': $route.name === 'myGuilds'}") {{ $t('myGuilds') }}
|
||||
router-link.nav-link(:to="{name: 'guildsDiscovery'}", :class="{'active': $route.name === 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
|
||||
|
||||
.col-12
|
||||
router-view
|
||||
|
||||
group-form-modal
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import groupFormModal from './groupFormModal';
|
||||
import SecondaryMenu from 'client/components/secondaryMenu';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
groupFormModal,
|
||||
SecondaryMenu,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,199 @@
|
||||
<template lang="pug">
|
||||
b-modal#invite-modal(:title='$t(`inviteTo${groupType}`)', :hide-footer='true')
|
||||
div
|
||||
strong {{ $t('inviteEmailUsername') }}
|
||||
.small {{ $t('inviteEmailUsernameInfo') }}
|
||||
div(v-for='(invite, index) in invites')
|
||||
.input-group
|
||||
.d-flex.align-items-center.justify-content-center(v-if='index === invites.length - 1 && invite.text.length === 0')
|
||||
.svg-icon.positive-icon(v-html='icons.positiveIcon')
|
||||
input.form-control(
|
||||
type='text',
|
||||
:placeholder='$t("emailOrUsernameInvite")',
|
||||
v-model='invite.text',
|
||||
v-on:keyup='expandInviteList',
|
||||
v-on:change='checkInviteList',
|
||||
:class='{"input-valid": invite.valid, "is-invalid input-invalid": invite.valid === false}',
|
||||
)
|
||||
.input-error.text-center.mt-2(v-if="invite.error") {{ invite.error }}
|
||||
.modal-footer.d-flex.justify-content-center
|
||||
a.mr-3(@click='close()') {{ $t('cancel') }}
|
||||
button.btn.btn-primary(@click='sendInvites()', :class='{disabled: cannotSubmit}', :disabled='cannotSubmit') {{ $t('sendInvitations') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
#invite-modal___BV_modal_outer_ {
|
||||
.modal-content {
|
||||
padding: 0rem 0.25rem;
|
||||
}
|
||||
}
|
||||
#invite-modal___BV_modal_header_.modal-header {
|
||||
border-bottom: 0px;
|
||||
}
|
||||
#invite-modal___BV_modal_header_ {
|
||||
.modal-title {
|
||||
color: #4F2A93;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
a:not([href]) {
|
||||
color: $blue-10;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
border: 0px;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.input-error {
|
||||
color: $red-50;
|
||||
font-size: 90%;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
border-radius: 2px;
|
||||
border: solid 1px $gray-400;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: $gray-200;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.input-group:focus-within {
|
||||
border-color: $purple-500;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
border: 0px;
|
||||
}
|
||||
|
||||
.positive-icon {
|
||||
color: $green-10;
|
||||
width: 10px;
|
||||
margin: auto 0rem auto 1rem;
|
||||
}
|
||||
|
||||
.small {
|
||||
color: $gray-200;
|
||||
font-size: 12px;
|
||||
margin: 0.5rem 0rem 1rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import clone from 'lodash/clone';
|
||||
import debounce from 'lodash/debounce';
|
||||
import filter from 'lodash/filter';
|
||||
import forEach from 'lodash/forEach';
|
||||
import isEmail from 'validator/lib/isEmail';
|
||||
import isUUID from 'validator/lib/isUUID';
|
||||
import notifications from 'client/mixins/notifications';
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
const INVITE_DEFAULTS = {text: '', error: null, valid: null};
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
cannotSubmit () {
|
||||
const filteredInvites = filter(this.invites, (invite) => {
|
||||
return invite.text.length > 0 && !invite.valid;
|
||||
});
|
||||
if (filteredInvites.length > 0) return true;
|
||||
},
|
||||
inviter () {
|
||||
return this.user.profile.name;
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
invites: [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)],
|
||||
icons: Object.freeze({
|
||||
positiveIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
checkInviteList: debounce(function checkList () {
|
||||
this.invites = filter(this.invites, (invite, index) => {
|
||||
return invite.text.length > 0 || index === this.invites.length - 1;
|
||||
});
|
||||
while (this.invites.length < 2) this.invites.push(clone(INVITE_DEFAULTS));
|
||||
forEach(this.invites, (value, index) => {
|
||||
if (value.text.length < 1 || isEmail(value.text)) {
|
||||
return this.fillErrors(index);
|
||||
}
|
||||
if (isUUID(value.text)) {
|
||||
this.$store.dispatch('user:userLookup', {uuid: value.text})
|
||||
.then(res => {
|
||||
return this.fillErrors(index, res);
|
||||
});
|
||||
} else {
|
||||
let searchUsername = value.text;
|
||||
if (searchUsername[0] === '@') searchUsername = searchUsername.slice(1, searchUsername.length);
|
||||
this.$store.dispatch('user:userLookup', {username: searchUsername})
|
||||
.then(res => {
|
||||
return this.fillErrors(index, res);
|
||||
});
|
||||
}
|
||||
});
|
||||
}, 250),
|
||||
expandInviteList () {
|
||||
if (this.invites[this.invites.length - 1].text.length > 0) this.invites.push(clone(INVITE_DEFAULTS));
|
||||
},
|
||||
fillErrors (index, res) {
|
||||
if (!res || res.status === 200) {
|
||||
this.invites[index].error = null;
|
||||
if (this.invites[index].text.length < 1) return this.invites[index].valid = null;
|
||||
return this.invites[index].valid = true;
|
||||
}
|
||||
this.invites[index].error = res.response.data.message;
|
||||
return this.invites[index].valid = false;
|
||||
},
|
||||
close () {
|
||||
this.invites = [clone(INVITE_DEFAULTS), clone(INVITE_DEFAULTS)];
|
||||
this.$root.$emit('bv::hide::modal', 'invite-modal');
|
||||
},
|
||||
async sendInvites () {
|
||||
let invitationDetails = {
|
||||
inviter: this.inviter,
|
||||
emails: [],
|
||||
uuids: [],
|
||||
usernames: [],
|
||||
};
|
||||
forEach(this.invites, (invite) => {
|
||||
if (invite.text.length < 1) return;
|
||||
if (isEmail(invite.text)) {
|
||||
invitationDetails.emails.push({email: invite.text});
|
||||
} else if (isUUID(invite.text)) {
|
||||
invitationDetails.uuids.push(invite.text);
|
||||
} else {
|
||||
invitationDetails.usernames.push(invite.text);
|
||||
}
|
||||
});
|
||||
await this.$store.dispatch('guilds:invite', {
|
||||
invitationDetails,
|
||||
groupId: this.group._id,
|
||||
});
|
||||
|
||||
const invitesSent = invitationDetails.emails.length + invitationDetails.uuids.length + invitationDetails.usernames.length;
|
||||
let invitationString = invitesSent > 1 ? 'invitationsSent' : 'invitationSent';
|
||||
|
||||
this.text(this.$t(invitationString));
|
||||
this.close();
|
||||
},
|
||||
},
|
||||
mixins: [notifications],
|
||||
props: ['group', 'groupType'],
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,515 @@
|
||||
<template lang="pug">
|
||||
// @TODO: Move this to a member directory
|
||||
div
|
||||
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
|
||||
h1(v-once) {{$t('members')}}
|
||||
.col-6
|
||||
button(type="button" aria-label="Close" class="close", @click='close()')
|
||||
span(aria-hidden="true") ×
|
||||
.row.d-flex.align-items-center
|
||||
.col-4
|
||||
input.form-control.input-search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
.col
|
||||
select.form-control(@change='changeSortOption($event)')
|
||||
option(v-for='sortOption in sortOptions', :value='sortOption.value') {{sortOption.text}}
|
||||
.col-3
|
||||
select.form-control(@change='changeSortDirection($event)')
|
||||
option(v-for='sortDirection in sortDirections', :value='sortDirection.value') {{sortDirection.text}}
|
||||
|
||||
.row.apply-options.d-flex.justify-content-center(v-if='sortDirty && group.type === "party"')
|
||||
a(@click='applySortOptions()') {{ $t('applySortToHeader') }}
|
||||
.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='sendMessage(member)')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.messageIcon")
|
||||
span.text {{$t('sendMessage')}}
|
||||
b-dropdown-item(@click='promoteToLeader(member)', v-if='shouldShowLeaderFunctions(member._id)')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.starIcon")
|
||||
span.text {{$t('promoteToLeader')}}
|
||||
b-dropdown-item(@click='addManager(member._id)', v-if='shouldShowAddManager(member._id)')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.starIcon")
|
||||
span.text {{$t('addManager')}}
|
||||
b-dropdown-item(@click='removeManager(member._id)', v-if='shouldShowRemoveManager(member._id)')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.removeIcon")
|
||||
span.text {{$t('removeManager2')}}
|
||||
b-dropdown-item(@click='viewProgress(member)', v-if='challengeId')
|
||||
span.dropdown-icon-item
|
||||
span.text {{ $t('viewProgress') }}
|
||||
b-dropdown-item(@click='removeMember(member, index)', v-if='shouldShowLeaderFunctions(member._id)')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.removeIcon")
|
||||
span.text {{$t('removeMember')}}
|
||||
.row(v-if='isLoadMoreAvailable')
|
||||
.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, index) in invites')
|
||||
.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='removeInvite(member, index)', v-if='isLeader')
|
||||
span.dropdown-icon-item
|
||||
.svg-icon.inline(v-html="icons.removeIcon", v-if='isLeader')
|
||||
span.text {{$t('removeInvite')}}
|
||||
.modal-footer
|
||||
button.btn.btn-primary(@click='close()') {{ $t('close') }}
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
#members-modal {
|
||||
.modal-header {
|
||||
background-color: #edecee;
|
||||
border-radius: 8px 8px 0 0;
|
||||
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background-color: #edecee;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.small-text, .character-name {
|
||||
color: #878190;
|
||||
}
|
||||
|
||||
.no-padding-left {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.actions .dropdown-toggle::after {
|
||||
content: none !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.apply-options {
|
||||
padding: 1em;
|
||||
margin: 0;
|
||||
background-color: #f9f9f9;
|
||||
color: #2995cd;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.form-control {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #4f2a93;
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding-top: 5em;
|
||||
|
||||
.dots {
|
||||
height: 16px;
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.btn-group {
|
||||
margin-left: -2em;
|
||||
margin-top: -2em;
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
|
||||
#members-modal_modal_body {
|
||||
padding: 0;
|
||||
max-height: 450px;
|
||||
|
||||
.col-8 {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.member-stats {
|
||||
width: 382px;
|
||||
height: 147px;
|
||||
}
|
||||
|
||||
.gradient {
|
||||
background-image: linear-gradient(to bottom, rgba(255, 255, 255, 0), #ffffff);
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
}
|
||||
|
||||
.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>
|
||||
import orderBy from 'lodash/orderBy';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
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';
|
||||
import starIcon from 'assets/members/star.svg';
|
||||
import dots from 'assets/svg/dots.svg';
|
||||
|
||||
export default {
|
||||
props: ['hideBadge'],
|
||||
components: {
|
||||
MemberDetails,
|
||||
removeMemberModal,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
sortOption: {},
|
||||
sortDirty: false,
|
||||
selectedPage: 'members',
|
||||
members: [],
|
||||
invites: [],
|
||||
memberToRemove: {},
|
||||
sortOptions: [
|
||||
{
|
||||
value: 'stats.class',
|
||||
text: this.$t('sortClass'),
|
||||
},
|
||||
{
|
||||
value: 'preferences.background',
|
||||
text: this.$t('sortBackground'),
|
||||
},
|
||||
{
|
||||
value: 'auth.timestamps.created',
|
||||
text: this.$t('sortDateJoined'),
|
||||
},
|
||||
{
|
||||
value: 'auth.timestamps.loggedin',
|
||||
text: this.$t('sortLogin'),
|
||||
},
|
||||
{
|
||||
value: 'stats.lvl',
|
||||
text: this.$t('sortLevel'),
|
||||
},
|
||||
{
|
||||
value: 'profile.name',
|
||||
text: this.$t('sortName'),
|
||||
},
|
||||
{
|
||||
value: 'contributor.level',
|
||||
text: this.$t('sortTier'),
|
||||
},
|
||||
],
|
||||
sortDirections: [
|
||||
{
|
||||
value: 'asc',
|
||||
text: this.$t('ascendingAbbrev'),
|
||||
},
|
||||
{
|
||||
value: 'desc',
|
||||
text: this.$t('descendingAbbrev'),
|
||||
},
|
||||
],
|
||||
searchTerm: '',
|
||||
icons: Object.freeze({
|
||||
removeIcon,
|
||||
messageIcon,
|
||||
starIcon,
|
||||
dots,
|
||||
}),
|
||||
userIdToMessage: '',
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
this.$root.$on('habitica:show-member-modal', (data) => {
|
||||
// @TODO: Remove store
|
||||
this.$store.state.memberModalOptions.challengeId = data.challengeId;
|
||||
this.$store.state.memberModalOptions.groupId = data.groupId;
|
||||
this.$store.state.memberModalOptions.group = data.group;
|
||||
this.$store.state.memberModalOptions.memberCount = data.memberCount;
|
||||
this.$store.state.memberModalOptions.viewingMembers = data.viewingMembers;
|
||||
this.$store.state.memberModalOptions.fetchMoreMembers = data.fetchMoreMembers;
|
||||
this.$root.$emit('bv::show::modal', 'members-modal');
|
||||
this.getMembers();
|
||||
});
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('habitica:show-member-modal');
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
isLeader () {
|
||||
if (!this.group || !this.group.leader) return false;
|
||||
return this.user._id === this.group.leader || this.user._id === this.group.leader._id;
|
||||
},
|
||||
isAdmin () {
|
||||
return Boolean(this.user.contributor.admin);
|
||||
},
|
||||
isLoadMoreAvailable () {
|
||||
// Only available if the current length of `members` is less than the
|
||||
// total size of the Group/Challenge
|
||||
return this.members.length < this.$store.state.memberModalOptions.memberCount;
|
||||
},
|
||||
groupIsSubscribed () {
|
||||
return this.group.purchased && this.group.purchased.active;
|
||||
},
|
||||
group () {
|
||||
return this.$store.state.memberModalOptions.group;
|
||||
},
|
||||
groupId () {
|
||||
return this.$store.state.memberModalOptions.groupId || this.group._id;
|
||||
},
|
||||
challengeId () {
|
||||
return this.$store.state.memberModalOptions.challengeId;
|
||||
},
|
||||
sortedMembers () {
|
||||
let sortedMembers = this.members;
|
||||
|
||||
if (!isEmpty(this.sortOption)) {
|
||||
// Use the memberlist filtered by searchTerm
|
||||
sortedMembers = orderBy(sortedMembers, [this.sortOption.value], [this.sortOption.direction]);
|
||||
}
|
||||
|
||||
return sortedMembers;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
groupId () {
|
||||
// @TOOD: We might not need this since groupId is computed now
|
||||
this.getMembers();
|
||||
},
|
||||
challengeId () {
|
||||
this.getMembers();
|
||||
},
|
||||
group () {
|
||||
this.getMembers();
|
||||
},
|
||||
// Watches `searchTerm` and if present, performs a `searchMembers` action
|
||||
// and usual `getMembers` otherwise
|
||||
searchTerm () {
|
||||
if (this.searchTerm) {
|
||||
this.searchMembers(this.searchTerm);
|
||||
} else {
|
||||
this.getMembers();
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
sendMessage (member) {
|
||||
this.$root.$emit('habitica::new-inbox-message', {
|
||||
userIdToMessage: member._id,
|
||||
displayName: member.profile.name,
|
||||
username: member.auth.local.username,
|
||||
backer: member.backer,
|
||||
contributor: member.contributor,
|
||||
});
|
||||
},
|
||||
async searchMembers (searchTerm = '') {
|
||||
this.members = await this.$store.state.memberModalOptions.fetchMoreMembers({
|
||||
challengeId: this.challengeId,
|
||||
groupId: this.groupId,
|
||||
searchTerm,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
},
|
||||
async getMembers () {
|
||||
let groupId = this.groupId;
|
||||
|
||||
if (groupId && groupId !== 'challenge') {
|
||||
let invites = await this.$store.dispatch('members:getGroupInvites', {
|
||||
groupId,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
this.invites = invites;
|
||||
}
|
||||
|
||||
this.members = this.$store.state.memberModalOptions.viewingMembers;
|
||||
},
|
||||
async clickMember (uid, forceShow) {
|
||||
let user = this.$store.state.user.data;
|
||||
|
||||
if (user._id === uid && !forceShow) {
|
||||
if (this.$route.name === 'tasks') {
|
||||
this.$route.router.go('options.profile.avatar');
|
||||
return;
|
||||
}
|
||||
|
||||
this.$route.router.go('tasks');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.$store.dispatch('members:selectMember', {
|
||||
memberId: uid,
|
||||
});
|
||||
|
||||
this.$root.$emit('bv::show::modal', 'members-modal');
|
||||
},
|
||||
async removeMember (member, index) {
|
||||
this.memberToRemove = member;
|
||||
this.memberToRemove.index = index;
|
||||
this.$root.$emit('bv::show::modal', 'remove-member');
|
||||
},
|
||||
memberRemoved () {
|
||||
this.members.splice(this.memberToRemove.index, 1);
|
||||
this.group.memberCount -= 1;
|
||||
this.memberToRemove = {};
|
||||
},
|
||||
async quickReply (uid) {
|
||||
this.memberToReply = uid;
|
||||
await this.$store.dispatch('members:selectMember', {
|
||||
memberId: uid,
|
||||
});
|
||||
this.$root.$emit('bv::show::modal', 'private-message'); // MemberModalCtrl
|
||||
},
|
||||
async addManager (memberId) {
|
||||
await this.$store.dispatch('guilds:addManager', {
|
||||
groupId: this.groupId,
|
||||
memberId,
|
||||
});
|
||||
alert(this.$t('managerAdded'));
|
||||
},
|
||||
async removeManager (memberId) {
|
||||
await this.$store.dispatch('guilds:removeManager', {
|
||||
groupId: this.groupId,
|
||||
memberId,
|
||||
});
|
||||
alert(this.$t('managerRemoved'));
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'members-modal');
|
||||
},
|
||||
changeSortOption (e) {
|
||||
this.sortOption.value = e.target.value;
|
||||
this.sort();
|
||||
},
|
||||
changeSortDirection (e) {
|
||||
this.sortOption.direction = e.target.value;
|
||||
this.sort();
|
||||
},
|
||||
sort () {
|
||||
this.sortDirty = true;
|
||||
this.members = orderBy(this.members, [this.sortOption.value], [this.sortOption.direction]);
|
||||
},
|
||||
async applySortOptions () {
|
||||
const settings = {
|
||||
'party.order': this.sortOption.value,
|
||||
'party.orderAscending': this.sortOption.direction,
|
||||
};
|
||||
await this.$store.dispatch('user:set', settings);
|
||||
this.sortDirty = false;
|
||||
},
|
||||
async loadMoreMembers () {
|
||||
const lastMember = this.members[this.members.length - 1];
|
||||
if (!lastMember) return;
|
||||
|
||||
let newMembers = await this.$store.state.memberModalOptions.fetchMoreMembers({
|
||||
challengeId: this.challengeId,
|
||||
groupId: this.groupId,
|
||||
lastMemberId: lastMember._id,
|
||||
includeAllPublicFields: true,
|
||||
});
|
||||
|
||||
this.members = this.members.concat(newMembers);
|
||||
},
|
||||
viewMembers () {
|
||||
this.selectedPage = 'members';
|
||||
},
|
||||
viewInvites () {
|
||||
this.selectedPage = 'invites';
|
||||
},
|
||||
async removeInvite (member, index) {
|
||||
this.invites.splice(index, 1);
|
||||
await this.$store.dispatch('members:removeMember', {
|
||||
memberId: member._id,
|
||||
groupId: this.groupId,
|
||||
});
|
||||
this.viewMembers();
|
||||
},
|
||||
async promoteToLeader (member) {
|
||||
let groupData = Object.assign({}, this.group);
|
||||
|
||||
groupData.leader = member._id;
|
||||
await this.$store.dispatch('guilds:update', {group: groupData});
|
||||
|
||||
alert(this.$t('leaderChanged'));
|
||||
|
||||
groupData.leader = member;
|
||||
this.$root.$emit('updatedGroup', groupData);
|
||||
},
|
||||
viewProgress (member) {
|
||||
this.$root.$emit('habitica:challenge:member-progress', {
|
||||
progressMemberId: member._id,
|
||||
});
|
||||
},
|
||||
shouldShowAddManager (memberId) {
|
||||
if (!this.isLeader && !this.isAdmin) return false;
|
||||
if (memberId === this.group.leader || memberId === this.group.leader._id) return false;
|
||||
return this.groupIsSubscribed && !(this.group.managers && this.group.managers[memberId]);
|
||||
},
|
||||
shouldShowRemoveManager (memberId) {
|
||||
if (!this.isLeader && !this.isAdmin) return false;
|
||||
return this.group.managers && this.group.managers[memberId];
|
||||
},
|
||||
shouldShowLeaderFunctions (memberId) {
|
||||
return !this.challengeId && (this.isLeader || this.isAdmin) && this.user._id !== memberId;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,144 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
sidebar(v-on:search="updateSearch", v-on:filter="updateFilters")
|
||||
|
||||
.standard-page
|
||||
.row
|
||||
.col-md-8.text-left
|
||||
h1.page-header(v-once) {{ $t('myGuilds') }}
|
||||
h2(v-if='loading && guilds.length === 0') {{ $t('loading') }}
|
||||
.col-4
|
||||
button.btn.btn-secondary.create-group-button.float-right(@click='createGroup()')
|
||||
.svg-icon.positive-icon(v-html="icons.positiveIcon")
|
||||
span(v-once) {{$t('createGuild2')}}
|
||||
// @TODO: Add when we implement recent activity .float-right
|
||||
span.dropdown-label {{ $t('sortBy') }}
|
||||
b-dropdown(:text="$t('sort')", right=true)
|
||||
b-dropdown-item(v-for='sortOption in sortOptions', :key="sortOption.value", @click='sort(sortOption.value)') {{sortOption.text}}
|
||||
|
||||
.row
|
||||
.no-guilds.text-center.col-md-6.offset-md-3(v-if='!loading && guilds.length === 0')
|
||||
.svg-icon(v-html='icons.greyBadge')
|
||||
h2(v-once) {{$t('noGuildsTitle')}}
|
||||
p(v-once) {{$t('noGuildsParagraph1')}}
|
||||
p(v-once) {{$t('noGuildsParagraph2')}}
|
||||
|
||||
.row
|
||||
.no-guilds.text-center.col-md-6.offset-md-3(v-if='!loading && guilds.length > 0 && filteredGuilds.length === 0')
|
||||
h2(v-once) {{$t('noGuildsMatchFilters')}}
|
||||
|
||||
.row
|
||||
.col-md-12
|
||||
public-guild-item(v-for="guild in filteredGuilds", :key='guild._id', :guild="guild", :display-gem-bank='true')
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
.sort-select {
|
||||
margin: 2em;
|
||||
}
|
||||
|
||||
.positive-icon {
|
||||
color: $green-10;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.no-guilds {
|
||||
color: $gray-200;
|
||||
margin-top: 10em;
|
||||
|
||||
h2 {
|
||||
color: $gray-200;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 88.7px;
|
||||
margin: 1em auto;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import groupUtilities from 'client/mixins/groupsUtilities';
|
||||
|
||||
import MugenScroll from 'vue-mugen-scroll';
|
||||
|
||||
import PublicGuildItem from './publicGuildItem';
|
||||
import Sidebar from './sidebar';
|
||||
|
||||
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
|
||||
import positiveIcon from 'assets/svg/positive.svg';
|
||||
|
||||
export default {
|
||||
mixins: [groupUtilities],
|
||||
components: { PublicGuildItem, MugenScroll, Sidebar },
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
greyBadge: greyBadgeIcon,
|
||||
positiveIcon,
|
||||
}),
|
||||
loading: false,
|
||||
search: '',
|
||||
filters: {},
|
||||
sort: 'none',
|
||||
sortOptions: [
|
||||
{
|
||||
text: this.$t('none'),
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
text: this.$t('memberCount'),
|
||||
value: 'member_count',
|
||||
},
|
||||
{
|
||||
text: this.$t('recentActivity'),
|
||||
value: 'recent_activity',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
created () {
|
||||
this.fetchGuilds();
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
guilds: 'myGuilds',
|
||||
}),
|
||||
filteredGuilds () {
|
||||
let search = this.search;
|
||||
let filters = this.filters;
|
||||
let user = this.$store.state.user.data;
|
||||
let filterGuild = this.filterGuild;
|
||||
return this.guilds.filter((guild) => {
|
||||
if (guild.categories) {
|
||||
guild.categorySlugs = guild.categories.map(cat => {
|
||||
return cat.slug;
|
||||
});
|
||||
}
|
||||
return filterGuild(guild, filters, search, user);
|
||||
});
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateSearch (eventData) {
|
||||
this.search = eventData.searchTerm;
|
||||
},
|
||||
updateFilters (eventData) {
|
||||
this.filters = eventData;
|
||||
},
|
||||
async fetchGuilds () {
|
||||
this.loading = true;
|
||||
await this.$store.dispatch('guilds:getMyGuilds');
|
||||
this.loading = false;
|
||||
},
|
||||
createGroup () {
|
||||
this.$store.state.editingGroup = {};
|
||||
this.$root.$emit('bv::show::modal', 'guild-form');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,20 @@
|
||||
<template lang="pug">
|
||||
div
|
||||
button.btn.btn-primary(b-btn, @click="$root.$emit('bv::show::modal','new-party-modal')") {{ $t('viewMembers') }}
|
||||
|
||||
b-modal#new-party-modal(:title="$t('createGuild')", size='lg')
|
||||
.header-wrap(slot="modal-header")
|
||||
.row
|
||||
.col-6
|
||||
h1(v-once) {{$t('members')}}
|
||||
.col-6
|
||||
button(type="button" aria-label="Close" class="close")
|
||||
span(aria-hidden="true") ×
|
||||
.row
|
||||
.form-group.col-6
|
||||
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
.col-4.offset-2
|
||||
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}}
|
||||
</template>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template lang="pug">
|
||||
b-modal#participant-list(size='md', :hide-footer='true')
|
||||
.header-wrap(slot="modal-header")
|
||||
.row
|
||||
.col-6
|
||||
h1(v-once) {{ $t('participantsTitle') }}
|
||||
.col-6
|
||||
button(type="button" aria-label="Close" class="close", @click='close()')
|
||||
span(aria-hidden="true") ×
|
||||
.row(v-for='member in participants')
|
||||
.col-12.no-padding-left
|
||||
member-details(:member='member')
|
||||
.modal-footer
|
||||
button.btn.btn-primary(@click='close()') {{ $t('close') }}
|
||||
</template>
|
||||
|
||||
<style lang='scss'>
|
||||
#participant-list {
|
||||
.modal-header {
|
||||
background-color: #edecee;
|
||||
border-radius: 8px 8px 0 0;
|
||||
box-shadow: 0 1px 2px 0 rgba(26, 24, 29, 0.24);
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
background-color: #edecee;
|
||||
border-radius: 0 0 8px 8px;
|
||||
}
|
||||
|
||||
.small-text, .character-name {
|
||||
color: #878190;
|
||||
}
|
||||
|
||||
.no-padding-left {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.member-details {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
.header-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #4f2a93;
|
||||
}
|
||||
|
||||
#participant-list_modal_body {
|
||||
padding: 0;
|
||||
max-height: 450px;
|
||||
|
||||
.member-details {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'client/libs/store';
|
||||
|
||||
import MemberDetails from '../memberDetails';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
components: {
|
||||
MemberDetails,
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
partyMembers: 'party:members',
|
||||
}),
|
||||
participants () {
|
||||
let partyMembers = this.partyMembers || [];
|
||||
return partyMembers.filter(member => this.group.quest.members[member._id] === true);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'participant-list');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,195 @@
|
||||
<template lang="pug">
|
||||
router-link.card-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
|
||||
.card
|
||||
.card-body
|
||||
.row
|
||||
.col-md-2.badge-column
|
||||
.shield-wrap(:class="{gold: guild.memberCount >= 1000, silver: guild.memberCount >= 100 && guild.memberCount < 1000}")
|
||||
.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 < 1000')
|
||||
.svg-icon.shield(v-html="icons.bronzeGuildBadgeIcon", v-if='guild.memberCount < 100')
|
||||
.member-count {{ guild.memberCount | abbrNum }}
|
||||
.col-md-10
|
||||
.row
|
||||
.col-md-8
|
||||
router-link(:to="{ name: 'guild', params: { groupId: guild._id } }")
|
||||
h3 {{ guild.name }}
|
||||
p.summary(v-if='guild.summary') {{guild.summary.substr(0, MAX_SUMMARY_SIZE_FOR_GUILDS)}}
|
||||
p.summary(v-else) {{ guild.name }}
|
||||
.col-md-2.cta-container
|
||||
button.btn.btn-danger(v-if='isMember && displayLeave' @click.prevent='leave()', v-once) {{ $t('leave') }}
|
||||
button.btn.btn-success(v-if='!isMember' @click='join()', v-once) {{ $t('join') }}
|
||||
div.item-with-icon.gem-bank(v-if='displayGemBank')
|
||||
.svg-icon.gem(v-html="icons.gem")
|
||||
span.count {{ guild.balance * 4 }}
|
||||
div.guild-bank(v-if='displayGemBank', v-once) {{$t('guildBank')}}
|
||||
.row
|
||||
category-tags.col-md-12(:categories="guild.categories", :owner="isOwner", v-once)
|
||||
span.recommend-text(v-if='showSuggested(guild._id)') {{$t('suggestedGroup')}}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.card-link {
|
||||
color: #4E4A57 !important;
|
||||
}
|
||||
|
||||
.card-link:hover {
|
||||
text-decoration: none !important;
|
||||
}
|
||||
|
||||
.card {
|
||||
min-height: 160px;
|
||||
border-radius: 4px;
|
||||
background-color: $white;
|
||||
box-shadow: 0 2px 2px 0 rgba($black, 0.15), 0 1px 4px 0 rgba($black, 0.1);
|
||||
margin-bottom: 1rem;
|
||||
|
||||
.recommend-text {
|
||||
font-size: 12px;
|
||||
font-style: italic;
|
||||
line-height: 2;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.cta-container {
|
||||
margin: 0 auto;
|
||||
|
||||
button {
|
||||
margin-top: 2.5em;
|
||||
}
|
||||
}
|
||||
|
||||
.item-with-icon {
|
||||
.count {
|
||||
font-size: 20px;
|
||||
height: 37px;
|
||||
width: 37px;
|
||||
margin-left: .2em;
|
||||
}
|
||||
}
|
||||
|
||||
.shield {
|
||||
width: 70px;
|
||||
}
|
||||
|
||||
.gold {
|
||||
color: #fdbb5a;
|
||||
|
||||
.member-count {
|
||||
color: #fdbb5a;
|
||||
}
|
||||
}
|
||||
|
||||
.silver {
|
||||
color: #c2c2c2;
|
||||
|
||||
.member-count {
|
||||
color: #c2c2c2;
|
||||
}
|
||||
}
|
||||
|
||||
.badge-column {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.shield-wrap {
|
||||
display: inline-block;
|
||||
height: 70px;
|
||||
}
|
||||
}
|
||||
|
||||
.guild-bank {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
.member-count {
|
||||
position: relative;
|
||||
top: -3.7em;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
line-height: 1.1;
|
||||
text-align: center;
|
||||
color: #b36213;
|
||||
margin-top: 2.0em;
|
||||
}
|
||||
|
||||
.gem-bank {
|
||||
margin-top: 2em;
|
||||
|
||||
.gem {
|
||||
width: 25px;
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
margin-right: .8em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import moment from 'moment';
|
||||
import { mapState } from 'client/libs/store';
|
||||
import categoryTags from '../categories/categoryTags';
|
||||
import groupUtilities from 'client/mixins/groupsUtilities';
|
||||
import markdown from 'client/directives/markdown';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import goldGuildBadgeIcon from 'assets/svg/gold-guild-badge-large.svg';
|
||||
import silverGuildBadgeIcon from 'assets/svg/silver-guild-badge-large.svg';
|
||||
import bronzeGuildBadgeIcon from 'assets/svg/bronze-guild-badge-large.svg';
|
||||
import { MAX_SUMMARY_SIZE_FOR_GUILDS } from 'common/script/constants';
|
||||
|
||||
export default {
|
||||
mixins: [groupUtilities],
|
||||
directives: {
|
||||
markdown,
|
||||
},
|
||||
props: ['guild', 'displayLeave', 'displayGemBank'],
|
||||
components: {
|
||||
categoryTags,
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
isOwner () {
|
||||
return this.guild.leader && this.guild.leader === this.user._id;
|
||||
},
|
||||
isMember () {
|
||||
return this.isMemberOfGroup(this.user, this.guild);
|
||||
},
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
MAX_SUMMARY_SIZE_FOR_GUILDS,
|
||||
icons: Object.freeze({
|
||||
gem: gemIcon,
|
||||
goldGuildBadge: goldGuildBadgeIcon,
|
||||
silverGuildBadgeIcon,
|
||||
bronzeGuildBadgeIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
showSuggested (guildId) {
|
||||
let habiticaHelpingGuildId = '5481ccf3-5d2d-48a9-a871-70a7380cee5a';
|
||||
let sixtyDaysAgoFromNow = moment().subtract(60, 'days');
|
||||
let isUserNew = moment(this.user.auth.timestamps.created).isAfter(sixtyDaysAgoFromNow);
|
||||
return guildId === habiticaHelpingGuildId && isUserNew;
|
||||
},
|
||||
async join () {
|
||||
// @TODO: This needs to be in the notifications where users will now accept invites
|
||||
if (this.guild.cancelledPlan && !confirm(window.env.t('aboutToJoinCancelledGroupPlan'))) {
|
||||
return;
|
||||
}
|
||||
await this.$store.dispatch('guilds:join', {groupId: this.guild._id, type: 'guild'});
|
||||
},
|
||||
async leave () {
|
||||
// @TODO: ask about challenges when we add challenges
|
||||
await this.$store.dispatch('guilds:leave', {groupId: this.guild._id, type: 'myGuilds'});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,224 @@
|
||||
<template lang="pug">
|
||||
b-modal#quest-details(title="Empty", size='md', :hide-footer="true", :hide-header="true")
|
||||
.left-panel.content
|
||||
h3.text-center {{ $t('participantsTitle') }}
|
||||
.row
|
||||
.col-10.offset-1.text-center
|
||||
span.description(v-once) {{ $t('participantDesc') }}
|
||||
.row
|
||||
.col-12.member(v-for='member in members')
|
||||
strong(:class="{'declined-name': member.accepted === false}") {{member.name}}
|
||||
.accepted.float-right(v-if='member.accepted === true') {{ $t('accepted') }}
|
||||
.declined.float-right(v-if='member.accepted === false') {{ $t('declined') }}
|
||||
.pending.float-right(v-if='member.accepted === null') {{ $t('pending') }}
|
||||
div(v-if='questData')
|
||||
questDialogContent(:item="questData")
|
||||
div.text-center.actions(v-if='canEditQuest')
|
||||
div
|
||||
button.btn.btn-secondary(v-once, @click="questConfirm()") {{ $t('begin') }}
|
||||
// @TODO don't allow the party leader to start the quest until the leader has accepted or rejected the invitation (users get confused and think "begin" means "join quest")
|
||||
div
|
||||
.cancel(v-once, @click="questCancel()") {{ $t('cancel') }}
|
||||
.side-panel(v-if='questData')
|
||||
questDialogDrops(:item="questData")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
header {
|
||||
background-color: $white !important;
|
||||
border: none !important;
|
||||
|
||||
h5 {
|
||||
text-indent: -99999px;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-details {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
background: #4e4a57;
|
||||
color: $white;
|
||||
position: absolute;
|
||||
height: 460px;
|
||||
width: 320px;
|
||||
top: 2.5em;
|
||||
left: -22.8em;
|
||||
z-index: -1;
|
||||
padding: 2em;
|
||||
overflow-y: auto;
|
||||
h3 {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.selected .quest-wrapper {
|
||||
border: solid 1.5px #9a62ff;
|
||||
}
|
||||
|
||||
.quest-wrapper:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.quest-col .quest-wrapper {
|
||||
background: $white;
|
||||
padding: .2em;
|
||||
margin-bottom: 1em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
color: #a5a1ac;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
position: absolute;
|
||||
right: -350px;
|
||||
top: 25px;
|
||||
border-radius: 8px;
|
||||
background-color: $gray-600;
|
||||
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 364px;
|
||||
z-index: -1;
|
||||
height: 93%;
|
||||
}
|
||||
|
||||
.member {
|
||||
padding: 1em .5em;
|
||||
border-top: 1px solid #686274;
|
||||
|
||||
.declined-name {
|
||||
color: #878190;
|
||||
}
|
||||
|
||||
.accepted {
|
||||
color: #1ed3a0;
|
||||
}
|
||||
|
||||
.declined {
|
||||
color: #f19595;
|
||||
}
|
||||
|
||||
.pending {
|
||||
color: #c3c0c7;
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
padding-top: 2em;
|
||||
padding-bottom: 2em;
|
||||
|
||||
.cancel {
|
||||
color: #f74e52;
|
||||
margin-top: 3em;
|
||||
}
|
||||
|
||||
.cancel:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState, mapGetters } from 'client/libs/store';
|
||||
import quests from 'common/script/content/quests';
|
||||
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
|
||||
import qrCodeIcon from 'assets/svg/qrCode.svg';
|
||||
import facebookIcon from 'assets/svg/facebook.svg';
|
||||
import twitterIcon from 'assets/svg/twitter.svg';
|
||||
import starIcon from 'assets/svg/star.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import difficultyStarIcon from 'assets/svg/difficulty-star.svg';
|
||||
import questDialogDrops from '../shops/quests/questDialogDrops';
|
||||
import questDialogContent from '../shops/quests/questDialogContent';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
components: {
|
||||
questDialogDrops,
|
||||
questDialogContent,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
selectedQuest: {},
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
greyBadge: greyBadgeIcon,
|
||||
qrCode: qrCodeIcon,
|
||||
facebook: facebookIcon,
|
||||
twitter: twitterIcon,
|
||||
starIcon,
|
||||
goldIcon,
|
||||
difficultyStarIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
...mapGetters({
|
||||
partyMembers: 'party:members',
|
||||
}),
|
||||
questData () {
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
members () {
|
||||
let partyMembers = this.partyMembers || [];
|
||||
return partyMembers.map(member => {
|
||||
return {
|
||||
name: member.profile.name,
|
||||
accepted: this.group.quest.members[member._id],
|
||||
};
|
||||
});
|
||||
},
|
||||
canEditQuest () {
|
||||
if (!this.group.quest) return false;
|
||||
let isQuestLeader = this.group.quest.leader === this.user._id;
|
||||
let isPartyLeader = this.group.leader._id === this.user._id;
|
||||
return isQuestLeader || isPartyLeader;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async questConfirm () {
|
||||
let count = 0;
|
||||
for (let uuid in this.group.quest.members) {
|
||||
if (this.group.quest.members[uuid]) count += 1;
|
||||
}
|
||||
if (!confirm(this.$t('questConfirm', { questmembers: count, totalmembers: this.group.memberCount}))) return;
|
||||
this.questForceStart();
|
||||
},
|
||||
async questForceStart () {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/force-start'});
|
||||
this.group.quest = quest;
|
||||
this.close();
|
||||
},
|
||||
async questCancel () {
|
||||
if (!confirm(this.$t('sureCancel'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/cancel'});
|
||||
this.group.quest = quest;
|
||||
this.close();
|
||||
},
|
||||
close () {
|
||||
this.$root.$emit('bv::hide::modal', 'quest-details');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,294 @@
|
||||
<template lang="pug">
|
||||
sidebar-section(:title="$t('questDetailsTitle')")
|
||||
.row.no-quest-section(v-if='!onPendingQuest && !onActiveQuest')
|
||||
.col-12.text-center
|
||||
.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') }}
|
||||
.row.quest-active-section(v-if='onPendingQuest && !onActiveQuest')
|
||||
.col-2
|
||||
.quest(:class='`inventory_quest_scroll_${questData.key}`')
|
||||
.col-6.titles
|
||||
strong {{ questData.text() }}
|
||||
p {{acceptedCount}} / {{group.memberCount}}
|
||||
.col-4
|
||||
button.btn.btn-secondary(@click="openQuestDetails()") {{ $t('details') }}
|
||||
.row.quest-active-section.quest-invite(v-if='user.party.quest && user.party.quest.RSVPNeeded')
|
||||
span {{ $t('wouldYouParticipate') }}
|
||||
button.btn.btn-primary.accept(@click='questAccept(group._id)') {{$t('accept')}}
|
||||
button.btn.btn-primary.reject(@click='questReject(group._id)') {{$t('reject')}}
|
||||
.row.quest-active-section(v-if='!onPendingQuest && onActiveQuest')
|
||||
.col-12.text-center
|
||||
.quest-boss(:class="'quest_' + questData.key")
|
||||
h3(v-once) {{ questData.text() }}
|
||||
.quest-box
|
||||
.collect-info(v-if='questData.collect')
|
||||
.row
|
||||
.col-12
|
||||
a.float-right(@click="openParticipantList()") {{ $t('participantsTitle') }}
|
||||
.row(v-for='(value, key) in questData.collect')
|
||||
.col-2
|
||||
div(:class="'quest_' + questData.key + '_' + key")
|
||||
.col-10
|
||||
strong {{value.text()}}
|
||||
.grey-progress-bar
|
||||
.collect-progress-bar(:style="{width: (group.quest.progress.collect[key] / value.count) * 100 + '%'}")
|
||||
strong {{group.quest.progress.collect[key]}} / {{value.count}}
|
||||
div.text-right(v-if='userIsOnQuest') {{parseFloat(user.party.quest.progress.collectedItems) || 0}} items found
|
||||
.boss-info(v-if='questData.boss')
|
||||
.row
|
||||
.col-6
|
||||
h4.float-left(v-once) {{ questData.boss.name() }}
|
||||
.col-6
|
||||
a.float-right(@click="openParticipantList()") {{ $t('participantsTitle') }}
|
||||
.row
|
||||
.col-12
|
||||
.grey-progress-bar
|
||||
.boss-health-bar(:style="{width: bossHpPercent + '%'}")
|
||||
.row.boss-details
|
||||
.col-6
|
||||
span.float-left
|
||||
| {{ Math.ceil(parseFloat(group.quest.progress.hp) * 100) / 100 }} / {{ parseFloat(questData.boss.hp).toFixed(2) }}
|
||||
// current boss hp uses ceil so you don't underestimate damage needed to end quest
|
||||
.col-6(v-if='userIsOnQuest')
|
||||
// @TODO: Why do we not sync quest progress on the group doc? Each user could have different progress.
|
||||
span.float-right {{ user.party.quest.progress.up | floor(10) }} {{ $t('pendingDamageLabel') }}
|
||||
// player's pending damage uses floor so you don't overestimate damage you've already done
|
||||
.row.rage-bar-row(v-if='questData.boss.rage')
|
||||
.col-12
|
||||
.grey-progress-bar
|
||||
.boss-health-bar.rage-bar(:style="{width: (group.quest.progress.rage / questData.boss.rage.value) * 100 + '%'}")
|
||||
.row.boss-details.rage-details(v-if='questData.boss.rage')
|
||||
.col-6
|
||||
span.float-left {{ $t('rage') }} {{ parseFloat(group.quest.progress.rage).toFixed(2) }} / {{ questData.boss.rage.value }}
|
||||
button.btn.btn-secondary(v-once, @click="questAbort()", v-if='canEditQuest') {{ $t('abort') }}
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
.svg-icon {
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.quest-boss {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.boss-health-bar {
|
||||
width: 80%;
|
||||
background-color: red;
|
||||
height: 15px;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
|
||||
.rage-details {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.boss-health-bar.rage-bar {
|
||||
margin-top: 1em;
|
||||
background-color: orange;
|
||||
}
|
||||
|
||||
.grey-progress-bar {
|
||||
width: 100%;
|
||||
height: 15px;
|
||||
background-color: #e1e0e3;
|
||||
}
|
||||
|
||||
.collect-progress-bar {
|
||||
background-color: #24cc8f;
|
||||
height: 15px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.no-quest-section {
|
||||
padding: 2em;
|
||||
color: $gray-300;
|
||||
|
||||
h4 {
|
||||
color: $gray-300;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
height: 30px;
|
||||
width: 30px;
|
||||
margin: 0 auto;
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-active-section {
|
||||
.titles {
|
||||
padding-top: .5em;
|
||||
}
|
||||
|
||||
.quest-box {
|
||||
background-image: url('~client/assets/svg/for-css/quest-border.svg');
|
||||
background-size: 100% 100%;
|
||||
width: 100%;
|
||||
padding: .5em;
|
||||
margin-bottom: 1em;
|
||||
|
||||
a {
|
||||
font-family: 'Roboto Condensed', sans-serif;
|
||||
font-weight: bold;
|
||||
color: $gray-10;
|
||||
}
|
||||
|
||||
svg: {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.boss-info, .collect-info {
|
||||
width: 90%;
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-invite {
|
||||
background-color: #2995cd;
|
||||
color: #fff;
|
||||
padding: 1em;
|
||||
|
||||
span {
|
||||
margin-top: .3em;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.accept, .reject {
|
||||
padding: .2em 1em;
|
||||
font-size: 12px;
|
||||
height: 24px;
|
||||
border-radius: 2px;
|
||||
box-shadow: 0 2px 2px 0 rgba(26, 24, 29, 0.16), 0 1px 4px 0 rgba(26, 24, 29, 0.12);
|
||||
}
|
||||
|
||||
.accept {
|
||||
background-color: #24cc8f;
|
||||
margin-left: 4em;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
.reject {
|
||||
border-radius: 2px;
|
||||
background-color: #f74e52;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
|
||||
import quests from 'common/script/content/quests';
|
||||
import percent from 'common/script/libs/percent';
|
||||
import sidebarSection from '../sidebarSection';
|
||||
|
||||
import questIcon from 'assets/svg/quest.svg';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
components: {
|
||||
sidebarSection,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
icons: Object.freeze({
|
||||
questIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
userIsOnQuest () {
|
||||
if (!this.group.quest || !this.group.quest.members) return false;
|
||||
return Boolean(this.group.quest.members[this.user._id]);
|
||||
},
|
||||
onPendingQuest () {
|
||||
return Boolean(this.group.quest.key) && !this.group.quest.active;
|
||||
},
|
||||
onActiveQuest () {
|
||||
return this.group.quest.active;
|
||||
},
|
||||
bossHpPercent () {
|
||||
return percent(this.group.quest.progress.hp, this.questData.boss.hp);
|
||||
},
|
||||
questData () {
|
||||
if (!this.group.quest) return {};
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
canEditQuest () {
|
||||
if (!this.group.quest) return false;
|
||||
let isQuestLeader = this.group.quest.leader === this.user._id;
|
||||
let isPartyLeader = this.group.leader._id === this.user._id;
|
||||
return isQuestLeader || isPartyLeader;
|
||||
},
|
||||
isMemberOfPendingQuest () {
|
||||
let userid = this.user._id;
|
||||
let group = this.group;
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (group.quest.active) return false; // quest is started, not pending
|
||||
return userid in group.quest.members && group.quest.members[userid] !== false;
|
||||
},
|
||||
isMemberOfRunningQuest () {
|
||||
let userid = this.user._id;
|
||||
let group = this.group;
|
||||
if (!group.quest || !group.quest.members) return false;
|
||||
if (!group.quest.active) return false; // quest is pending, not started
|
||||
return group.quest.members[userid];
|
||||
},
|
||||
acceptedCount () {
|
||||
let count = 0;
|
||||
|
||||
if (!this.group || !this.group.quest) return count;
|
||||
|
||||
for (let uuid in this.group.quest.members) {
|
||||
if (this.group.quest.members[uuid]) count += 1;
|
||||
}
|
||||
|
||||
return count;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
openStartQuestModal () {
|
||||
this.$root.$emit('bv::show::modal', 'start-quest-modal');
|
||||
},
|
||||
openQuestDetails () {
|
||||
this.$root.$emit('bv::show::modal', 'quest-details');
|
||||
},
|
||||
openParticipantList () {
|
||||
this.$root.$emit('bv::show::modal', 'participant-list');
|
||||
},
|
||||
async questAbort () {
|
||||
if (!confirm(this.$t('sureAbort'))) return;
|
||||
if (!confirm(this.$t('doubleSureAbort'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/abort'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questLeave () {
|
||||
if (!confirm(this.$t('sureLeave'))) return;
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: this.group._id, action: 'quests/leave'});
|
||||
this.group.quest = quest;
|
||||
},
|
||||
async questAccept (partyId) {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/accept'});
|
||||
this.user.party.quest = quest;
|
||||
},
|
||||
async questReject (partyId) {
|
||||
let quest = await this.$store.dispatch('quests:sendAction', {groupId: partyId, action: 'quests/reject'});
|
||||
this.user.party.quest = quest;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,157 @@
|
||||
<template lang="pug">
|
||||
.standard-sidebar.d-none.d-sm-block
|
||||
.form-group
|
||||
input.form-control.search(type="text", :placeholder="$t('search')", v-model='searchTerm')
|
||||
|
||||
form
|
||||
h2(v-once) {{ $t('filter') }}
|
||||
.form-group
|
||||
h3 {{ $t('category') }}
|
||||
.form-check(
|
||||
v-for="group in categoryOptions",
|
||||
:key="group.key",
|
||||
)
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value='group.key' v-model="categoryFilters", :id="group.key")
|
||||
label.custom-control-label(v-once, :for="group.key") {{ $t(group.label) }}
|
||||
.form-group
|
||||
h3 {{ $t('role') }}
|
||||
.form-check(
|
||||
v-for="group in roleOptions",
|
||||
:key="group.key",
|
||||
)
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value='group.key' v-model="roleFilters", :id="group.key")
|
||||
label.custom-control-label(v-once, :for="group.key") {{ $t(group.label) }}
|
||||
.form-group
|
||||
h3 {{ $t('guildSize') }}
|
||||
.form-check(
|
||||
v-for="group in guildSizeOptions",
|
||||
:key="group.key",
|
||||
)
|
||||
.custom-control.custom-checkbox
|
||||
input.custom-control-input(type="checkbox", :value='group.key' v-model="guildSizeFilters", :id="group.key")
|
||||
label.custom-control-label(v-once, :for="group.key") {{ $t(group.label) }}
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import throttle from 'lodash/throttle';
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
categoryFilters: [],
|
||||
categoryOptions: [
|
||||
{
|
||||
label: 'habitica_official',
|
||||
key: 'habitica_official',
|
||||
},
|
||||
{
|
||||
label: 'academics',
|
||||
key: 'academics',
|
||||
},
|
||||
{
|
||||
label: 'advocacy_causes',
|
||||
key: 'advocacy_causes',
|
||||
},
|
||||
{
|
||||
label: 'creativity',
|
||||
key: 'creativity',
|
||||
},
|
||||
{
|
||||
label: 'entertainment',
|
||||
key: 'entertainment',
|
||||
},
|
||||
{
|
||||
label: 'finance',
|
||||
key: 'finance',
|
||||
},
|
||||
{
|
||||
label: 'health_fitness',
|
||||
key: 'health_fitness',
|
||||
},
|
||||
{
|
||||
label: 'hobbies_occupations',
|
||||
key: 'hobbies_occupations',
|
||||
},
|
||||
{
|
||||
label: 'location_based',
|
||||
key: 'location_based',
|
||||
},
|
||||
{
|
||||
label: 'mental_health',
|
||||
key: 'mental_health',
|
||||
},
|
||||
{
|
||||
label: 'getting_organized',
|
||||
key: 'getting_organized',
|
||||
},
|
||||
{
|
||||
label: 'recovery_support_groups',
|
||||
key: 'recovery_support_groups',
|
||||
},
|
||||
{
|
||||
label: 'spirituality',
|
||||
key: 'spirituality',
|
||||
},
|
||||
{
|
||||
label: 'time_management',
|
||||
key: 'time_management',
|
||||
},
|
||||
],
|
||||
roleFilters: [],
|
||||
roleOptions: [
|
||||
{
|
||||
label: 'guildLeader',
|
||||
key: 'guild_leader',
|
||||
},
|
||||
{
|
||||
label: 'member',
|
||||
key: 'member',
|
||||
},
|
||||
],
|
||||
guildSizeFilters: [],
|
||||
guildSizeOptions: [
|
||||
{
|
||||
label: 'goldTier',
|
||||
key: 'gold_tier',
|
||||
},
|
||||
{
|
||||
label: 'silverTier',
|
||||
key: 'silver_tier',
|
||||
},
|
||||
{
|
||||
label: 'bronzeTier',
|
||||
key: 'bronze_tier',
|
||||
},
|
||||
],
|
||||
searchTerm: '',
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
categoryFilters: function categoryFilters () {
|
||||
this.emitFilters();
|
||||
},
|
||||
roleFilters: function roleFilters () {
|
||||
this.emitFilters();
|
||||
},
|
||||
guildSizeFilters: function guildSizeFilters () {
|
||||
this.emitFilters();
|
||||
},
|
||||
searchTerm: throttle(function searchTerm (newSearch) {
|
||||
this.$emit('search', {
|
||||
searchTerm: newSearch,
|
||||
});
|
||||
}, 1000),
|
||||
},
|
||||
methods: {
|
||||
emitFilters () {
|
||||
this.$emit('filter', {
|
||||
categories: this.categoryFilters,
|
||||
roles: this.roleFilters,
|
||||
guildSize: this.guildSizeFilters,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,197 @@
|
||||
<template lang="pug">
|
||||
b-modal#start-quest-modal(title="Empty", size='md', :hide-footer="true", :hide-header="true")
|
||||
.left-panel.content
|
||||
h3.text-center Quests
|
||||
.row
|
||||
.col-4.quest-col(
|
||||
v-for='(value, key, index) in user.items.quests',
|
||||
@click='selectQuest({key})',
|
||||
:class="{selected: key === selectedQuest}", v-if='value > 0')
|
||||
.quest-wrapper
|
||||
b-popover(
|
||||
:target="`inventory_quest_scroll_${key}`"
|
||||
placement="top"
|
||||
triggers="hover")
|
||||
h4.popover-content-title {{ quests.quests[key].text() }}
|
||||
questInfo(:quest="quests.quests[key]")
|
||||
.quest(:class="`inventory_quest_scroll_${key}`", :id="`inventory_quest_scroll_${key}`")
|
||||
.row
|
||||
.col-10.offset-1.text-center
|
||||
span.description(v-once) {{ $t('noQuestToStart') }}
|
||||
div(v-if='questData')
|
||||
questDialogContent(:item="questData")
|
||||
div.text-center
|
||||
button.btn.btn-primary(@click='questInit()', :disabled="!Boolean(selectedQuest) || loading") {{$t('inviteToPartyOrQuest')}}
|
||||
div.text-center
|
||||
p {{$t('inviteInformation')}}
|
||||
.side-panel(v-if='questData')
|
||||
questDialogDrops(:item="questData")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
|
||||
header {
|
||||
background-color: $white !important;
|
||||
border: none !important;
|
||||
|
||||
h5 {
|
||||
text-indent: -99999px;
|
||||
}
|
||||
}
|
||||
|
||||
.quest-details {
|
||||
margin: 0 auto;
|
||||
text-align: left;
|
||||
width: 180px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
background: #4e4a57;
|
||||
color: $white;
|
||||
position: absolute;
|
||||
height: 460px;
|
||||
width: 320px;
|
||||
top: 2.5em;
|
||||
left: -23em;
|
||||
z-index: -1;
|
||||
padding: 2em;
|
||||
overflow-y: auto;
|
||||
|
||||
h3 {
|
||||
color: $white;
|
||||
}
|
||||
|
||||
.selected .quest-wrapper {
|
||||
border: solid 1.5px #9a62ff;
|
||||
}
|
||||
|
||||
.quest-wrapper:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.quest-col .quest-wrapper {
|
||||
background: $white;
|
||||
padding: .2em;
|
||||
margin-bottom: 1em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
color: #a5a1ac;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.side-panel {
|
||||
position: absolute;
|
||||
right: -350px;
|
||||
top: 25px;
|
||||
border-radius: 8px;
|
||||
background-color: $gray-600;
|
||||
box-shadow: 0 2px 16px 0 rgba(26, 24, 29, 0.32);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
width: 364px;
|
||||
z-index: -1;
|
||||
height: 93%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import * as Analytics from 'client/libs/analytics';
|
||||
|
||||
import quests from 'common/script/content/quests';
|
||||
|
||||
import copyIcon from 'assets/svg/copy.svg';
|
||||
import greyBadgeIcon from 'assets/svg/grey-badge.svg';
|
||||
import qrCodeIcon from 'assets/svg/qrCode.svg';
|
||||
import facebookIcon from 'assets/svg/facebook.svg';
|
||||
import twitterIcon from 'assets/svg/twitter.svg';
|
||||
import starIcon from 'assets/svg/star.svg';
|
||||
import goldIcon from 'assets/svg/gold.svg';
|
||||
import difficultyStarIcon from 'assets/svg/difficulty-star.svg';
|
||||
import questDialogDrops from '../shops/quests/questDialogDrops';
|
||||
import questDialogContent from '../shops/quests/questDialogContent';
|
||||
import QuestInfo from '../shops/quests/questInfo';
|
||||
|
||||
export default {
|
||||
props: ['group'],
|
||||
components: {
|
||||
questDialogDrops,
|
||||
questDialogContent,
|
||||
QuestInfo,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
loading: false,
|
||||
selectedQuest: {},
|
||||
icons: Object.freeze({
|
||||
copy: copyIcon,
|
||||
greyBadge: greyBadgeIcon,
|
||||
qrCode: qrCodeIcon,
|
||||
facebook: facebookIcon,
|
||||
twitter: twitterIcon,
|
||||
starIcon,
|
||||
goldIcon,
|
||||
difficultyStarIcon,
|
||||
}),
|
||||
shareUserIdShown: false,
|
||||
quests,
|
||||
};
|
||||
},
|
||||
mounted () {
|
||||
const userQuests = this.user.items.quests;
|
||||
for (const key in userQuests) {
|
||||
if (userQuests[key] > 0) {
|
||||
this.selectedQuest = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.$root.$on('selectQuest', this.selectQuest);
|
||||
},
|
||||
destroyed () {
|
||||
this.$root.$off('selectQuest', this.selectQuest);
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
questData () {
|
||||
return quests.quests[this.selectedQuest];
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
selectQuest (quest) {
|
||||
this.selectedQuest = quest.key;
|
||||
},
|
||||
async questInit () {
|
||||
this.loading = true;
|
||||
|
||||
Analytics.updateUser({
|
||||
partyID: this.group._id,
|
||||
partySize: this.group.memberCount,
|
||||
});
|
||||
|
||||
let groupId = this.group._id || this.user.party._id;
|
||||
|
||||
const key = this.selectedQuest;
|
||||
try {
|
||||
const response = await this.$store.dispatch('guilds:inviteToQuest', {groupId, key});
|
||||
const quest = response.data.data;
|
||||
|
||||
if (this.$store.state.party.data) this.$store.state.party.data.quest = quest;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
this.$root.$emit('bv::hide::modal', 'start-quest-modal');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,550 @@
|
||||
<template lang="pug">
|
||||
.row
|
||||
world-boss-info-modal
|
||||
world-boss-rage-modal
|
||||
.col-12.col-sm-8.clearfix.standard-page
|
||||
.row
|
||||
.col-6.title-details
|
||||
h1(v-once) {{ $t('welcomeToTavern') }}
|
||||
|
||||
chat(
|
||||
:label="$t('tavernChat')",
|
||||
:group="group",
|
||||
:placeholder="$t('tavernCommunityGuidelinesPlaceholder')",
|
||||
@fetchRecentMessages="fetchRecentMessages()"
|
||||
)
|
||||
.col-12.col-sm-4.sidebar
|
||||
.section
|
||||
.grassy-meadow-backdrop
|
||||
.daniel_front
|
||||
|
||||
.boss-section
|
||||
.world-boss(v-if='group && group.quest && group.quest.active', :style="{background: questData.colors.dark, 'border-color': questData.colors.extralight, 'outline-color': questData.colors.light}")
|
||||
.corner-decoration(:style="{top: '-2px', right: '-2px'}")
|
||||
.corner-decoration(:style="{top: '-2px', left: '-2px'}")
|
||||
.corner-decoration(:style="{bottom: '-2px', right: '-2px'}")
|
||||
.corner-decoration(:style="{bottom: '-2px', left: '-2px'}")
|
||||
.text-center.float-bar.d-flex.align-items-center
|
||||
span.diamond
|
||||
span.strong.reduce(:style="{background: questData.colors.dark}") {{ $t('worldBossEvent') }}
|
||||
span.diamond
|
||||
.boss-gradient.pb-3.pt-3
|
||||
p.text-center.reduce(:style="{color: questData.colors.extralight}") {{ $t(`${questData.key}ArtCredit`) }}
|
||||
.quest-boss(:class="'background_' + questData.key")
|
||||
.quest-boss(:class="'quest_' + questData.key")
|
||||
.quest-boss(:class="'phobia_' + questData.key", :style="{display: 'none'}")
|
||||
.p-3
|
||||
.row.d-flex.align-items-center.mb-2
|
||||
.col-sm-6
|
||||
strong.float-left {{ questData.boss.name() }}
|
||||
.col-sm-6
|
||||
span.d-flex.float-right
|
||||
.svg-icon.boss-icon(v-html="icons.swordIcon")
|
||||
span.ml-1.reduce(:style="{color: questData.colors.extralight}") {{ $t('pendingDamage', {damage: pendingDamage()}) }}
|
||||
.grey-progress-bar.mb-1
|
||||
.boss-health-bar(:style="{width: (group.quest.progress.hp / questData.boss.hp) * 100 + '%'}")
|
||||
span.d-flex.align-items-center
|
||||
.svg-icon.boss-icon(v-html="icons.healthIcon")
|
||||
span.reduce.ml-1.pt-1 {{ $t('bossHealth', {currentHealth: bossCurrentHealth(), maxHealth: questData.boss.hp.toLocaleString()}) }}
|
||||
.mt-3.mb-2
|
||||
strong.mr-1 {{ $t('rageAttack') }}
|
||||
span {{ questData.boss.rage.title() }}
|
||||
.grey-progress-bar.mb-1
|
||||
.boss-health-bar.rage-bar(:style="{width: (group.quest.progress.rage / questData.boss.rage.value) * 100 + '%'}")
|
||||
span.d-flex.align-items-center
|
||||
.svg-icon.boss-icon(v-html="icons.rageIcon")
|
||||
span.reduce.ml-1.pt-1 {{ $t('bossRage', {currentRage: bossCurrentRage(), maxRage: questData.boss.rage.value.toLocaleString()}) }}
|
||||
.row.d-flex.align-items-center.mb-2.mt-2
|
||||
.col-sm-4.d-flex
|
||||
strong.mr-2 {{ $t('rageStrikes') }}
|
||||
.svg-icon.boss-icon.information-icon.m-auto(v-html="icons.informationIcon", v-b-tooltip.hover.top="questData.boss.rage.description()")
|
||||
.col-sm-8.d-flex.align-items-center.justify-content-center
|
||||
.m-auto(@click="showWorldBossRage('seasonalShop')")
|
||||
img.rage-strike(src="~assets/images/world-boss/rage_strike@2x.png", v-if="!group.quest.extra.worldDmg.seasonalShop")
|
||||
img.rage-strike-active(src="~assets/images/world-boss/rage_strike-seasonalShop@2x.png", v-if="group.quest.extra.worldDmg.seasonalShop")
|
||||
.m-auto(@click="showWorldBossRage('market')")
|
||||
img.rage-strike(src="~assets/images/world-boss/rage_strike@2x.png", v-if="!group.quest.extra.worldDmg.market")
|
||||
img.rage-strike-active(src="~assets/images/world-boss/rage_strike-market@2x.png", v-if="group.quest.extra.worldDmg.market")
|
||||
.m-auto(@click="showWorldBossRage('quests')")
|
||||
img.rage-strike(src="~assets/images/world-boss/rage_strike@2x.png", v-if="!group.quest.extra.worldDmg.quests")
|
||||
img.rage-strike-active(src="~assets/images/world-boss/rage_strike-quests@2x.png", v-if="group.quest.extra.worldDmg.quests")
|
||||
.boss-description.p-3(:style="{'border-color': questData.colors.extralight}", @click="sections.worldBoss = !sections.worldBoss")
|
||||
strong.float-left {{ $t('worldBossDescription') }}
|
||||
.float-right
|
||||
.toggle-down(v-if="!sections.worldBoss")
|
||||
.svg-icon.boss-icon(v-html="icons.chevronIcon")
|
||||
.toggle-up(v-if="sections.worldBoss")
|
||||
.svg-icon.boss-icon.reverse(v-html="icons.chevronIcon")
|
||||
.mt-3(v-if="sections.worldBoss", v-html="questData.notes()")
|
||||
// .text-center.mt-4
|
||||
.world-boss-info-button(@click="showWorldBossInfo()") {{$t('whatIsWorldBoss') }}
|
||||
|
||||
.sleep.below-header-sections
|
||||
strong(v-once) {{ $t('sleepDescription') }}
|
||||
ul
|
||||
li(v-once) {{ $t('sleepBullet1') }}
|
||||
li(v-once) {{ $t('sleepBullet2') }}
|
||||
li(v-once) {{ $t('sleepBullet3') }}
|
||||
li(v-once) {{ $t('sleepBullet4') }}
|
||||
button.btn.btn-secondary.pause-button(v-if='!user.preferences.sleep', @click='toggleSleep()', v-once) {{ $t('pauseDailies') }}
|
||||
button.btn.btn-secondary.pause-button(v-if='user.preferences.sleep', @click='toggleSleep()', v-once) {{ $t('unpauseDailies') }}
|
||||
.px-3
|
||||
sidebar-section(:title="$t('staffAndModerators')")
|
||||
.row
|
||||
.col-4.staff(v-for='user in staff', :class='{staff: user.type === "Staff", moderator: user.type === "Moderator", bailey: user.name === "It\'s Bailey"}')
|
||||
div
|
||||
router-link.title(:to="{'name': 'userProfile', 'params': {'userId': user.uuid}}") {{user.name}}
|
||||
.svg-icon.staff-icon(v-html="icons.tierStaff", v-if='user.type === "Staff"')
|
||||
.svg-icon.mod-icon(v-html="icons.tierMod", v-if='user.type === "Moderator" && user.name !== "It\'s Bailey"')
|
||||
.svg-icon.npc-icon(v-html="icons.tierNPC", v-if='user.name === "It\'s Bailey"')
|
||||
.type {{user.type}}
|
||||
|
||||
sidebar-section(:title="$t('helpfulLinks')")
|
||||
ul
|
||||
li
|
||||
a(href='', @click.prevent='modForm()') {{ $t('contactForm') }}
|
||||
li
|
||||
router-link(to='/static/community-guidelines', v-once) {{ $t('communityGuidelinesLink') }}
|
||||
li
|
||||
router-link(to="/groups/guild/f2db2a7f-13c5-454d-b3ee-ea1f5089e601") {{ $t('lookingForGroup') }}
|
||||
li
|
||||
router-link(to='/static/faq', v-once) {{ $t('faq') }}
|
||||
li
|
||||
a(href='', v-html="$t('glossary')")
|
||||
li
|
||||
a(href='http://habitica.fandom.com/wiki/Habitica_Wiki' target='_blank', v-once) {{ $t('wiki') }}
|
||||
li
|
||||
a(href='https://oldgods.net/habitrpg/habitrpg_user_data_display.html', target='_blank', v-once) {{ $t('dataDisplayTool') }}
|
||||
li
|
||||
router-link(to="/groups/guild/a29da26b-37de-4a71-b0c6-48e72a900dac") {{ $t('reportProblem') }}
|
||||
li
|
||||
a(href='https://trello.com/c/odmhIqyW/440-read-first-table-of-contents', target='_blank', v-once) {{ $t('requestFeature') }}
|
||||
li
|
||||
a(href='', v-html="$t('communityForum')")
|
||||
li
|
||||
router-link(to="/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a") {{ $t('askQuestionGuild') }}
|
||||
|
||||
sidebar-section(:title="$t('playerTiers')")
|
||||
.row
|
||||
.col-12
|
||||
p(v-once) {{ $t('playerTiersDesc') }}
|
||||
ul.tier-list
|
||||
li.tier1(v-once)
|
||||
| {{ $t('tier1') }}
|
||||
.svg-icon.tier1-icon(v-html="icons.tier1")
|
||||
li.tier2(v-once)
|
||||
| {{ $t('tier2') }}
|
||||
.svg-icon.tier2-icon(v-html="icons.tier2")
|
||||
li.tier3(v-once)
|
||||
| {{ $t('tier3') }}
|
||||
.svg-icon.tier3-icon(v-html="icons.tier3")
|
||||
li.tier4(v-once)
|
||||
| {{ $t('tier4') }}
|
||||
.svg-icon.tier4-icon(v-html="icons.tier4")
|
||||
li.tier5(v-once)
|
||||
| {{ $t('tier5') }}
|
||||
.svg-icon.tier5-icon(v-html="icons.tier5")
|
||||
li.tier6(v-once)
|
||||
| {{ $t('tier6') }}
|
||||
.svg-icon.tier6-icon(v-html="icons.tier6")
|
||||
li.tier7(v-once)
|
||||
| {{ $t('tier7') }}
|
||||
.svg-icon.tier7-icon(v-html="icons.tier7")
|
||||
li.moderator(v-once)
|
||||
| {{ $t('tierModerator') }}
|
||||
.svg-icon.mod-icon(v-html="icons.tierMod")
|
||||
li.staff(v-once)
|
||||
| {{ $t('tierStaff') }}
|
||||
.svg-icon.staff-icon(v-html="icons.tierStaff")
|
||||
li.npc(v-once)
|
||||
| {{ $t('tierNPC') }}
|
||||
.svg-icon.npc-icon(v-html="icons.tierNPC")
|
||||
</template>
|
||||
|
||||
<style lang='scss' scoped>
|
||||
@import '~client/assets/scss/colors.scss';
|
||||
@import '~client/assets/scss/variables.scss';
|
||||
|
||||
h1 {
|
||||
color: $purple-200;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
background-color: $gray-600;
|
||||
padding: 0em;
|
||||
|
||||
.below-header-sections {
|
||||
padding: 1em 1.75em 1em 1.75em;
|
||||
}
|
||||
}
|
||||
|
||||
.pause-button {
|
||||
background-color: #ffb445 !important;
|
||||
color: $white;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grassy-meadow-backdrop {
|
||||
background-image: url('~assets/images/npc/#{$npc_tavern_flavor}/tavern_background.png');
|
||||
background-repeat: repeat-x;
|
||||
width: 100%;
|
||||
height: 246px;
|
||||
}
|
||||
|
||||
.daniel_front {
|
||||
background-image: url('~assets/images/npc/#{$npc_tavern_flavor}/tavern_npc.png');
|
||||
height: 246px;
|
||||
width: 471px;
|
||||
background-repeat: no-repeat;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.svg-icon {
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-left: .5em;
|
||||
}
|
||||
|
||||
.tier1-icon, .tier2-icon {
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.tier5-icon, .tier6-icon {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.tier7-icon {
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
.mod-icon {
|
||||
width: 13px;
|
||||
}
|
||||
|
||||
.npc-icon {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
.boss-icon {
|
||||
width: 16px;
|
||||
margin-top: .1em;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.boss-icon-large {
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.staff {
|
||||
margin-bottom: 1em;
|
||||
|
||||
.staff-icon {
|
||||
width: 11px;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #6133b4;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
.tier-list {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
width: 98%;
|
||||
|
||||
li {
|
||||
border-radius: 2px;
|
||||
background-color: #edecee;
|
||||
border: solid 1px #c3c0c7;
|
||||
text-align: center;
|
||||
padding: 1em;
|
||||
margin-bottom: 1em;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.tier1 {
|
||||
color: #c42870;
|
||||
}
|
||||
|
||||
.tier2 {
|
||||
color: #b01515;
|
||||
}
|
||||
|
||||
.tier3 {
|
||||
color: #d70e14;
|
||||
}
|
||||
|
||||
.tier4 {
|
||||
color: #c24d00;
|
||||
}
|
||||
|
||||
.tier5 {
|
||||
color: #9e650f;
|
||||
}
|
||||
|
||||
.tier6 {
|
||||
color: #2b8363;
|
||||
}
|
||||
|
||||
.tier7 {
|
||||
color: #167e87;
|
||||
}
|
||||
|
||||
.tier8, .moderator {
|
||||
color: #277eab;
|
||||
}
|
||||
|
||||
.tier9, .staff {
|
||||
color: #6133b4;
|
||||
}
|
||||
|
||||
.npc {
|
||||
color: $black;
|
||||
}
|
||||
}
|
||||
|
||||
.staff .title {
|
||||
color: #6133b4;
|
||||
}
|
||||
|
||||
.moderator .title {
|
||||
color: #277eab;
|
||||
}
|
||||
|
||||
.bailey .title {
|
||||
color: $black;
|
||||
}
|
||||
|
||||
.boss-section {
|
||||
padding: 1.75em;
|
||||
}
|
||||
|
||||
.world-boss {
|
||||
color: $white;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
outline-style: solid;
|
||||
outline-width: 2px;
|
||||
margin: 2px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.quest-boss {
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
.grey-progress-bar {
|
||||
width: 100%;
|
||||
height: 15px;
|
||||
background-color: rgba(255, 255, 255, 0.24);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.boss-health-bar {
|
||||
width: 80%;
|
||||
height: 15px;
|
||||
margin-bottom: .5em;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
background-color: #f74e52;
|
||||
}
|
||||
|
||||
.boss-health-bar.rage-bar {
|
||||
background-color: #ff944c;
|
||||
}
|
||||
|
||||
.boss-gradient {
|
||||
background-image: linear-gradient(to bottom, #401f2a, #931f4d);
|
||||
margin-top: -1.4em;
|
||||
}
|
||||
|
||||
.boss-description {
|
||||
border-top: 1px solid;
|
||||
margin-left: -16px;
|
||||
margin-right: -16px;
|
||||
padding: .25em 0 0 .25em;
|
||||
}
|
||||
|
||||
.float-bar {
|
||||
position: relative;
|
||||
top: -16px;
|
||||
width: 162px;
|
||||
height: 28px;
|
||||
border-radius: 2px;
|
||||
background-color: inherit;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.corner-decoration {
|
||||
position: absolute;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: inherit;
|
||||
border: inherit;
|
||||
outline: inherit;
|
||||
}
|
||||
|
||||
.reverse {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.diamond {
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
background-color: #dc4069;
|
||||
border: solid 2px #931f4d;
|
||||
}
|
||||
|
||||
.reduce {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.rage-strike {
|
||||
max-width: 50px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.rage-strike-active {
|
||||
max-width: 75px;
|
||||
height: auto;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.world-boss-info-button {
|
||||
width: 100%;
|
||||
background-color: $gray-500;
|
||||
border-radius: 2px;
|
||||
font-size: 14px;
|
||||
color: $blue-10;
|
||||
padding: 1em;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'client/libs/store';
|
||||
import { goToModForm } from 'client/libs/modform';
|
||||
|
||||
import { TAVERN_ID } from '../../../common/script/constants';
|
||||
import worldBossInfoModal from '../world-boss/worldBossInfoModal';
|
||||
import worldBossRageModal from '../world-boss/worldBossRageModal';
|
||||
import sidebarSection from '../sidebarSection';
|
||||
import chat from './chat';
|
||||
|
||||
|
||||
import challengeIcon from 'assets/svg/challenge.svg';
|
||||
import chevronIcon from 'assets/svg/chevron-red.svg';
|
||||
import gemIcon from 'assets/svg/gem.svg';
|
||||
import healthIcon from 'assets/svg/health.svg';
|
||||
import informationIconRed from 'assets/svg/information-red.svg';
|
||||
import questBackground from 'assets/svg/quest-background-border.svg';
|
||||
import rageIcon from 'assets/svg/rage.svg';
|
||||
import swordIcon from 'assets/svg/sword.svg';
|
||||
|
||||
import tier1 from 'assets/svg/tier-1.svg';
|
||||
import tier2 from 'assets/svg/tier-2.svg';
|
||||
import tier3 from 'assets/svg/tier-3.svg';
|
||||
import tier4 from 'assets/svg/tier-4.svg';
|
||||
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 tierMod from 'assets/svg/tier-mod.svg';
|
||||
import tierNPC from 'assets/svg/tier-npc.svg';
|
||||
import tierStaff from 'assets/svg/tier-staff.svg';
|
||||
|
||||
import quests from 'common/script/content/quests';
|
||||
import staffList from '../../libs/staffList';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
worldBossInfoModal,
|
||||
worldBossRageModal,
|
||||
sidebarSection,
|
||||
chat,
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
groupId: TAVERN_ID,
|
||||
icons: Object.freeze({
|
||||
challengeIcon,
|
||||
chevronIcon,
|
||||
gem: gemIcon,
|
||||
healthIcon,
|
||||
informationIcon: informationIconRed,
|
||||
questBackground,
|
||||
rageIcon,
|
||||
swordIcon,
|
||||
tier1,
|
||||
tier2,
|
||||
tier3,
|
||||
tier4,
|
||||
tier5,
|
||||
tier6,
|
||||
tier7,
|
||||
tierMod,
|
||||
tierNPC,
|
||||
tierStaff,
|
||||
}),
|
||||
group: {
|
||||
chat: [],
|
||||
},
|
||||
sections: {
|
||||
worldBoss: true,
|
||||
},
|
||||
staff: staffList,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({user: 'user.data'}),
|
||||
questData () {
|
||||
if (!this.group.quest) return {};
|
||||
return quests.quests[this.group.quest.key];
|
||||
},
|
||||
},
|
||||
async mounted () {
|
||||
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
|
||||
},
|
||||
methods: {
|
||||
modForm () {
|
||||
goToModForm(this.user);
|
||||
},
|
||||
toggleSleep () {
|
||||
this.$store.dispatch('user:sleep');
|
||||
},
|
||||
|
||||
pendingDamage () {
|
||||
if (!this.user.party.quest.progress.up) return 0;
|
||||
return this.$options.filters.floor(this.user.party.quest.progress.up, 10);
|
||||
// keep user's pending damage consistent with how it's displayed on the party page
|
||||
},
|
||||
bossCurrentHealth () {
|
||||
if (!this.group.quest.progress.hp) return 0;
|
||||
|
||||
return Math.ceil(parseFloat(this.group.quest.progress.hp)).toLocaleString();
|
||||
},
|
||||
bossCurrentRage () {
|
||||
if (!this.group.quest.progress.hp) return 0;
|
||||
|
||||
return Math.floor(parseFloat(this.group.quest.progress.rage)).toLocaleString();
|
||||
},
|
||||
showWorldBossInfo () {
|
||||
this.$root.$emit('bv::show::modal', 'world-boss-info');
|
||||
},
|
||||
showWorldBossRage (npc) {
|
||||
if (this.group.quest.extra.worldDmg[npc]) {
|
||||
this.$store.state.rageModalOptions.npc = npc;
|
||||
this.$root.$emit('bv::show::modal', 'world-boss-rage');
|
||||
}
|
||||
},
|
||||
async fetchRecentMessages () {
|
||||
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user