diff --git a/website/client/app.vue b/website/client/app.vue
index e191bc580b..b16ff2beab 100644
--- a/website/client/app.vue
+++ b/website/client/app.vue
@@ -162,6 +162,42 @@ export default {
console.error('Impossible to fetch user. Clean up localStorage and refresh.', err); // eslint-disable-line no-console
});
}
+
+ // Manage modals
+ this.$root.$on('show::modal', (modalId, data) => {
+ if (data && data.fromRoot) return;
+
+ // Get last modal on stack and hide
+ let modalStackLength = this.$store.state.modalStack.length;
+ let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
+
+ // Add new modal to the stack
+ this.$store.state.modalStack.push(modalId);
+
+ // Hide the previous top modal
+ if (modalOnTop) this.$root.$emit('hide::modal', modalOnTop, {fromRoot: true});
+ });
+
+ // @TODO: This part is hacky and could be solved with two options:
+ // 1 - Find a way to pass fromRoot to hidden
+ // 2 - Enforce that all modals use the hide::modal event
+ this.$root.$on('hidden::modal', (modalId) => {
+ let modalStackLength = this.$store.state.modalStack.length;
+ let modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
+ let modalSecondToTop = this.$store.state.modalStack[modalStackLength - 2];
+ // Don't remove modal if hid was called from main app
+ // @TODO: I'd reather use this, but I don't know how to pass data to hidden event
+ // if (data && data.fromRoot) return;
+ if (modalId === modalSecondToTop) return;
+
+ // Remove modal from stack
+ this.$store.state.modalStack.pop();
+
+ // Recalculate and show the last modal if there is one
+ modalStackLength = this.$store.state.modalStack.length;
+ modalOnTop = this.$store.state.modalStack[modalStackLength - 1];
+ if (modalOnTop) this.$root.$emit('show::modal', modalOnTop, {fromRoot: true});
+ });
},
methods: {
resetItemToBuy ($event) {
diff --git a/website/client/components/achievements/achievementAvatar.vue b/website/client/components/achievements/achievementAvatar.vue
index e2503c4abf..ccbfdc17e5 100644
--- a/website/client/components/achievements/achievementAvatar.vue
+++ b/website/client/components/achievements/achievementAvatar.vue
@@ -1,7 +1,7 @@
div(:class='achievementClass')
//- +generatedAvatar({sleep: false})
- avatar(:member='user')
+ avatar(:member='user', :avatarOnly='true', :withBackground='true')
diff --git a/website/client/components/chat/chatMessages.vue b/website/client/components/chat/chatMessages.vue
index e4b915ccf6..a29a73f808 100644
--- a/website/client/components/chat/chatMessages.vue
+++ b/website/client/components/chat/chatMessages.vue
@@ -203,8 +203,8 @@ import tier5 from 'assets/svg/tier-5.svg';
import tier6 from 'assets/svg/tier-6.svg';
import tier7 from 'assets/svg/tier-7.svg';
import tier8 from 'assets/svg/tier-mod.svg';
-import tier9 from 'assets/svg/tier-npc.svg';
-import tier10 from 'assets/svg/tier-staff.svg';
+import tier9 from 'assets/svg/tier-staff.svg';
+import tier10 from 'assets/svg/tier-npc.svg';
export default {
props: ['chat', 'groupId', 'groupName', 'inbox'],
diff --git a/website/client/components/creatorIntro.vue b/website/client/components/creatorIntro.vue
index 937c22ca75..c75015675d 100644
--- a/website/client/components/creatorIntro.vue
+++ b/website/client/components/creatorIntro.vue
@@ -176,7 +176,7 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
strong {{backgroundShopSets[0].text}}
.row.incentive-background-row
.col-2(v-for='bg in backgroundShopSets[0].items',
- @click='unlock("background." + bg.key)',
+ @click='buy("background." + bg.key)',
:popover-title='bg.text',
:popover='bg.notes',
popover-trigger='mouseenter')
@@ -197,7 +197,7 @@ b-modal#avatar-modal(title="", size='lg', :hide-header='true', :hide-footer='tru
strong {{set.text}}
.col-12(v-if='showPlainBackgroundBlurb(set.identifier, set.items)') {{ $t('incentiveBackgroundsUnlockedWithCheckins') }}
.col-4.text-center.customize-option.background-button(v-for='bg in set.items',
- @click='backgroundSelected(bg)',
+ @click='!user.purchased.background[bg.key] ? backgroundSelected(bg) : unlock("background." + bg.key)',
:popover-title='bg.text',
:popover='bg.notes',
popover-trigger='mouseenter')
@@ -868,6 +868,9 @@ export default {
},
mounted () {
if (this.editing) this.modalPage = 2;
+
+ // Buy modal is global, so we listen at root. I'd like to not
+ this.$root.$on('buyModal::boughtItem', this.backgroundPurchased);
},
data () {
let backgroundShopSets = getBackgroundShopSets();
@@ -1066,6 +1069,9 @@ export default {
backgroundSelected (bg) {
this.$root.$emit('buyModal::showItem', bg);
},
+ backgroundPurchased () {
+ this.backgroundUpdate = new Date();
+ },
},
};
diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue
index 1498850597..51436843d4 100644
--- a/website/client/components/groups/group.vue
+++ b/website/client/components/groups/group.vue
@@ -542,20 +542,17 @@ export default {
},
},
mounted () {
- this.searchId = this.groupId;
- if (this.isParty) {
- this.searchId = 'party';
- // @TODO: Set up from old client. Decide what we need and what we don't
- // Check Desktop notifs
- // Mark Chat seen
- // Load invites
- }
- this.fetchGuild();
+ if (!this.searchId) this.searchId = this.groupId;
- this.$root.$on('updatedGroup', group => {
- let updatedGroup = extend(this.group, group);
- this.$set(this.group, updatedGroup);
- });
+ this.load();
+
+ if (this.user.newMessages[this.searchId]) {
+ this.$store.dispatch('chat:markChatSeen', {groupId: this.searchId});
+ }
+ },
+ beforeRouteUpdate (to, from, next) {
+ this.searchId = to.params.groupId;
+ next();
},
watch: {
// call again the method if the route changes (when this route is already active)
@@ -570,6 +567,21 @@ export default {
},
},
methods: {
+ load () {
+ if (this.isParty) {
+ this.searchId = 'party';
+ // @TODO: Set up from old client. Decide what we need and what we don't
+ // Check Desktop notifs
+ // Mark Chat seen
+ // Load invites
+ }
+ this.fetchGuild();
+
+ this.$root.$on('updatedGroup', group => {
+ let updatedGroup = extend(this.group, group);
+ this.$set(this.group, updatedGroup);
+ });
+ },
// @TODO: abstract autocomplete
// https://medium.com/@_jh3y/how-to-where-s-the-caret-getting-the-xy-position-of-the-caret-a24ba372990a
getCoord (e, text) {
diff --git a/website/client/components/hall/heroes.vue b/website/client/components/hall/heroes.vue
index 9b8a17b4fa..2ec6ca91b0 100644
--- a/website/client/components/hall/heroes.vue
+++ b/website/client/components/hall/heroes.vue
@@ -1,69 +1,73 @@
-.row
+.row.standard-page
small.muted(v-html="$t('blurbHallContributors')")
.well(v-if='user.contributor.admin')
h2 {{ $t('rewardUser') }}
- form(submit='loadHero(heroID)') // @TODO: make click
- .form-group
- input.form-control(type='text', v-model='heroID', placeholder="$t('UUID')")
- .form-group
- input.btn.btn-default(type='submit')
- | {{ $t('loadUser') }}
- form(v-if='hero && hero.profile', submit='saveHero(hero)') // @TODO: make click
- a(v-click='clickMember(hero._id, true)')
- h3 {{hero.profile.name}}
- .form-group
- input.form-control(type='text', v-model='hero.contributor.text', placeholder {{ $t('contribTitle') }})
- .form-group
- label {{ $t('contribLevel') }}
- input.form-control(type='number', v-model='hero.contributor.level')
- small {{ $t('contribHallText') }}
- |
- a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear') {{ $t('moreDetails') }}
- |,
- a(target='_blank', href='https://github.com/HabitRPG/habitica/issues/3801') {{ $t('moreDetails2') }}
- .form-group
- textarea.form-control(cols=5, placeholder {{ $t('contributions') }}, v-model='hero.contributor.contributions')
- //include ../../shared/formattiv-help
- hr
- .form-group
- label {{ $t('balance') }}
- input.form-control(type='number', step="any", v-model='hero.balance')
- small {{ '`user.balance`' + this.$t('notGems') }}
- accordion
- accordion-group(heading='Items')
- h4 Update Item
- .form-group.well
- input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',v-model='hero.itemPath')
- small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. You can find all the item paths below.
- br
- input.form-control(type='text',placeholder='Value (eg, 5)',v-model='hero.itemVal')
- small.muted Enter the item value. E.g., 5 or false or head_warrior_3. All values are listed in the All Item Paths section below.
- accordion
- accordion-group(heading='All Item Paths')
- pre {{allItemPaths}}
- accordion-group(heading='Current Items')
- pre {{toJson(hero.items, true)}}
- accordion-group(heading='Auth')
- h4 Auth
- pre {{toJson(hero.auth)}}
- .form-group
- .checkbox
- label
- input(type='checkbox', v-model='hero.flags.chatRevoked')
- | Chat Privileges Revoked
- .form-group
- .checkbox
- label
- input(type='checkbox', v-model='hero.auth.blocked')
- | Blocked
+ .row
+ .form.col-6(v-if='!hero.profile')
+ .form-group
+ input.form-control(type='text', v-model='heroID', :placeholder="$t('UUID')")
+ .form-group
+ button.btn.btn-default(@click='loadHero(heroID)')
+ | {{ $t('loadUser') }}
- // h4 Backer Status
- // Add backer stuff like tier, disable adds, etcs
- .form-group
- input.form-control.btn.btn-primary(type='submit')
- | {{ $t('save') }}
+ .row
+ .form.col-6(v-if='hero && hero.profile', submit='saveHero(hero)')
+ a(@click='clickMember(hero._id, true)')
+ h3 {{hero.profile.name}}
+ .form-group
+ input.form-control(type='text', v-model='hero.contributor.text', :placeholder="$t('contribTitle')")
+ .form-group
+ label {{ $t('contribLevel') }}
+ input.form-control(type='number', v-model='hero.contributor.level')
+ small {{ $t('contribHallText') }}
+ |
+ a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear') {{ $t('moreDetails') }}
+ |,
+ a(target='_blank', href='https://github.com/HabitRPG/habitica/issues/3801') {{ $t('moreDetails2') }}
+ .form-group
+ textarea.form-control(cols=5, :placeholder="$t('contributions')", v-model='hero.contributor.contributions')
+ //include ../../shared/formattiv-help
+ hr
+
+ .form-group
+ label {{ $t('balance') }}
+ input.form-control(type='number', step="any", v-model='hero.balance')
+ small {{ '`user.balance`' + this.$t('notGems') }}
+ .accordion
+ .accordion-group(heading='Items')
+ h4 Update Item
+ .form-group.well
+ input.form-control(type='text',placeholder='Path (eg, items.pets.BearCub-Base)',v-model='hero.itemPath')
+ small.muted Enter the item path. E.g., items.pets.BearCub-Zombie or items.gear.owned.head_special_0 or items.gear.equipped.head. You can find all the item paths below.
+ br
+ input.form-control(type='text',placeholder='Value (eg, 5)',v-model='hero.itemVal')
+ small.muted Enter the item value. E.g., 5 or false or head_warrior_3. All values are listed in the All Item Paths section below.
+ .accordion
+ .accordion-group(heading='All Item Paths')
+ pre {{allItemPaths}}
+ .accordion-group(heading='Current Items')
+ pre {{hero.items}}
+ .accordion-group(heading='Auth')
+ h4 Auth
+ pre {{hero.auth}}
+ .form-group
+ .checkbox
+ label
+ input(type='checkbox', v-if='hero.flags', v-model='hero.flags.chatRevoked')
+ | Chat Privileges Revoked
+ .form-group
+ .checkbox
+ label
+ input(type='checkbox', v-model='hero.auth.blocked')
+ | Blocked
+
+ // h4 Backer Status
+ // Add backer stuff like tier, disable adds, etcs
+ .form-group
+ button.form-control.btn.btn-primary
+ | {{ $t('save') }}
.table-responsive
table.table.table-striped
@@ -75,25 +79,26 @@
th {{ $t('title') }}
th {{ $t('contributions') }}
tbody
- tr(v-for='(hero, $index) in heroes')
+ tr(v-for='(hero, index) in heroes')
td
span(v-if='hero.contributor && hero.contributor.admin', :popover="$t('gamemaster')", popover-trigger='mouseenter', popover-placement='right')
- a.label.label-default(v-class='userLevelStyle(hero)', v-click='clickMember(hero._id, true)')
+ a.label.label-default(:class='userLevelStyle(hero)', @click='clickMember(hero._id, true)')
| {{hero.profile.name}}
- span(v-class='userAdminGlyphiconStyle(hero)')
+ //- span(v-class='userAdminGlyphiconStyle(hero)')
span(v-if='!hero.contributor || !hero.contributor.admin')
- a.label.label-default(v-if='hero.profile', v-class='userLevelStyle(hero)', v-click='clickMember(hero._id, true)') {{hero.profile.name}}
- td(v-if='user.contributor.admin', v-click='populateContributorInput(hero._id, $index)').btn-link {{hero._id}}
+ a.label.label-default(v-if='hero.profile', v-class='userLevelStyle(hero)', @click='clickMember(hero._id, true)') {{hero.profile.name}}
+ td(v-if='user.contributor.admin', @click='populateContributorInput(hero._id, index)').btn-link {{hero._id}}
td {{hero.contributor.level}}
td {{hero.contributor.text}}
td
- markdown(text='hero.contributor.contributions', target='_blank')
+ div(v-markdown='hero.contributor.contributions', target='_blank')
diff --git a/website/client/components/shops/buyModal.vue b/website/client/components/shops/buyModal.vue
index 7b027c0112..73c1272a8e 100644
--- a/website/client/components/shops/buyModal.vue
+++ b/website/client/components/shops/buyModal.vue
@@ -66,9 +66,8 @@
:currencyNeeded="getPriceClass()",
:amountNeeded="item.value"
).float-right
-
-
+
diff --git a/website/client/mixins/groupsUtilities.js b/website/client/mixins/groupsUtilities.js
index 291daa68e0..52645c932c 100644
--- a/website/client/mixins/groupsUtilities.js
+++ b/website/client/mixins/groupsUtilities.js
@@ -19,7 +19,7 @@ export default {
return false;
},
isLeaderOfGroup (user, group) {
- return user._id === group.leader._id;
+ return user._id === group.leader || user._id === group.leader._id;
},
filterGuild (group, filters, search, user) {
let passedSearch = true;
diff --git a/website/client/store/actions/chat.js b/website/client/store/actions/chat.js
index 0d6bffaf42..6ae075903f 100644
--- a/website/client/store/actions/chat.js
+++ b/website/client/store/actions/chat.js
@@ -52,7 +52,7 @@ export async function clearFlagCount (store, payload) {
}
export async function markChatSeen (store, payload) {
- if (store.user.newMessages) delete store.user.newMessages[payload.groupId];
+ if (store.state.user.newMessages) delete store.state.user.newMessages[payload.groupId];
let url = `/api/v3/groups/${payload.groupId}/chat/seen`;
let response = await axios.post(url);
return response.data.data;
diff --git a/website/client/store/index.js b/website/client/store/index.js
index 52353e05f4..d50b778247 100644
--- a/website/client/store/index.js
+++ b/website/client/store/index.js
@@ -104,6 +104,7 @@ export default function () {
profileUser: {},
upgradingGroup: {},
notificationStore: [],
+ modalStack: [],
},
});
diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json
new file mode 100644
index 0000000000..c986f2bd4f
--- /dev/null
+++ b/website/common/locales/en/achievements.json
@@ -0,0 +1,6 @@
+{
+ "share": "Share",
+ "onwards": "Onwards!",
+ "levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
+ "reachedLevel": "You Reached Level <%= level %>"
+}
diff --git a/website/common/locales/en/challenge.json b/website/common/locales/en/challenge.json
index 97baf5d357..f259dd1771 100644
--- a/website/common/locales/en/challenge.json
+++ b/website/common/locales/en/challenge.json
@@ -114,5 +114,6 @@
"shortNamePlaceholder": "What short tag should be used to identify your Challenge?",
"updateChallenge": "Update Challenge",
"haveNoChallenges": "You don't have any Challenges",
- "loadMore": "Load More"
+ "loadMore": "Load More",
+ "exportChallengeCsv": "Export Challenge"
}
diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json
index 63a3f7eec5..d348823489 100644
--- a/website/common/locales/en/subscriber.json
+++ b/website/common/locales/en/subscriber.json
@@ -2,6 +2,7 @@
"subscription": "Subscription",
"subscriptions": "Subscriptions",
"subDescription": "Buy Gems with gold, get monthly mystery items, retain progress history, double daily drop-caps, support the devs. Click for more info.",
+ "sendGems": "Send Gems",
"buyGemsGold": "Buy Gems with Gold",
"buyGemsGoldText": "Alexander the Merchant will sell you Gems at a cost of 20 Gold per Gem. His monthly shipments are initially capped at 25 Gems per month, but for every 3 consecutive months that you are subscribed, this cap increases by 5 Gems, up to a maximum of 50 Gems per month!",
"mustSubscribeToPurchaseGems": "Must subscribe to purchase gems with GP",