New client many fixes (#8981)

* Footer style fixes

* Limited string display

* Fixed background reload

* Began adding more avatar items

* Fixed challenge updated cats and official to be seen by admins

* Fixed min prize

* Fixed required fields

* Added my challenges and find challenges to menu

* Removed nav to party page when have no party

* Updated user and notifications icon

* Added accept, reject and messages

* Added selfcare

* Underline links

* Added forgot password

* Fixed task adding

* Disabled habit options that should be

* Added markdown to tags

* Added confirm to delete

* Fixed cancel/delete style

* Fixed rounding

* Added gold icon

* Fixed task icon styles

* Fixed margin botton

* Fixed some repeat styles

* Fixed custom reward style

* Hide like count 0

* Added new tavern images

* Redirect to party page after create

* Hid leader options from non leaders

* Removed manager options from non group plan

* Fixed some nav styles

* Fixed overlay color

* Prevented edit data from being transfered to create

* Added hover state for spells

* Add performance fixes for chat avatars

* Fixed merge conflicts

* Updated fron navigation

* Fixed reg gryphon logo

* Began adding gem modal functionality

* Added purchase gems with gold

* Fixed restore

* Replaced description with summary

* Spells that target tasks fix

* Added initial challenge task load

* Fixed lint issue
This commit is contained in:
Keith Holliday
2017-08-22 21:58:13 -06:00
committed by GitHub
parent 69662f84df
commit f529a5c64c
42 changed files with 586 additions and 305 deletions

View File

@@ -21,6 +21,13 @@
}
</style>
<style>
.modal-backdrop.show {
opacity: 1 !important;
background-color: rgba(67, 40, 116, 0.9) !important;
}
</style>
<script>
import axios from 'axios';
import AppMenu from './components/appMenu';

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="20" viewBox="0 0 24 20">
<path fill="#C3C0C7" fill-rule="evenodd" d="M13 16h2v-2h-2v2zm-4 0h2v-2H9v2zm-4 0h2v-2H5v2zm12-4h2v-2h-2v2zm-4 0h2v-2h-2v2zm-4 0h2v-2H9v2zm13-4H2v8a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8zm2-2v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1V0h2v2h10V0h2v2h1a4 4 0 0 1 4 4zM5 12h2v-2H5v2z"/>
<path fill-rule="evenodd" d="M13 16h2v-2h-2v2zm-4 0h2v-2H9v2zm-4 0h2v-2H5v2zm12-4h2v-2h-2v2zm-4 0h2v-2h-2v2zm-4 0h2v-2H9v2zm13-4H2v8a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8zm2-2v10a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4V6a4 4 0 0 1 4-4h1V0h2v2h10V0h2v2h1a4 4 0 0 1 4 4zM5 12h2v-2H5v2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 382 B

After

Width:  |  Height:  |  Size: 367 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="30" height="20" viewBox="0 0 30 20">
<path fill="#C3C0C7" fill-rule="evenodd" d="M11 11H9v2H7v-2H5V9h2V7h2v2h2v2zm8 0h6V9h-6v2zm9 5c0 1.103-.897 2-2 2H16V2h10c1.103 0 2 .897 2 2v12zM4 18c-1.103 0-2-.897-2-2V4c0-1.103.897-2 2-2h10v16H4zM26 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h22a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
<path fill-rule="evenodd" d="M11 11H9v2H7v-2H5V9h2V7h2v2h2v2zm8 0h6V9h-6v2zm9 5c0 1.103-.897 2-2 2H16V2h10c1.103 0 2 .897 2 2v12zM4 18c-1.103 0-2-.897-2-2V4c0-1.103.897-2 2-2h10v16H4zM26 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h22a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 369 B

After

Width:  |  Height:  |  Size: 354 B

7
website/client/assets/svg/notifications.svg Executable file → Normal file
View File

@@ -1,4 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>notifications</title>
<path d="M26 4h-20c-1.7 0-3 1.3-3 3v14c0 1.7 1.3 3 3 3h5l2.9 3.1c1.2 1.2 3.1 1.2 4.2 0l2.9-3.1h5c1.7 0 3-1.3 3-3v-14c0-1.7-1.3-3-3-3v0zM27 21c0 0.6-0.4 1-1 1h-5.9l-3.5 3.7c-0.2 0.2-0.4 0.3-0.6 0.3s-0.4-0.1-0.7-0.3l-3.4-3.7h-5.9c-0.6 0-1-0.4-1-1v-14c0-0.6 0.4-1 1-1h20c0.6 0 1 0.4 1 1v14zM24 11h-16v-2h16v2zM24 15h-16v-2h16v2zM24 19h-16v-2h16v2z"></path>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path fill="#D5C8FF" fill-rule="evenodd" d="M21 0H3C1.3 0 0 1.3 0 3v14c0 1.7 1.3 3 3 3h4l2.9 3.1c1.2 1.2 3.1 1.2 4.2 0L17 20h4c1.7 0 3-1.3 3-3V3c0-1.7-1.3-3-3-3zm1 17c0 .6-.4 1-1 1h-4.9l-3.5 3.7c-.2.2-.4.3-.6.3-.2 0-.4-.1-.7-.3L7.9 18H3c-.6 0-1-.4-1-1V3c0-.6.4-1 1-1h18c.6 0 1 .4 1 1v14zM19 7H5V5h14v2zm0 4H5V9h14v2zm0 4H5v-2h14v2z"/>
</svg>

Before

Width:  |  Height:  |  Size: 487 B

After

Width:  |  Height:  |  Size: 430 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="26" height="20" viewBox="0 0 26 20">
<path fill="#C3C0C7" fill-rule="evenodd" d="M24 10h-8V8h4a2 2 0 0 0 2-2V2c1.103 0 2 .897 2 2v6zm0 6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4h8v1a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-1h8v4zM2 4c0-1.103.897-2 2-2v4a2 2 0 0 0 2 2h4v2H2V4zm10 9h2V8h-2v5zm8-11v4H6V2h14zm2-2H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
<path fill-rule="evenodd" d="M24 10h-8V8h4a2 2 0 0 0 2-2V2c1.103 0 2 .897 2 2v6zm0 6a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2v-4h8v1a2 2 0 0 0 2 2h2a2 2 0 0 0 2-2v-1h8v4zM2 4c0-1.103.897-2 2-2v4a2 2 0 0 0 2 2h4v2H2V4zm10 9h2V8h-2v5zm8-11v4H6V2h14zm2-2H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h18a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 406 B

View File

@@ -1,3 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20">
<path fill="#C3C0C7" fill-rule="evenodd" d="M8.343 14.916c-.312 0-.61-.123-.831-.344l-3.831-3.831 1.662-1.662 2.934 2.934 5.938-6.929L16 6.613l-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001zM18 16c0 1.103-.897 2-2 2H4c-1.102 0-2-.897-2-2V4c0-1.103.898-2 2-2h12c1.103 0 2 .897 2 2v12zM16 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
<path fill-rule="evenodd" d="M8.343 14.916c-.312 0-.61-.123-.831-.344l-3.831-3.831 1.662-1.662 2.934 2.934 5.938-6.929L16 6.613l-6.764 7.893a1.182 1.182 0 0 1-.848.409l-.045.001zM18 16c0 1.103-.897 2-2 2H4c-1.102 0-2-.897-2-2V4c0-1.103.898-2 2-2h12c1.103 0 2 .897 2 2v12zM16 0H4a4 4 0 0 0-4 4v12a4 4 0 0 0 4 4h12a4 4 0 0 0 4-4V4a4 4 0 0 0-4-4z"/>
</svg>

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 442 B

5
website/client/assets/svg/user.svg Executable file → Normal file
View File

@@ -1,4 +1,3 @@
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<title>user</title>
<path d="M20 17h-0.4c1.9-1.2 3.3-3.3 3.4-5.8 0.1-3.8-3.1-7.2-6.9-7.2-4 0-7.1 3.1-7.1 7 0 2.6 1.3 4.8 3.4 6h-0.4c-3.9 0-7 3.1-7 7v1c0 1.7 1.3 3 3 3h16c1.7 0 3-1.3 3-3v-1c0-3.9-3.1-7-7-7v0zM11 11c0-2.8 2.2-5 5-5s5 2.2 5 5c0 2.8-2.2 5-5 5s-5-2.2-5-5v0zM24 26h-16c-0.6 0-1-0.4-1-1v-1c0-2.8 2.2-5 5-5h8c2.8 0 5 2.2 5 5v1c0 0.6-0.4 1-1 1v0z"></path>
<svg xmlns="http://www.w3.org/2000/svg" width="22" height="24" viewBox="0 0 22 24">
<path fill-rule="evenodd" d="M15 13h-.4c1.9-1.2 3.3-3.3 3.4-5.8.1-3.8-3.1-7.2-6.9-7.2C7.1 0 4 3.1 4 7c0 2.6 1.3 4.8 3.4 6H7c-3.9 0-7 3.1-7 7v1c0 1.7 1.3 3 3 3h16c1.7 0 3-1.3 3-3v-1c0-3.9-3.1-7-7-7zM6 7c0-2.8 2.2-5 5-5s5 2.2 5 5-2.2 5-5 5-5-2.2-5-5zm13 15H3c-.6 0-1-.4-1-1v-1c0-2.8 2.2-5 5-5h8c2.8 0 5 2.2 5 5v1c0 .6-.4 1-1 1z"/>
</svg>

Before

Width:  |  Height:  |  Size: 469 B

After

Width:  |  Height:  |  Size: 424 B

View File

@@ -113,7 +113,8 @@
<style lang="scss" scoped>
footer {
background-color: #e1e0e3;
height: 376px;
min-height: 408px;
width: 100%;
padding-left: 6em;
padding-right: 6em;
padding-top: 3em;

View File

@@ -1,7 +1,7 @@
<template lang="pug">
#app-header.row
create-party-modal
members-modal(:group='user.party', :hide-badge="true")
members-modal(:hide-badge="true")
member-details(:member="user")
.view-party(v-if="user.party && user.party._id && partyMembers && partyMembers.length > 1")
// TODO button should open the party members modal
@@ -124,6 +124,8 @@ export default {
openPartyModal () {
if (this.user.party._id) {
this.$store.state.groupId = this.user.party._id;
// @TODO: do we need to fetch party?
this.$store.state.memberModalOptions.group = this.$store.state.party;
this.$root.$emit('show::modal', 'members-modal');
return;
}

View File

@@ -22,7 +22,9 @@ div
router-link.dropdown-item(:to="{name: 'quests'}") {{ $t('quests') }}
router-link.dropdown-item(:to="{name: 'seasonal'}") {{ $t('titleSeasonalShop') }}
router-link.dropdown-item(:to="{name: 'time'}") {{ $t('titleTimeTravelers') }}
router-link.nav-item(tag="li", :to="{name: 'party'}")
router-link.nav-item(tag="li", :to="{name: 'party'}", v-if='this.user.party._id')
a.nav-link(v-once) {{ $t('party') }}
.nav-item(@click='openPartyModal()', v-if='!this.user.party._id')
a.nav-link(v-once) {{ $t('party') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'tavern'}", :class="{'active': $route.path.startsWith('/guilds')}")
a.nav-link(v-once) {{ $t('guilds') }}
@@ -30,15 +32,15 @@ div
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item.dropdown(
tag="li",
:to="{name: 'groupPlan'}",
:class="{'active': $route.path.startsWith('/group-plan')}")
a.nav-link(v-once) {{ $t('group') }}
.dropdown-menu
router-link.dropdown-item(v-for='group in groupPlans', :key='group._id', :to="{name: 'groupPlanDetailTaskInformation', params: {groupId: group._id}}") {{ group.name }}
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
.nav-item.dropdown(tag="li", :class="{'active': $route.path.startsWith('/group-plans')}")
a.nav-link(v-once) {{ $t('group') }}
.dropdown-menu
router-link.dropdown-item(v-for='group in groupPlans', :key='group._id', :to="{name: 'groupPlanDetailTaskInformation', params: {groupId: group._id}}") {{ group.name }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'myChallenges'}", :class="{'active': $route.path.startsWith('/challenges')}")
a.nav-link(v-once) {{ $t('challenges') }}
.dropdown-menu
router-link.dropdown-item(:to="{name: 'myChallenges'}") {{ $t('myChallenges') }}
router-link.dropdown-item(:to="{name: 'findChallenges'}") {{ $t('findChallenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}", :to="{name: 'faq'}")
a.nav-link(v-once) {{ $t('help') }}
.dropdown-menu
@@ -57,10 +59,10 @@ div
span {{userGems | roundBigNumber}}
.item-with-icon
.svg-icon(v-html="icons.gold")
span {{user.stats.gp | roundBigNumber}}
span {{Math.floor(user.stats.gp * 100) / 100}}
notification-menu
a.dropdown.item-with-icon.item-user
.svg-icon(v-html="icons.user")
.svg-icon.user(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
a.dropdown-item.edit-avatar.dropdown-separated(@click='showAvatar()')
h3 {{ user.profile.name }}
@@ -173,11 +175,12 @@ div
padding-left: 16px;
.svg-icon {
vertical-align: middle;
width: 24px;
height: 24px;
vertical-align: bottom;
display: inline-block;
width: 20px;
height: 20px;
margin-right: 8px;
float: left;
margin-left: 8px;
}
}
@@ -187,13 +190,13 @@ div
color: $header-color;
transition: none;
&:hover {
color: $white;
}
.svg-icon {
margin-right: 0px;
color: $white;
color: $header-color;
&:hover {
color: $white;
}
}
}
@@ -276,6 +279,9 @@ export default {
async getUserGroupPlans () {
this.groupPlans = await this.$store.dispatch('guilds:getGroupPlans');
},
openPartyModal () {
this.$root.$emit('show::modal', 'create-party-modal');
},
showBuyGemsModal () {
this.$root.$emit('show::modal', 'buy-gems');
},

View File

@@ -3,10 +3,10 @@
#top-background
.seamless_stars_varied_opacity_repeat
form#login-form(v-on:submit.prevent='handleSubmit', @keyup.enter="handleSubmit")
form#login-form(v-on:submit.prevent='handleSubmit', @keyup.enter="handleSubmit", v-if='!forgotPassword')
.text-center
div
.svg-icon.gryphon(v-html="icons.gryphon")
.svg-icon.gryphon
div
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
.form-group.row.text-center
@@ -29,6 +29,7 @@
input#emailInput.form-control(type='email', :placeholder='$t("emailPlaceholder")', v-model='email')
.form-group
label(for='passwordInput', v-once) {{$t('password')}}
a.float-right.forgot-password(v-once, v-if='!registering', @click='forgotPassword = true') {{$t('forgotPassword')}}
input#passwordInput.form-control(type='password', :placeholder='$t("passwordPlaceholder")', v-model='password')
.form-group(v-if='registering')
label(for='confirmPasswordInput', v-once) {{$t('confirmPassword')}}
@@ -43,6 +44,21 @@
router-link(:to="{name: 'register'}", v-if='!registering', exact)
a.toggle-link(v-once) Don't have an account? Join Habitica!
form#forgot-form(v-on:submit.prevent='handleSubmit', @keyup.enter="handleSubmit", v-if='forgotPassword')
.text-center
div
.svg-icon.gryphon
div
.svg-icon.habitica-logo(v-html="icons.habiticaIcon")
.header
h2 Email a Password Reset Link
p Enter the email address you used to register your Habitica account.
.form-group.row.text-center
label(for='usernameInput', v-once) {{$t('email')}}
input#usernameInput.form-control(type='text', :placeholder='$t("emailPlaceholder")', v-model='username')
.text-center
.btn.btn-info(@click='forgotPasswordLink()', v-once) {{$t('sendLink')}}
#bottom-wrap
#bottom-background
.seamless_mountains_demo_repeat
@@ -56,6 +72,7 @@
small a {
color: #fff;
text-decoration: underline;
}
</style>
@@ -85,7 +102,7 @@
color: $purple-400;
}
#login-form {
#login-form, #forgot-form {
margin: 0 auto;
width: 40em;
padding-top: 5em;
@@ -93,9 +110,20 @@
position: relative;
z-index: 1;
.header {
h2 {
color: $white;
}
color: $white;
}
.gryphon {
background-image: url('~assets/images/melior@3x.png');
width: 63.2px;
height: 69.4px;
background-size: cover;
color: $white;
margin: 0 auto;
}
@@ -188,9 +216,14 @@
.toggle-link {
color: #fff !important;
}
.forgot-password {
color: #bda8ff !important;
}
</style>
<script>
import axios from 'axios';
import hello from 'hellojs';
import gryphon from 'assets/svg/gryphon.svg';
@@ -205,6 +238,7 @@ export default {
email: '',
password: '',
passwordConfirm: '',
forgotPassword: false,
};
data.icons = Object.freeze({
@@ -282,8 +316,25 @@ export default {
return;
}
if (this.forgotPassword) {
this.forgotPasswordLink();
return;
}
this.login();
},
async forgotPasswordLink () {
if (!this.username) {
alert('Email is required');
return;
}
await axios.post('/api/v3/user/reset-password', {
email: this.username,
});
alert(this.$t('newPassSent'));
},
},
};
</script>

View File

@@ -250,13 +250,17 @@ export default {
this.tasksByType[task.type].splice(index, 1, task);
},
showMemberModal () {
this.$store.state.groupId = 'challenge'; // @TODO: change these terrible settings
this.$store.state.viewingMembers = this.members;
this.$store.state.memberModalOptions.groupId = 'challenge'; // @TODO: change these terrible settings
this.$store.state.memberModalOptions.group = this.group;
this.$store.state.memberModalOptions.viewingMembers = this.members;
this.$root.$emit('show::modal', 'members-modal');
},
async joinChallenge () {
this.user.challenges.push(this.challengeId);
await this.$store.dispatch('challenges:joinChallenge', {challengeId: this.challengeId});
// @TODO: this doesn't work because of asyncresource
let tasks = await this.$store.dispatch('tasks:fetchUserTasks');
this.$store.state.tasks.data = tasks.data;
},
async leaveChallenge () {
let keepChallenge = confirm('Do you want to keep challenge tasks?');

View File

@@ -24,7 +24,7 @@
div Challenge Prize
.row.description
.col-12
| {{challenge.description}}
| {{challenge.summary}}
.well.row
.col-3
.count-details
@@ -104,7 +104,8 @@
.description {
color: $gray-200;
margin-bottom: 2em;
margin-top: 1em;
margin-bottom: 1em;
overflow: hidden;
}

View File

@@ -34,9 +34,12 @@
.form-check(
v-for="group in categoryOptions",
:key="group.key",
v-if='group.key !== "habitica_official" || user.contributor.admin'
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", :value="group.key" v-model="workingChallenge.categories")
input.custom-control-input(type="checkbox",
:value='group.key',
v-model="workingChallenge.categories")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t(group.label) }}
button.btn.btn-primary(@click.prevent="toggleCategorySelect") {{$t('close')}}
@@ -47,7 +50,7 @@
.form-group
label
strong(v-once) {{$t('prize')}}
input(type='number', min='1', :max='maxPrize', v-model="workingChallenge.prize")
input(type='number', :min='minPrize', :max='maxPrize', v-model="workingChallenge.prize")
.row.footer-wrap
.col-12.text-center.submit-button-wrapper
.alert.alert-warning(v-if='insufficientGemsForTavernChallenge')
@@ -220,6 +223,13 @@ export default {
if (this.challenge) {
Object.assign(this.workingChallenge, this.challenge);
this.workingChallenge.categories = [];
if (this.challenge.categories) {
this.challenge.categories.forEach(category => {
this.workingChallenge.categories.push(category.slug);
});
}
this.creating = false;
}
});
@@ -230,6 +240,7 @@ export default {
this.groups.push({
name: party.name,
_id: party._id,
privacy: 'private',
});
}
@@ -256,7 +267,13 @@ export default {
userBalance = userBalance * 4;
let groupBalance = 0;
let group = find(this.groups, { _id: this.workingChallenge.group });
let group;
this.groups.forEach(item => {
if (item._id === this.workingChallenge.group) {
group = item;
return;
}
});
if (group && group.balance && group.leader === this.user._id) {
groupBalance = group.balance * 4;
@@ -264,6 +281,18 @@ export default {
return userBalance + groupBalance;
},
minPrize () {
let groupFound;
this.groups.forEach(group => {
if (group._id === this.workingChallenge.group) {
groupFound = group;
return;
}
});
if (groupFound && groupFound.privacy === 'private') return 0;
return 1;
},
insufficientGemsForTavernChallenge () {
let balance = this.user.balance || 0;
let isForTavern = this.workingChallenge.group === TAVERN_ID;
@@ -320,6 +349,17 @@ export default {
}
},
updateChallenge () {
let categoryKeys = this.workingChallenge.categories;
let serverCategories = [];
categoryKeys.forEach(key => {
let catName = this.categoriesHashByKey[key];
serverCategories.push({
slug: key,
name: catName,
});
});
this.workingChallenge.categories = serverCategories;
this.$emit('updatedChallenge', {
challenge: this.workingChallenge,
});

View File

@@ -35,7 +35,7 @@
span.action(v-if='msg.uuid === user._id', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right
span.action.float-right(v-if='likeCount(msg) > 0')
.svg-icon(v-html="icons.liked")
| + {{ likeCount(msg) }}
.row(v-if='user._id === msg.uuid')
@@ -58,7 +58,7 @@
span.action(v-if='msg.uuid === user._id', @click='remove(msg, index)')
.svg-icon(v-html="icons.delete")
| {{$t('delete')}}
span.action.float-right
span.action.float-right(v-if='likeCount(msg) > 0')
.svg-icon(v-html="icons.liked")
| + {{ likeCount(msg) }}
.col-2
@@ -128,6 +128,7 @@ import axios from 'axios';
import moment from 'moment';
import cloneDeep from 'lodash/cloneDeep';
import { mapState } from 'client/libs/store';
import throttle from 'lodash/throttle';
import markdownDirective from 'client/directives/markdown';
import Avatar from '../avatar';
import styleHelper from 'client/mixins/styleHelper';
@@ -155,6 +156,14 @@ export default {
mounted () {
this.loadProfileCache();
},
created () {
window.addEventListener('scroll', throttle(() => {
this.loadProfileCache(window.scrollY / 1000);
}, 1000));
},
destroyed () {
// window.removeEventListener('scroll', this.handleScroll);
},
data () {
return {
icons: Object.freeze({
@@ -167,6 +176,8 @@ export default {
copyingMessage: {},
currentDayDividerDisplay: moment().day(),
cachedProfileData: {},
currentProfileLoadedCount: 0,
currentProfileLoadedEnd: 10,
};
},
filters: {
@@ -191,14 +202,22 @@ export default {
},
},
methods: {
async loadProfileCache () {
async loadProfileCache (screenPosition) {
let promises = [];
// @TODO: write an explination
if (screenPosition && Math.floor(screenPosition) + 1 > this.currentProfileLoadedEnd / 10) {
this.currentProfileLoadedEnd = 10 * (Math.floor(screenPosition) + 1);
} else {
return;
}
this.messages.forEach(message => {
let uuid = message.uuid;
if (uuid && !this.cachedProfileData[uuid]) {
if (uuid === 'system') return;
if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return;
promises.push(axios.get(`/api/v3/members/${uuid}`));
this.currentProfileLoadedCount += 1;
}
});

View File

@@ -53,8 +53,10 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
.slim_shirt_pink.option(@click='set({"preferences.shirt":"pink"})', :class='{active: user.preferences.shirt === "pink"}')
.slim_shirt_white.option(@click='set({"preferences.shirt":"white"})', :class='{active: user.preferences.shirt === "white"}')
.slim_shirt_yellow.option(@click='set({"preferences.shirt":"yellow"})', :class='{active: user.preferences.shirt === "yellow"}')
.col-12.premium-shirts(v-if='editing')
.broad_shirt_convict.option(@click='set({"preferences.shirt":"convict"})', :class='{active: user.preferences.shirt === "convict"}')
.col-12.customize-options
.option(v-for='option in ["convict", "cross", "fire", "horizon", "ocean", "purple", "rainbow", "redblue", "thunder", "tropical", "zombie"]',
:class='[`broad_shirt_${option}`, {active: user.preferences.shirt === option}]',
@click='set({"preferences.shirt": option})')
.section.customize-section(v-if='activeTopPage === "skin"')
.row.sub-menu.col-6.offset-3.text-center
@@ -141,6 +143,13 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
.hair_flower_4.option(@click='set({"preferences.hair.flower":4})', :class='{active: user.preferences.hair.flower === 4}')
.hair_flower_5.option(@click='set({"preferences.hair.flower":5})', :class='{active: user.preferences.hair.flower === 5}')
.hair_flower_6.option(@click='set({"preferences.hair.flower":6})', :class='{active: user.preferences.hair.flower === 6}')
.row(v-if='activeSubPage === "flower"')
.col-12.customize-options
// button.customize-option(ng-repeat='item in ::getGearArray("animal")', class='{{::item.key}}',
ng-class="{locked: user.items.gear.owned[item.key] == undefined, selectableInventory: user.preferences.costume ? user.items.gear.costume.headAccessory == item.key : user.items.gear.equipped.headAccessory == item.key}",
popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter',
popover-placement='right', popover-append-to-body='true',
ng-click='user.items.gear.owned[item.key] ? equip(item.key) : purchase(item.type,item)')
#backgrounds.section.container.customize-section(v-if='activeTopPage === "backgrounds"')
.row.sub-menu.col-6.offset-3
@@ -172,6 +181,7 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
@click.prevent.stop="togglePinned(bg)"
)
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
.col-12.text-center(v-if='!ownsSet("background", set.items) && set.identifier !== "incentiveBackgrounds"')
.gem-amount
.svg-icon.gem(v-html='icons.gem')
@@ -215,6 +225,11 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('creativity') }}
div
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('self_care') }}
.section.row.justin-message-section(:class='{top: modalPage > 1}')
.col-9
@@ -401,6 +416,10 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
border-radius: 2px;
}
.background:hover {
cursor: pointer;
}
.purchase-single {
width: 141px;
margin: 0 auto;
@@ -489,7 +508,6 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
}
}
.badge-svg {
left: calc((100% - 18px) / 2);
cursor: pointer;
@@ -559,22 +577,9 @@ export default {
data () {
let backgroundShopSets = getBackgroundShopSets();
// @TODO: add dates to backgrounds
let backgroundShopSetsByYear = {
2014: [],
2015: [],
2016: [],
2017: [],
};
backgroundShopSets.forEach((set) => {
let year = set.identifier.substr(set.identifier.length - 4);
if (!backgroundShopSetsByYear[year]) return;
backgroundShopSetsByYear[year].push(set);
});
return {
backgroundShopSets,
backgroundShopSetsByYear,
backgroundUpdate: new Date(),
icons: Object.freeze({
logoPurple,
bodyIcon,
@@ -610,6 +615,32 @@ export default {
startingPage () {
return this.$store.state.avatarEditorOptions.startingPage;
},
backgroundShopSetsByYear () {
// @TODO: add dates to backgrounds
let backgroundShopSetsByYear = {
2014: [],
2015: [],
2016: [],
2017: [],
};
// Hack to force update for now until we restructure the data
let backgroundUpdate = this.backgroundUpdate; // eslint-disable-line
this.backgroundShopSets.forEach((set) => {
let year = set.identifier.substr(set.identifier.length - 4);
if (!backgroundShopSetsByYear[year]) return;
let setOwnedByUser = false;
for (let key in set.items) {
if (this.user.purchased.background[key]) setOwnedByUser = true;
}
set.userOwns = setOwnedByUser;
backgroundShopSetsByYear[year].push(set);
});
return backgroundShopSetsByYear;
},
},
methods: {
prev () {
@@ -654,7 +685,6 @@ export default {
for (let key in set) {
let value = set[key];
if (type === 'background') key = value.key;
if (this.user.purchased[type][key]) setOwnedByUser = true;
}
@@ -697,6 +727,7 @@ export default {
path,
},
});
this.backgroundUpdate = new Date();
} catch (e) {
alert(e.message);
}

View File

@@ -172,9 +172,10 @@ export default {
};
group.name = this.$t('possessiveParty', {name: this.user.profile.name});
let party = await this.$store.dispatch('guilds:create', {group});
this.$store.state.party = party;
this.$store.state.party.data = party;
this.user.party._id = party._id;
this.$root.$emit('hide::modal', 'create-party-modal');
this.$router.push('/party');
// @TODO: Analytics.updateUser({'partyID': $scope.group ._id, 'partySize': 1});
},
},

View File

@@ -28,7 +28,7 @@
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
.sort-select {
margin: 2em;
}
@@ -120,6 +120,7 @@ export default {
this.loading = false;
},
createGroup () {
this.$store.state.editingGroup = {};
this.$root.$emit('show::modal', 'guild-form');
},
},

View File

@@ -578,7 +578,8 @@ export default {
this.newMessage = newText;
},
showMemberModal () {
this.$store.state.groupId = this.group._id;
this.$store.state.memberModalOptions.groupId = this.group._id;
this.$store.state.memberModalOptions.group = this.group;
this.$root.$emit('show::modal', 'members-modal');
},
async sendMessage () {
@@ -638,8 +639,7 @@ export default {
}
},
async join () {
// @TODO: This needs to be in the notifications where users will now accept invites
if (this.group.cancelledPlan && !confirm(window.env.t('aboutToJoinCancelledGroupPlan'))) {
if (this.group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
return;
}
await this.$store.dispatch('guilds:join', {guildId: this.group._id, type: 'myGuilds'});
@@ -685,11 +685,6 @@ export default {
await this.$store.dispatch('guilds:join', {groupId: this.group._id});
},
// @TODO: Move to notificatin component
async reject () {
await this.$store.dispatch('guilds:rejectInvite', {groupId: this.group._id});
// User.sync();
},
clickStartQuest () {
// Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Start a Quest'});
let hasQuests = find(this.user.items.quests, (quest) => {

View File

@@ -4,11 +4,11 @@
.form-group
label
strong(v-once) {{$t('name')}} *
b-form-input(type="text", :placeholder="$t('newGuildPlaceholder')", v-model="workingGuild.name")
.form-group(v-if='workingGuild.id && members.length > 0')
b-form-input(type="text", :placeholder="$t('newGuildPlaceholder')", v-model="workingGroup.name")
.form-group(v-if='workingGroup.id && members.length > 0')
label
strong(v-once) {{$t('guildOrPartyLeader')}} *
select.form-control(v-model="workingGuild.newLeader")
select.form-control(v-model="workingGroup.newLeader")
option(v-for='member in members', :value="member._id") {{ member.profile.name }}
.form-group
@@ -16,7 +16,7 @@
strong(v-once) {{$t('privacySettings')}} *
br
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="workingGuild.onlyLeaderCreatesChallenges")
input.custom-control-input(type="checkbox", v-model="workingGroup.onlyLeaderCreatesChallenges")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('onlyLeaderCreatesChallenges') }}
b-tooltip.icon(:content="$t('privateDescription')")
@@ -24,13 +24,13 @@
// br
// @TODO Implement in V2 label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", v-model="workingGuild.guildLeaderCantBeMessaged")
input.custom-control-input(type="checkbox", v-model="workingGroup.guildLeaderCantBeMessaged")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('guildLeaderCantBeMessaged') }}
br
label.custom-control.custom-checkbox(v-if='!isParty')
input.custom-control-input(type="checkbox", v-model="workingGuild.privateGuild")
input.custom-control-input(type="checkbox", v-model="workingGroup.privateGuild")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('privateGuild') }}
b-tooltip.icon(:content="$t('privateDescription')")
@@ -38,7 +38,7 @@
// br
// @TODO: Implement in v2 label.custom-control.custom-checkbox(v-if='!creatingParty')
input.custom-control-input(type="checkbox", v-model="workingGuild.allowGuildInvationsFromNonMembers")
input.custom-control-input(type="checkbox", v-model="workingGroup.allowGuildInvationsFromNonMembers")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $t('allowGuildInvationsFromNonMembers') }}
@@ -46,16 +46,16 @@
label
strong(v-once) {{$t('guildSummary')}} *
div.summary-count {{charactersRemaining}} {{ $t('charactersRemaining') }}
textarea.form-control.summary-textarea(:placeholder="$t('guildSummaryPlaceholder')", v-model="workingGuild.summary")
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 {{ $t('markdownFormattingHelp') }}
b-form-input.description-textarea(type="text", textarea, :placeholder="creatingParty ? $t('partyDescriptionPlaceholder') : $t('guildDescriptionPlaceholder')", v-model="workingGuild.description")
b-form-input.description-textarea(type="text", textarea, :placeholder="isParty ? $t('partyDescriptionPlaceholder') : $t('guildDescriptionPlaceholder')", v-model="workingGroup.description")
.form-group(v-if='creatingParty && !workingGuild.id')
.form-group(v-if='creatingParty && !workingGroup.id')
span
toggleSwitch(:label="$t('inviteMembersNow')", v-model='inviteMembers')
@@ -63,21 +63,21 @@
label
strong(v-once) {{$t('categories')}} *
div.category-wrap(@click.prevent="toggleCategorySelect")
span.category-select(v-if='workingGuild.categories.length === 0') {{$t('none')}}
.category-label(v-for='category in workingGuild.categories') {{$t(categoriesHashByKey[category])}}
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",
)
label.custom-control.custom-checkbox
input.custom-control-input(type="checkbox", :value="group.key", v-model="workingGuild.categories")
input.custom-control-input(type="checkbox", :value="group.key", v-model="workingGroup.categories")
span.custom-control-indicator
span.custom-control-description(v-once) {{ $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 && !workingGuild.id')
.form-group(v-if='inviteMembers && !workingGroup.id')
label
strong(v-once) Invite via Email or User ID
p(v-once) {{$t('inviteMembersHowTo')}} *
@@ -91,12 +91,12 @@
button(@click.prevent='addMemberToInvite()') Add
.form-group.text-center
div.item-with-icon(v-if='!this.workingGuild.id')
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='!workingGuild.id', :disabled='!workingGuild.name || !workingGuild.description') {{ creatingParty ? $t('createParty') : $t('createGuild') }}
button.btn.btn-primary.btn-md(v-if='workingGuild.id', :disabled='!workingGuild.name || !workingGuild.description') {{ isParty ? $t('updateParty') : $t('updateGuild') }}
.gem-description(v-once, v-if='!this.workingGuild.id') {{ $t('guildGemCostInfo') }}
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>
@@ -187,7 +187,7 @@ export default {
},
data () {
let data = {
workingGuild: {
workingGroup: {
id: '',
name: '',
type: 'guild',
@@ -286,25 +286,34 @@ export default {
// @TODO: do we need this? Maybe us computed. If we need, then make it on show a specific modal
this.$root.$on('shown::modal', () => {
let editingGroup = this.$store.state.editingGroup;
if (!editingGroup._id) return;
this.workingGuild.name = editingGroup.name;
this.workingGuild.type = editingGroup.type;
this.workingGuild.privacy = editingGroup.privacy;
if (editingGroup.summary) this.workingGuild.summary = editingGroup.summary;
if (editingGroup.description) this.workingGuild.description = editingGroup.description;
if (editingGroup._id) this.workingGuild.id = editingGroup._id;
if (editingGroup.leader._id) this.workingGuild.newLeader = editingGroup.leader._id;
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.summary) this.workingGroup.summary = editingGroup.summary;
if (editingGroup.description) this.workingGroup.description = editingGroup.description;
if (editingGroup._id) this.workingGroup.id = editingGroup._id;
if (editingGroup.leader._id) this.workingGroup.newLeader = editingGroup.leader._id;
if (editingGroup._id) this.getMembers();
});
},
computed: {
charactersRemaining () {
let currentLength = this.workingGuild.summary ? this.workingGuild.summary.length : 0;
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.workingGuild.id) return this.$t('createGuild');
if (!this.workingGroup.id) return this.$t('createGuild');
if (this.isParty) return this.$t('updateParty');
return this.$t('updateGuild');
},
@@ -312,14 +321,14 @@ export default {
return this.$store.state.groupFormOptions.createParty;
},
isParty () {
return this.workingGuild.type === 'party';
return this.workingGroup.type === 'party';
},
},
methods: {
async getMembers () {
if (!this.workingGuild.id) return;
if (!this.workingGroup.id) return;
let members = await this.$store.dispatch('members:getGroupMembers', {
groupId: this.workingGuild.id,
groupId: this.workingGroup.id,
includeAllPublicFields: true,
});
this.members = members;
@@ -339,51 +348,51 @@ export default {
this.showCategorySelect = !this.showCategorySelect;
},
async submit () {
if (this.$store.state.user.data.balance < 1 && !this.workingGuild.id) {
if (this.$store.state.user.data.balance < 1 && !this.workingGroup.id) {
// @TODO: Add proper notifications
alert('Not enough gems');
return;
// @TODO return $rootScope.openModal('buyGems', {track:"Gems > Create Group"});
}
if (!this.workingGuild.name || !this.workingGuild.description) {
if (!this.workingGroup.name || !this.workingGroup.description) {
// @TODO: Add proper notifications - split this out into two, make errors translatable. Suggestion: `<% fieldName %> is required` for all errors where possible, where `fieldName` is inserted as the translatable string that's used for the field header.
alert('Enter a name and description');
return;
}
if (!this.workingGuild.summary) {
if (!this.workingGroup.summary) {
// @TODO: Add proper notifications. Summary is mandatory for only public guilds (not tavern, private guilds, parties)
alert('Enter a summary');
return;
}
if (this.workingGuild.summary.length > MAX_SUMMARY_SIZE_FOR_GUILDS) {
if (this.workingGroup.summary.length > MAX_SUMMARY_SIZE_FOR_GUILDS) {
// @TODO: Add proper notifications. Summary is mandatory for only public guilds (not tavern, private guilds, parties)
alert('Summary is too long');
return;
}
if (!this.workingGuild.categories || this.workingGuild.categories.length === 0) {
if (!this.workingGroup.categories || this.workingGroup.categories.length === 0) {
// @TODO: Add proper notifications
alert('One or more categories must be selected');
return;
}
// @TODO: Add proper notifications
if (!this.workingGuild.id && !confirm(this.$t('confirmGuild'))) return;
if (!this.workingGroup.id && !confirm(this.$t('confirmGuild'))) return;
if (!this.workingGuild.privateGuild) {
this.workingGuild.privacy = 'public';
if (!this.workingGroup.privateGuild) {
this.workingGroup.privacy = 'public';
}
if (!this.workingGuild.onlyLeaderCreatesChallenges) {
this.workingGuild.leaderOnly = {
if (!this.workingGroup.onlyLeaderCreatesChallenges) {
this.workingGroup.leaderOnly = {
challenges: true,
};
}
let categoryKeys = this.workingGuild.categories;
let categoryKeys = this.workingGroup.categories;
let serverCategories = [];
categoryKeys.forEach(key => {
let catName = this.categoriesHashByKey[key];
@@ -392,22 +401,22 @@ export default {
name: catName,
});
});
this.workingGuild.categories = serverCategories;
this.workingGroup.categories = serverCategories;
let newgroup;
if (this.workingGuild.id) {
await this.$store.dispatch('guilds:update', {group: this.workingGuild});
this.$root.$emit('updatedGroup', this.workingGuild);
if (this.workingGroup.id) {
await this.$store.dispatch('guilds:update', {group: this.workingGroup});
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: this.workingGuild});
newgroup = await this.$store.dispatch('guilds:create', {group: this.workingGroup});
this.$store.state.user.data.balance -= 1;
}
this.$store.state.editingGroup = {};
this.workingGuild = {
this.workingGroup = {
name: '',
type: 'guild',
privacy: 'private',
@@ -424,6 +433,19 @@ export default {
}
this.$root.$emit('hide::modal', 'guild-form');
},
resetWorkingGroup () {
this.workingGroup = {
name: '',
type: 'guild',
privacy: 'private',
description: '',
categories: [],
onlyLeaderCreatesChallenges: true,
guildLeaderCantBeMessaged: true,
privateGuild: true,
allowGuildInvationsFromNonMembers: true,
};
},
},
};
</script>

View File

@@ -19,24 +19,24 @@ div
.col-8.offset-1
member-details(:member='member')
.col-3.actions
b-dropdown(:text="$t('sort')", right=true)
b-dropdown-item(@click='sort(option.value)')
b-dropdown(text="...", right=true)
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon")
.svg-icon.inline(v-html="icons.removeIcon", v-if='isLeader')
span.text {{$t('removeMember')}}
b-dropdown-item(@click='sort(option.value)')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.messageIcon")
span.text {{$t('sendMessage')}}
b-dropdown-item(@click='sort(option.value)')
b-dropdown-item(@click='sort(option.value)', v-if='isLeader')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('promoteToLeader')}}
b-dropdown-item(@click='sort(option.value)')
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.starIcon")
span.text {{$t('addManager')}}
b-dropdown-item(@click='sort(option.value)')
b-dropdown-item(@click='sort(option.value)', v-if='isLeader && groupIsSubscribed')
span.dropdown-icon-item
.svg-icon.inline(v-html="icons.removeIcon")
span.text {{$t('removeManager2')}}
@@ -109,6 +109,7 @@ import sortBy from 'lodash/sortBy';
import bModal from 'bootstrap-vue/lib/components/modal';
import bDropdown from 'bootstrap-vue/lib/components/dropdown';
import bDropdownItem from 'bootstrap-vue/lib/components/dropdown-item';
import { mapState } from 'client/libs/store';
import MemberDetails from '../memberDetails';
import removeIcon from 'assets/members/remove.svg';
@@ -116,7 +117,7 @@ import messageIcon from 'assets/members/message.svg';
import starIcon from 'assets/members/star.svg';
export default {
props: ['group', 'hideBadge'],
props: ['hideBadge'],
components: {
bModal,
bDropdown,
@@ -160,6 +161,16 @@ export default {
};
},
computed: {
...mapState({user: 'user.data'}),
isLeader () {
return this.user._id === this.group.leader || this.user._id === this.group.leader._id;
},
groupIsSubscribed () {
return this.group.purchased.active;
},
group () {
return this.$store.state.memberModalOptions.group;
},
sortedMembers () {
let sortedMembers = this.members;
if (!this.sortOption) return sortedMembers;
@@ -182,7 +193,7 @@ export default {
},
methods: {
async getMembers () {
let groupId = this.$store.state.groupId || this.group._id;
let groupId = this.$store.state.memberModalOptions.groupId || this.group._id;
if (groupId && groupId !== 'challenge') {
let members = await this.$store.dispatch('members:getGroupMembers', {
groupId,
@@ -191,7 +202,7 @@ export default {
this.members = members;
}
if (this.$store.state.viewingMembers.length > 0) {
if (this.$store.state.memberModalOptions.viewingMembers.length > 0) {
this.members = this.$store.state.viewingMembers;
}
},

View File

@@ -136,6 +136,7 @@ export default {
this.loading = false;
},
createGroup () {
this.$store.state.editingGroup = {};
this.$root.$emit('show::modal', 'guild-form');
},
},

View File

@@ -26,6 +26,7 @@
.col-md-4.sidebar
.section
.grassy-meadow-backdrop
.daniel_front
.sleep.below-header-sections
strong(v-once) {{ $t('sleepDescription') }}
@@ -196,12 +197,21 @@
}
.grassy-meadow-backdrop {
background-image: url('~assets/images/tavern_backdrop_web.png');
background-size: cover;
background-image: url('~assets/images/tavern_backdrop_web_backgroundtile.png');
background-repeat: repeat-x;
width: 100%;
height: 246px;
}
.daniel_front {
background-image: url('~assets/images/tavern_backdrop_web_daniel_and_props.png');
width: 100%;
height: 246px;
position: absolute;
top: 0;
margin: 0 auto;
}
.sleep {
margin-top: 1em;
}

View File

@@ -1,6 +1,6 @@
<template lang="pug">
.item-with-icon.item-notifications.dropdown
.svg-icon(v-html="icons.notifications")
div.item-with-icon.item-notifications.dropdown
.svg-icon.notifications(v-html="icons.notifications")
// span.glyphicon(:class='iconClasses()')
// span.notification-counter(v-if='getNotificationsCount()') {{getNotificationsCount()}}
.dropdown-menu.dropdown-menu-right.user-dropdown
@@ -9,24 +9,32 @@
a.dropdown-item(v-if='user.purchased.plan.mysteryItems.length', @click='go("/inventory/items")')
span.glyphicon.glyphicon-gift
span {{ $t('newSubscriberItem') }}
a.dropdown-item(v-for='party in user.invitations.parties', @click='go("/party")')
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: party.name}) }}
a.dropdown-item(v-for='party in user.invitations.parties')
div
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: party.name}) }}
div
span(@click='accept(party)') Accept
span(@click='reject(party)') Reject
a.dropdown-item(v-if='user.flags.cardReceived', @click='go("/inventory/items")')
span.glyphicon.glyphicon-envelope
span {{ $t('cardReceived') }}
a.dropdown-item(@click='clearCards()', :popover="$t('clear')",
popover-placement='right', popover-trigger='mouseenter', popover-append-to-body='true')
a.dropdown-item(v-for='guild in user.invitations.guilds', @click='go("/groups/discovery")')
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: guild.name}) }}
a.dropdown-item(v-for='guild in user.invitations.guilds')
div
span.glyphicon.glyphicon-user
span {{ $t('invitedTo', {name: guild.name}) }}
div
span(@click='accept(guild)') Accept
span(@click='reject(guild)') Reject
a.dropdown-item(v-if='user.flags.classSelected && !user.preferences.disableClasses && user.stats.points',
@click='go("/user/profile")')
span.glyphicon.glyphicon-plus-sign
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
a.dropdown-item(v-for='(k,v) in user.newMessages', v-if='v.value', @click='navigateToGroup(k)')
a.dropdown-item(v-for='(message, key) in user.newMessages', v-if='message.value', @click='navigateToGroup(key)')
span.glyphicon.glyphicon-comment
span {{v.name}}
span {{message.name}}
a.dropdown-item(@click='clearMessages(k)', :popover="$t('clear')", popover-placement='right', popover-trigger='mouseenter',popover-append-to-body='true')
a.dropdown-item(v-for='(notification, index) in user.groupNotifications', @click='viewGroupApprovalNotification(notification, index, true)')
span(:class="groupApprovalNotificationIcon(notification)")
@@ -42,14 +50,25 @@
<style lang='scss' scoped>
@import '~client/assets/scss/colors.scss';
.svg-icon {
width: 25px;
.item-notifications {
width: 44px;
}
.item-notifications:hover {
cursor: pointer;
}
.notifications {
vertical-align: bottom;
display: inline-block;
width: 20px;
height: 20px;
margin-right: 8px;
margin-left: 8px;
margin-top: .2em;
}
/* @TODO: Move to shared css */
.dropdown:hover .dropdown-menu {
display: block;
@@ -231,7 +250,7 @@ export default {
// @TODO: USe notifications: User.readNotification(notification.id);
this.user.groupNotifications.splice(index, 1);
return navigate; // @TODO: remove
// @TODO: this.$route.go if (navigate) go('options.social.guilds.detail', {gid: notification.data.groupId});
// @TODO: this.$router.go if (navigate) go('options.social.guilds.detail', {gid: notification.data.groupId});
},
groupApprovalNotificationIcon (notification) {
if (notification.type === 'GROUP_TASK_APPROVAL') {
@@ -241,7 +260,7 @@ export default {
}
},
go (path) {
this.$route.push(path);
this.$router.push(path);
},
navigateToGroup (key) {
if (key === this.party._id || key === this.user.party._id) {
@@ -250,6 +269,18 @@ export default {
}
this.go(`/groups/guild/${key}`);
},
async reject (group) {
await this.$store.dispatch('guilds:rejectInvite', {groupId: group.id});
// @TODO: User.sync();
},
async accept (group) {
if (group.cancelledPlan && !confirm(this.$t('aboutToJoinCancelledGroupPlan'))) {
return;
}
// @TODO: check for party , type: 'myGuilds'
await this.$store.dispatch('guilds:join', {guildId: group.id});
// this.user.guilds.push(this.group._id);
},
},
};
</script>

View File

@@ -1,112 +1,111 @@
<template lang="pug">
b-modal#buy-gems(title="Amazon", :hide-footer="true", size='lg')
b-modal#buy-gems(title="Buy Gems", :hide-footer="true", size='lg')
.modal-body
.buy-gems
// @TODO: +gemButton(true)
div(v-if='userReachedGemCap')
h2 {{ $t('buyGemsGold') }}
p {{ $t('maxBuyGems') }}
div(ng-if='user.purchased.plan.customerId && (user.purchased.plan.gemsBought >= User.user.purchased.plan.consecutive.gemCapExtra + Shared.planGemLimits.convCap)')
.panel.panel-default
.panel-body
h3 {{ $t('buyGemsGold') }}
p {{ $t('maxBuyGems') }}
.row(v-if='!userReachedGemCap')
.col-12
h2 {{ $t('buyGemsGold') }}
p {{ $t('subGemPop') }}
div(ng-if='user.purchased.plan.customerId && (user.purchased.plan.gemsBought < User.user.purchased.plan.consecutive.gemCapExtra + Shared.planGemLimits.convCap)')
.panel.panel-default
.panel-body
h3 {{ $t('buyGemsGold') }}
p {{ $t('subGemPop') }}
.container-fluid
.row
.col-md-3
button.customize-option(ng-click='User.purchase({params:{type:"gems",key:"gem"}})')
span.Pet_Currency_Gem.inline-gems
// @TODO: .badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}
.col-4
button.btn.btn-primary(@click='purchase({ params: {type: "gems", key: "gem"} })')
| Buy Gems for 20 Gold each
span.Pet_Currency_Gem.inline-gems
.badge.badge-success.stack-count {{planGemLimits.convCap + user.purchased.plan.consecutive.gemCapExtra - user.purchased.plan.gemsBought}}
.col-8
p {{ $t('buyGemsAllow1') }}
| &nbsp; {{planGemLimits.convCap + user.purchased.plan.consecutive.gemCapExtra - user.purchased.plan.gemsBought}}&nbsp;
| {{ $t('buyGemsAllow2') }}
.col-12
p(v-html="$t('seeSubscriptionDetails')")
.row(v-if='user.purchased.plan.customerId')
.col-12
h2 {{ $t('purchaseGemsSeparately') }}
.col-12.alert.alert-info
| $5 {{ $t('USD') }} = +20
.col-12
h3 {{ $t('paymentMethods') }}
button.purchase.btn.btn-primary(ng-click='Payments.showStripe({})') {{ $t('card') }}
a.purchase(href='/paypal/checkout?_id=${user._id}&apiToken=${User.settings.auth.apiToken}')
img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt='Pay now with Paypal')
a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})")
img(src='https://payments.amazon.com/gp/cba/button', alt='Pay now with Amazon Payments')
.row(v-if='!user.purchased.plan.customerId')
.col-12
h2 {{ $t('purchaseGems') }}
.small
span.dashed-underline(popover="$t('donateText3')", popover-trigger='mouseenter', popover-placement='bottom')
| $5 {{ $t('USD') }}
span#TotalGemPrice.dashed-underline(popover="$t('donateText1')",
popover-trigger='mouseenter', ement='bottom')
| +20
span(class="Pet_Currency_Gem1x inline-gems")
.container-fluid
p
| 20&nbsp;
span.shop_gold
.col-md-8
.popover.right.gem-count-popover
.arrow
.popover-content
p {{ $t('buyGemsAllow1') }}
// @TOOD: | &nbsp;{{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}&nbsp;
| {{ $t('buyGemsAllow2') }}
p {{ $t('seeSubscriptionDetails') }}
div(ng-if='user.purchased.plan.customerId')
small.muted {{ $t('paymentMethods') }}
a.purchase.btn.btn-primary(ng-click='Payments.showStripe({})') {{ $t('card') }}
a.purchase(href='/paypal/checkout?_id=${user._id}&apiToken=${User.settings.auth.apiToken}')
img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',
alt='Pay now with Paypal')
a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})")
img(src='https://payments.amazon.com/gp/cba/button', alt='Pay now with Amazon Payments')
.container-fluid
h2 {{ $t('freeGemsTitle') }}
p {{ $t('subFreeGemsHow') }}
.well
h3 {{ $t('purchaseGemsSeparately') }}
.container-fluid
.row
.col-md-4.col-md-offset-4.alert.alert-info $5&nbsp;
| {{ $t('USD') }}
span#TotalGemPrice.dashed-underline(:popover="$t('donateText1')",
popover-trigger='mouseenter',popover-placement='bottom')
| +20
span(class="Pet_Currency_Gem1x inline-gems")
.container-fluid
.row
.col-md-10.col-md-offset-2
p
small.muted {{ $t('paymentMethods') }}
a.purchase.btn.btn-primary(ng-click='Payments.showStripe({})') {{ $t('card') }}
a.purchase(href='/paypal/checkout?_id=${user._id}&apiToken=${User.settings.auth.apiToken}')
img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',alt='Pay now with Paypal')
a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})")
img(src='https://payments.amazon.com/gp/cba/button', alt='Pay now with Amazon Payments')
div(ng-if='!user.purchased.plan.customerId')
.panel.panel-default
.panel-body
h3 {{ $t('purchaseGems') }}
.small
span.dashed-underline(popover="$t('donateText3')", popover-trigger='mouseenter', popover-placement='bottom')
| {{ $t('donateText2') }}
.container-fluid
.row
.col-md-4.col-md-offset-4.alert.alert-info $5&nbsp;
| {{ $t('USD') }}
span#TotalGemPrice.dashed-underline(popover="$t('donateText1')",
popover-trigger='mouseenter', ement='bottom')
| +20
span(class="Pet_Currency_Gem1x inline-gems")
.container-fluid
.row
.col-md-10.col-md-offset-2
p
small.muted {{ $t('paymentMethods') }}
a.purchase.btn.btn-primary(ng-click='Payments.showStripe({})') {{ $t('card') }}
a.purchase(href='/paypal/checkout?_id=${user._id}&apiToken=${User.settings.auth.apiToken}')
img(src='https://www.paypalobjects.com/webstatic/en_US/i/buttons/pp-acceptance-small.png',
alt='Pay now with Paypal')
a.purchase(ng-click="Payments.amazonPayments.init({type: 'single'})")
img(src='https://payments.amazon.com/gp/cba/button', alt='Pay now with Amazon Payments')
h3
.small {{ $t('buyGemsGoldTitle') }}
h3 {{ $t('becomeSubscriber') }}
.container-fluid
h3 {{ $t('freeGemsTitle') }}
p {{ $t('subFreeGemsHow') }}
div(ng-include="'partials/options.settings.subscription.html'", ng-controller='SettingsCtrl')
.well
h3
.small {{ $t('buyGemsGoldTitle') }}
h3 {{ $t('becomeSubscriber') }}
div(ng-include="'partials/options.settings.subscription.html'", ng-controller='SettingsCtrl')
div(ng-if='user.purchased.plan.customerId').pull-left
p {{ $t('seeSubscriptionDetails') }}
.text-right
button.btn.btn-default(ng-click='$close()') {{ $t('close') }}
.row(v-if='user.purchased.plan.customerId')
.col-12
p(v-html="$t('seeSubscriptionDetails')")
.modal-footer
.col-12.text-center
button.btn.btn-secondary(@click='close()') {{ $t('close') }}
</template>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapState } from 'client/libs/store';
import planGemLimits from '../../../common/script/libs/planGemLimits';
import purchase from '../../../common/script/ops/purchase';
export default {
components: {
bModal,
},
data () {
return {
planGemLimits,
};
},
computed: {
...mapState({user: 'user.data'}),
userReachedGemCap () {
return this.user.purchased.plan.customerId && this.user.purchased.plan.gemsBought >= this.user.purchased.plan.consecutive.gemCapExtra + this.planGemLimits.convCap;
},
},
methods: {
close () {
this.$root.$emit('hide::modal', 'buy-gems');
},
purchase (params) {
try {
purchase(this.user, params);
} catch (e) {
alert(e.message);
}
},
},
};
</script>

View File

@@ -72,8 +72,8 @@ export default {
};
},
mounted () {
this.restoreValues.stats = this.user.stats;
this.restoreValues.achievements.streak = this.user.achievements.streak;
Object.assign(this.restoreValues.stats, this.user.stats);
Object.assign(this.restoreValues.achievements.streak, this.user.achievements.streak);
},
computed: {
...mapState({user: 'user.data'}),

View File

@@ -7,46 +7,25 @@
nav.navbar.navbar-toggleable-md.navbar-light.bg-faded
button.navbar-toggler.navbar-toggler-right(type='button', data-toggle='collapse', data-target='#navbarNav', aria-controls='navbarNav', aria-expanded='false', aria-label='Toggle navigation')
span.navbar-toggler-icon
a.navbar-brand(href='#') Navbar
a.navbar-brand(href='#')
.logo.svg-icon(v-html="icons.logo")
#navbarNav.collapse.navbar-collapse
ul.navbar-nav
li.nav-item.active
a.nav-link(href='#')
| Home
span.sr-only (current)
ul.navbar-nav.float-right
li.nav-item
a.nav-link(href='#') Features
router-link.nav-link(to="/static/features") How it Works
li.nav-item
a.nav-link(href='#') Pricing
router-link.nav-link(to="/static/plans") Group Plans
li.nav-item
a.nav-link.disabled(href='#') Disabled
a.nav-link(href="https://habitica.wordpress.com/") Blog
li.nav-item
a.nav-link(href="http://blog.habitrpg.com/") Tumblr
li.nav-item
router-link.nav-link(to="/static/press-kit") Press Kit
li.nav-item
router-link.nav-link(to="/static/contact") Contact
li.nav-item
button#play-btn(class="btn btn-primary btn-lg gamifybutton") Enter Habitica
//-
//- nav.navbar.navbar-light.bg-faded
//- .navbar-header
//- button.navbar-toggle.collapsed(type='button', data-toggle='collapse', data-target='#bs-example-navbar-collapse-1')
//- span.sr-only Toggle navigation
//- span.icon-bar
//- span.icon-bar
//- span.icon-bar
//- a.navbar-brand(href='#')
//- img.img-rendering-auto(src='https://d2afqr2xdmyzvu.cloudfront.net/assets/habitica_lockup2_desat.png', alt="$t('altAttrNavLogo')", width='156px')
//- #bs-example-navbar-collapse-1.collapse.navbar-collapse
//- ul.nav.navbar-nav.navbar-right
//- li
//- a(href='/static/features') {{ $t('companyAbout') }}
//- li
//- a(href='/static/plans') {{ $t('groupPlans') }}
//- li
//- a(href='https://habitica.wordpress.com/') {{ $t('companyBlog') }}
//- li
//- a(href='http://blog.habitrpg.com/') {{ $t('tumblr') }}
//- li
//- a(href='/static/press-kit') {{ $t('presskit') }}
//- li
//- a(href='/static/contact') {{ $t('contactUs') }}
//- li
//- button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(@click='playButtonClick()') {{ $t('playButtonFull') }}
#intro.container-fluid
.row
h1.col-12.text-center {{ $t('motivate1') }}
@@ -556,6 +535,12 @@
</template>
<style lang="scss" scoped>
.logo {
width: 128px;
height: 28px;
color: purple;
}
#intro {
background: #fff;
padding-top: 1em;
@@ -634,9 +619,14 @@
</style>
<script>
import logo from 'assets/svg/logo.svg';
export default {
data () {
return {
icons: Object.freeze({
logo,
}),
userCount: 1000000,
};
},

View File

@@ -49,6 +49,10 @@ div(v-if='user.stats.lvl > 10')
white-space: initial;
}
.spell:hover {
cursor: pointer;
}
.spell {
background: #ffffff;
margin-bottom: 1em;
@@ -244,16 +248,16 @@ export default {
return;
}
// @TODO: do we need to fetcht the party everytime? We should probably just check store
let party = await this.$store.dispatch('guilds:getGroup', {groupId: 'party'});
let party = this.$store.state.party.members;
party = isArray(party) ? party : [];
party = party.concat(this.user);
this.$store.state.party.data = party;
this.castEnd(party, 'party');
} else if (spell.target === 'tasks') {
let tasks = this.$store.state.tasks.habits.concat(this.user.dailys)
.concat(this.$store.state.tasks.rewards)
.concat(this.$store.state.tasks.todos);
let userTasks = this.$store.state.tasks.data;
let tasks = userTasks.habits
.concat(userTasks.dailys)
.concat(userTasks.rewards)
.concat(userTasks.todos);
// exclude challenge tasks
tasks = tasks.filter((task) => {
if (!task.challenge) return true;

View File

@@ -49,7 +49,7 @@
// Habits right side control
.right-control.d-flex.align-items-center.justify-content-center(v-if="task.type === 'habit'", :class="controlClass.down")
.task-control.habit-control(:class="controlClass.down + '-control-habit'", @click="isUser ? score('down') : null")
.task-control.habit-control(:class="controlClass.down + '-control-habit'", @click="(isUser && controlClass.down !== 'task-habit-disabled') ? score('down') : null")
.svg-icon.negative(v-html="icons.negative")
// Rewards right side control
.right-control.d-flex.align-items-center.justify-content-center.reward-control(v-if="task.type === 'reward'", :class="controlClass", @click="isUser ? score('down') : null")
@@ -62,7 +62,7 @@
@import '~client/assets/scss/colors.scss';
.task {
margin-bottom: 8px;
margin-bottom: 2px;
box-shadow: 0 2px 2px 0 rgba($black, 0.16), 0 1px 4px 0 rgba($black, 0.12);
background: transparent;
border-radius: 2px;
@@ -78,6 +78,10 @@
}
}
.task-habit-disabled-control-habit:hover {
cursor: initial;
}
.task-title {
padding-bottom: 8px;
color: $gray-10;
@@ -247,6 +251,7 @@
.small-text {
margin-top: 4px;
color: $yellow-10;
font-style: initial;
}
}
</style>
@@ -276,6 +281,11 @@
color: $gray-300;
white-space: nowrap;
}
.task-reward {
// @TODO: I'm unsure of where this is defined. Can't find it in search. So, I am using important for now
background-color: rgba(255, 217, 160, 0.28) !important;
}
</style>
<script>

View File

@@ -11,10 +11,14 @@
slot="modal-header",
:class="[cssClass]",
)
h1 {{ title }}
.row
h1.col-8 {{ title }}
.col-4
span.cancel-task-btn(v-once, @click="cancel()") {{ $t('cancel') }}
button.btn.btn-secondary(type="submit", v-once) {{ $t('save') }}
.form-group
label(v-once) {{ `${$t('title')}*` }}
input.form-control(type='text', :class="[`${cssClass}-modal-input`]", required, v-model="task.text")
input.form-control.title-input(type='text', :class="[`${cssClass}-modal-input`]", required, v-model="task.text", autofocus)
.form-group
label(v-once) {{ $t('notes') }}
textarea.form-control(:class="[`${cssClass}-modal-input`]", v-model="task.notes", rows="3")
@@ -22,6 +26,7 @@
.option(v-if="task.type === 'reward'")
label(v-once) {{ $t('cost') }}
input(type="number", v-model="task.value", required, min="0")
.svg-icon.gold(v-html="icons.gold")
.option(v-if="['daily', 'todo'].indexOf(task.type) > -1")
label(v-once) {{ $t('checklist') }}
br
@@ -69,14 +74,16 @@
label(v-once) {{ $t('startDate') }}
datepicker(v-model="task.startDate")
.option(v-if="task.type === 'daily'")
label(v-once) {{ $t('repeats') }}
b-dropdown(:text="$t(task.frequency)")
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
| {{ $t(frequency) }}
label(v-once) {{ $t('repeatEvery') }}
input.form-control(type="number", v-model="task.everyX", min="0", required)
| {{ repeatSuffix }}
br
.form-group
label(v-once) {{ $t('repeats') }}
b-dropdown(:text="$t(task.frequency)")
b-dropdown-item(v-for="frequency in ['daily', 'weekly', 'monthly', 'yearly']", :key="frequency", @click="task.frequency = frequency", :class="{active: task.frequency === frequency}")
| {{ $t(frequency) }}
.form-group
label(v-once) {{ $t('repeatEvery') }}
input(type="number", v-model="task.everyX", min="0", required)
| {{ repeatSuffix }}
br
template(v-if="task.frequency === 'weekly'")
.form-check-inline.weekday-check(
v-for="(day, dayNumber) in ['su','m','t','w','th','f','s']",
@@ -141,7 +148,6 @@
@change="updateRequiresApproval")
.task-modal-footer(slot="modal-footer")
button.btn.btn-primary(type="submit", v-once) {{ $t('save') }}
span.cancel-task-btn(v-once, v-if="purpose === 'create'", @click="cancel()") {{ $t('cancel') }}
span.delete-task-btn(v-once, v-else, @click="destroy()") {{ $t('delete') }}
</template>
@@ -311,6 +317,10 @@
}
}
.cancel-task-btn {
margin-right: .5em;
}
.task-modal-footer {
margin: 0 auto;
padding-bottom: 24px;
@@ -319,7 +329,6 @@
margin-top: 50px;
.delete-task-btn, .cancel-task-btn {
margin-left: 16px;
cursor: pointer;
&:hover, &:focus, &:active {
@@ -343,6 +352,14 @@
}
</style>
<style lang="scss" scoped>
.gold {
width: 24px;
margin-left: 5em;
margin-top: -2.4em;
}
</style>
<script>
import bModal from 'bootstrap-vue/lib/components/modal';
import { mapGetters, mapActions, mapState } from 'client/libs/store';
@@ -361,6 +378,7 @@ import difficultyNormalIcon from 'assets/svg/difficulty-normal.svg';
import positiveIcon from 'assets/svg/positive.svg';
import negativeIcon from 'assets/svg/negative.svg';
import deleteIcon from 'assets/svg/delete.svg';
import goldIcon from 'assets/svg/gold.svg';
export default {
components: {
@@ -385,6 +403,7 @@ export default {
negative: negativeIcon,
positive: positiveIcon,
destroy: deleteIcon,
gold: goldIcon,
}),
requiresApproval: false, // We can't set task.group fields so we use this field to toggle
members: [],
@@ -527,6 +546,7 @@ export default {
this.$root.$emit('hide::modal', 'task-modal');
},
destroy () {
if (!confirm('Are you sure you want to delete this task?')) return;
this.destroyTask(this.task);
this.$root.$emit('hide::modal', 'task-modal');
},

View File

@@ -35,7 +35,7 @@
@change="toggleTag(tag)",
)
span.custom-control-indicator
span.custom-control-description {{ tag.name }}
span.custom-control-description(v-markdown='tag.name')
.filter-panel-footer.clearfix
template(v-if="editingTags === true")
@@ -117,9 +117,9 @@
</template>
<style lang="scss">
#create-dropdown .dropdown-toggle::after {
display: none;
}
#create-dropdown .dropdown-toggle::after {
display: none;
}
</style>
<style lang="scss" scoped>
@@ -142,7 +142,16 @@
}
.dropdown-icon-item .svg-icon {
width: 16px;
width: 22px;
color: #C3C0C7;
}
.dropdown-icon-item:hover .svg-icon, .dropdown-item.active .svg-icon {
color: $purple-500;
}
.dropdown-icon-item .text {
font-weight: bold;
}
button.btn.btn-secondary.filter-button {
@@ -278,6 +287,7 @@
import TaskColumn from './column';
import TaskModal from './taskModal';
import spells from './spells';
import markdown from 'client/directives/markdown';
import positiveIcon from 'assets/svg/positive.svg';
import filterIcon from 'assets/svg/filter.svg';
@@ -315,6 +325,9 @@ export default {
spells,
SelectMembersModal,
},
directives: {
markdown,
},
data () {
return {
columns: ['habit', 'daily', 'todo', 'reward'],

View File

@@ -27,7 +27,7 @@
div
span(:class="userLevelStyle(conversation)") {{conversation.name}}
span.timeago {{conversation.date}}
div {{conversation.lastMessageText}}
div {{conversation.lastMessageText.substring(0, 30)}}
.col-8.messages
chat-message.container-fluid(:chat.sync='activeChat')

View File

@@ -256,6 +256,7 @@ const router = new VueRouter({
{ name: 'merch', path: 'merch', component: MerchPage, meta: {requiresLogin: false}},
// { name: 'newStuff', path: 'newStuff', component: NewStuffPage, meta: {requiresLogin: false}},
{ name: 'overview', path: 'overview', component: OverviewPage, meta: {requiresLogin: false}},
{ name: 'oldNews', path: 'old-news', component: ParentPage, meta: {requiresLogin: false}},
{ name: 'plans', path: 'plans', component: GroupPlansPage, meta: {requiresLogin: false}},
{ name: 'pressKit', path: 'press-kit', component: PressKitPage, meta: {requiresLogin: false}},
{ name: 'privacy', path: 'privacy', component: PrivacyPage, meta: {requiresLogin: false}},

View File

@@ -98,11 +98,12 @@ export async function create (store, createdTask) {
sanitizeChecklist(createdTask);
list.unshift(createdTask);
store.state.user.data.tasksOrder[type].unshift(createdTask._id);
const response = await axios.post('/api/v3/tasks/user', createdTask);
Object.assign(list[0], response.data.data);
let newTask = response.data.data;
list.unshift(newTask);
store.state.user.data.tasksOrder[type].unshift(newTask._id);
Object.assign(list[0], newTask);
}
export async function save (store, editedTask) {

View File

@@ -88,7 +88,11 @@ export default function () {
selectedLanguage,
}),
hideHeader: false,
viewingMembers: [],
memberModalOptions: {
viewingMembers: [],
groupId: '',
group: {},
},
openedItemRows: [],
spellOptions: {
castingSpell: false,

View File

@@ -300,5 +300,8 @@
"partyInformationPlaceholder": "Write a message to your Party members here!",
"selectPartyMember": "Select a Party Member",
"errorNotInParty": "You are not in a Party",
"health_wellness": "Health & Wellness"
"health_wellness": "Health & Wellness",
"self_care": "Self-Care",
"sendLink": "Send Link",
"forgotPassword": "Forgot Password"
}

View File

@@ -37,6 +37,10 @@ let schema = new Schema({
group: {type: String, ref: 'Group', validate: [validator.isUUID, 'Invalid uuid.'], required: true},
memberCount: {type: Number, default: 1},
prize: {type: Number, default: 0, min: 0},
categories: [{
slug: {type: String},
name: {type: String},
}],
}, {
strict: true,
minimize: false, // So empty objects are returned