New client random catchup (#8891)
* Added initial challenge pages * Added challenge item and find guilds page * Added challenge detail * Added challenge modals * Ported over challenge service code * Ported over challenge ctrl code * Added styles and column * Minor modal updates * Removed duplicate keys * Fixed casing * Added initial chat component * Added copy as todo modal * Added sync * Added chat to groups * Fixed lint * Added notification service * Added tag services * Added notifications * Added hall * Added analytics * Added http interceptor * Added initial autocomplete * Added initial footer component * Began coding and designing footer * Added inital hall * Ported over inital group plan ctrl code * Added initial invite modal * Added initial member detail modal * Added initial notification menu * Ported over inital notification code * Fixed import line * Fixed autocomplete import casing
@@ -5,15 +5,19 @@
|
|||||||
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
|
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
|
||||||
p Loading...
|
p Loading...
|
||||||
template(v-else)
|
template(v-else)
|
||||||
|
notifications
|
||||||
app-menu
|
app-menu
|
||||||
.container-fluid
|
.container-fluid
|
||||||
app-header
|
app-header
|
||||||
router-view
|
router-view
|
||||||
|
app-footer
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import AppMenu from './components/appMenu';
|
import AppMenu from './components/appMenu';
|
||||||
import AppHeader from './components/appHeader';
|
import AppHeader from './components/appHeader';
|
||||||
|
import AppFooter from './components/appFooter';
|
||||||
|
import notifications from './components/notifications';
|
||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@@ -21,6 +25,8 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
AppMenu,
|
AppMenu,
|
||||||
AppHeader,
|
AppHeader,
|
||||||
|
AppFooter,
|
||||||
|
notifications,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="70" viewBox="0 0 65 70">
|
<svg xmlns="http://www.w3.org/2000/svg" width="65" height="70" viewBox="0 0 65 70">
|
||||||
<path fill="#FFF" fill-rule="evenodd" d="M62.749 65.754c1.728 2.297 1.853 3.623.136 3.623H51.783c-3.344 0-4.038.396-3.985-1.056.072-2.024.725-2.103 2.851-2.696 1.763-.493 1.13-3.63-.869-6.376-1.392-1.91-4.216-4.134-7.929-2.405-2.41 1.122-3.828 2.27-4.943 3.815-1.823 2.53-1.724 4.927 1.115 4.927 2.683 0 5.843-1.626 6.76.69 1.134 2.867.355 3.133-.2 3.133H26.79l.012.017c-9.532 0-19.054-.075-21.262-6.099-3.056-8.334 8.063-11.546 8.404-16.775.138-2.116-1.051-3.096-2.536-3.096H4.885V39.49H.921V27.6h3.964v3.964H8.85v3.964h3.965v5.506s3.447 1.71 3.33 5.478c-.186 5.982-10.304 9.296-8.45 15.567 1.695 5.738 14.502 4.364 14.053.804l-.222-2.028c-.063-2.84-.506-6.64.087-10.005.934-5.289 4.224-9.865 9.956-9.907 1.045-.008 1.929-.222 1.929-1.1 0-.927-1.68-.906-2.863-1.025-4.09-.417-9.18-1.787-13.627-5.445-2.634-2.167-6.736-8.999-4.054-8.498 1.656.31 3.116.437 4.4.433 1.346-.004 4.82-.382 4.82-1.203 0-1.138-2.01-.453-4.955-.741-3.521-.344-10.988-1.853-14.84-13.215C.932 5.89-.02-2.075 2.324.503 15.84 15.367 20.433 12.947 28.184 18.029c3.412 2.237 5.633 7.58 7.622 7.135 1.296-.292.502-1.483.696-4.262.173-2.484 1.354-3.022-1.683-3.773-3.538-.873-7.07-4.752-4.76-4.697 2.307.056 3.977.048 6-.828 2.024-.875 6.97-4.641 9.962-3.133 2.992 1.509 5.507-.656 7.531-.272 2.938.557 3.694 4.6 3.313 6.965-.24 1.5-.24 1.498-2.153 1.386-3.921-.23-4.92 2.893-2.162 4.615 4.197 2.621 5.98 4.588 7.121 10.395.555 2.817.105 3.474-.83 2.956-.933-.519-2.539-.778-.31 4.356 2.23 5.133-1.4 11.15-2.592 11.825-1.194.674-.986 1.452-.986 1.452-.023 12.387 0 13.315 2.078 13.538 3.15.34 4.49-1.565 5.718.067z"/>
|
<path fill-rule="evenodd" d="M62.749 65.754c1.728 2.297 1.853 3.623.136 3.623H51.783c-3.344 0-4.038.396-3.985-1.056.072-2.024.725-2.103 2.851-2.696 1.763-.493 1.13-3.63-.869-6.376-1.392-1.91-4.216-4.134-7.929-2.405-2.41 1.122-3.828 2.27-4.943 3.815-1.823 2.53-1.724 4.927 1.115 4.927 2.683 0 5.843-1.626 6.76.69 1.134 2.867.355 3.133-.2 3.133H26.79l.012.017c-9.532 0-19.054-.075-21.262-6.099-3.056-8.334 8.063-11.546 8.404-16.775.138-2.116-1.051-3.096-2.536-3.096H4.885V39.49H.921V27.6h3.964v3.964H8.85v3.964h3.965v5.506s3.447 1.71 3.33 5.478c-.186 5.982-10.304 9.296-8.45 15.567 1.695 5.738 14.502 4.364 14.053.804l-.222-2.028c-.063-2.84-.506-6.64.087-10.005.934-5.289 4.224-9.865 9.956-9.907 1.045-.008 1.929-.222 1.929-1.1 0-.927-1.68-.906-2.863-1.025-4.09-.417-9.18-1.787-13.627-5.445-2.634-2.167-6.736-8.999-4.054-8.498 1.656.31 3.116.437 4.4.433 1.346-.004 4.82-.382 4.82-1.203 0-1.138-2.01-.453-4.955-.741-3.521-.344-10.988-1.853-14.84-13.215C.932 5.89-.02-2.075 2.324.503 15.84 15.367 20.433 12.947 28.184 18.029c3.412 2.237 5.633 7.58 7.622 7.135 1.296-.292.502-1.483.696-4.262.173-2.484 1.354-3.022-1.683-3.773-3.538-.873-7.07-4.752-4.76-4.697 2.307.056 3.977.048 6-.828 2.024-.875 6.97-4.641 9.962-3.133 2.992 1.509 5.507-.656 7.531-.272 2.938.557 3.694 4.6 3.313 6.965-.24 1.5-.24 1.498-2.153 1.386-3.921-.23-4.92 2.893-2.162 4.615 4.197 2.621 5.98 4.588 7.121 10.395.555 2.817.105 3.474-.83 2.956-.933-.519-2.539-.778-.31 4.356 2.23 5.133-1.4 11.15-2.592 11.825-1.194.674-.986 1.452-.986 1.452-.023 12.387 0 13.315 2.078 13.538 3.15.34 4.49-1.565 5.718.067z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
223
website/client/components/appFooter.vue
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.row
|
||||||
|
footer.container-fluid
|
||||||
|
.row
|
||||||
|
.col-2
|
||||||
|
h3 iOS App
|
||||||
|
h3 Android App
|
||||||
|
.col-2
|
||||||
|
h3 Company
|
||||||
|
ul
|
||||||
|
li How it Works
|
||||||
|
li Blog
|
||||||
|
li Tumblr
|
||||||
|
li FAQ
|
||||||
|
li News
|
||||||
|
li Merchandis
|
||||||
|
li Press Kit
|
||||||
|
li Contact Us
|
||||||
|
.col-2
|
||||||
|
h3 Community
|
||||||
|
ul
|
||||||
|
li Community Guidelines
|
||||||
|
li Submit a Bug
|
||||||
|
li Request a Feature
|
||||||
|
li Add-Ons & Extensions
|
||||||
|
li Forum
|
||||||
|
li Kickstarter
|
||||||
|
li Facebook
|
||||||
|
li Reddit
|
||||||
|
.col-6
|
||||||
|
.row
|
||||||
|
.col-6
|
||||||
|
h3 Developers
|
||||||
|
ul
|
||||||
|
li APIv3
|
||||||
|
li Data Display Tool
|
||||||
|
li Guidance for Blacksmiths
|
||||||
|
li The Forge - Developer Blog
|
||||||
|
.col-6
|
||||||
|
h3 Social
|
||||||
|
.social-circle Twitter
|
||||||
|
.social-circle Instagram
|
||||||
|
.social-circle Facebook
|
||||||
|
.row
|
||||||
|
.col-10
|
||||||
|
| We’re an open source project that depends on our users for support. The money you donate helps us keep the servers running, maintain a small staff, develop new features, and provide incentives for our volunteers.
|
||||||
|
.col-2
|
||||||
|
button.btn.btn-primary Donate
|
||||||
|
.row
|
||||||
|
hr.col-12
|
||||||
|
.row
|
||||||
|
.col-4
|
||||||
|
| © 2017 Habitica. All rights reserved.
|
||||||
|
.col-4.text-center
|
||||||
|
.logo.svg-icon(v-html='icons.gryphon')
|
||||||
|
.col-4.text-right
|
||||||
|
span Privacy Policy
|
||||||
|
span Terms of Use
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
footer {
|
||||||
|
background-color: #e1e0e3;
|
||||||
|
height: 376px;
|
||||||
|
padding-left: 6em;
|
||||||
|
padding-right: 6em;
|
||||||
|
padding-top: 3em;
|
||||||
|
margin: 0;
|
||||||
|
color: #878190;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: #878190;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: 0;
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.social-circle {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: #c3c0c7;
|
||||||
|
display: inline-block;
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo.svg-icon {
|
||||||
|
width: 24px;
|
||||||
|
margin: 0 auto;
|
||||||
|
color: #c3c0c7;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import gryphon from 'assets/svg/gryphon.svg';
|
||||||
|
|
||||||
|
// const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
icons: Object.freeze({
|
||||||
|
gryphon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// @TODO: add https://github.com/HabitRPG/habitica/blob/develop/website/client-old/js/controllers/footerCtrl.js$scope.setHealthLow = function(){
|
||||||
|
// $scope.setHealthLow = function(){
|
||||||
|
// User.set({
|
||||||
|
// 'stats.hp': 1
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addMissedDay = function(numberOfDays){
|
||||||
|
// if (!confirm("Are you sure you want to reset the day by " + numberOfDays + " day(s)?")) return;
|
||||||
|
//
|
||||||
|
// User.setCron(numberOfDays);
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addTenGems = function(){
|
||||||
|
// User.addTenGems();
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addHourglass = function(){
|
||||||
|
// User.addHourglass();
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addGold = function(){
|
||||||
|
// User.set({
|
||||||
|
// 'stats.gp': User.user.stats.gp + 500,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addMana = function(){
|
||||||
|
// User.set({
|
||||||
|
// 'stats.mp': User.user.stats.mp + 500,
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addLevelsAndGold = function(){
|
||||||
|
// User.set({
|
||||||
|
// 'stats.exp': User.user.stats.exp + 10000,
|
||||||
|
// 'stats.gp': User.user.stats.gp + 10000,
|
||||||
|
// 'stats.mp': User.user.stats.mp + 10000
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addOneLevel = function(){
|
||||||
|
// User.set({
|
||||||
|
// 'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10)
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.addQuestProgress = function(){
|
||||||
|
// $http({
|
||||||
|
// method: "POST",
|
||||||
|
// url: 'api/v3/debug/quest-progress'
|
||||||
|
// })
|
||||||
|
// .then(function (response) {
|
||||||
|
// Notification.text('Quest progress increased');
|
||||||
|
// User.sync();
|
||||||
|
// })
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.makeAdmin = function () {
|
||||||
|
// User.makeAdmin();
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.openModifyInventoryModal = function () {
|
||||||
|
// $rootScope.openModal('modify-inventory', {controller: 'FooterCtrl', scope: $scope });
|
||||||
|
// $scope.showInv = { };
|
||||||
|
// $scope.inv = {
|
||||||
|
// gear: {},
|
||||||
|
// special: {},
|
||||||
|
// pets: {},
|
||||||
|
// mounts: {},
|
||||||
|
// eggs: {},
|
||||||
|
// hatchingPotions: {},
|
||||||
|
// food: {},
|
||||||
|
// quests: {},
|
||||||
|
// };
|
||||||
|
// $scope.setAllItems = function (type, value) {
|
||||||
|
// var set = $scope.inv[type];
|
||||||
|
//
|
||||||
|
// for (var item in set) {
|
||||||
|
// if (set.hasOwnProperty(item)) {
|
||||||
|
// set[item] = value;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// };
|
||||||
|
// };
|
||||||
|
//
|
||||||
|
// $scope.modifyInventory = function () {
|
||||||
|
// $http({
|
||||||
|
// method: "POST",
|
||||||
|
// url: 'api/v3/debug/modify-inventory',
|
||||||
|
// data: {
|
||||||
|
// gear: $scope.showInv.gear ? $scope.inv.gear : null,
|
||||||
|
// special: $scope.showInv.special ? $scope.inv.special : null,
|
||||||
|
// pets: $scope.showInv.pets ? $scope.inv.pets : null,
|
||||||
|
// mounts: $scope.showInv.mounts ? $scope.inv.mounts : null,
|
||||||
|
// eggs: $scope.showInv.eggs ? $scope.inv.eggs : null,
|
||||||
|
// hatchingPotions: $scope.showInv.hatchingPotions ? $scope.inv.hatchingPotions : null,
|
||||||
|
// food: $scope.showInv.food ? $scope.inv.food : null,
|
||||||
|
// quests: $scope.showInv.quests ? $scope.inv.quests : null,
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// .then(function (response) {
|
||||||
|
// Notification.text('Inventory updated. Refresh or sync.');
|
||||||
|
// })
|
||||||
|
// };
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters, mapActions } from 'client/libs/store';
|
import { mapGetters, mapActions } from 'client/libs/store';
|
||||||
import MemberDetails from './memberDetails';
|
import MemberDetails from './memberDetails';
|
||||||
import createPartyModal from './guilds/createPartyModal';
|
import createPartyModal from './groups/createPartyModal';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ div
|
|||||||
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
|
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
|
||||||
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
|
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
|
||||||
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
|
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') }}
|
||||||
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
|
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
|
||||||
a.nav-link(v-once) {{ $t('challenges') }}
|
a.nav-link(v-once) {{ $t('challenges') }}
|
||||||
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
|
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
|
||||||
@@ -42,8 +44,7 @@ div
|
|||||||
.item-with-icon
|
.item-with-icon
|
||||||
.svg-icon(v-html="icons.gold")
|
.svg-icon(v-html="icons.gold")
|
||||||
span {{user.stats.gp | roundBigNumber}}
|
span {{user.stats.gp | roundBigNumber}}
|
||||||
.item-with-icon.item-notifications
|
notification-menu
|
||||||
.svg-icon(v-html="icons.notifications")
|
|
||||||
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
|
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
|
||||||
.svg-icon(v-html="icons.user")
|
.svg-icon(v-html="icons.user")
|
||||||
.dropdown-menu.dropdown-menu-right.user-dropdown
|
.dropdown-menu.dropdown-menu-right.user-dropdown
|
||||||
@@ -203,21 +204,21 @@ div
|
|||||||
import { mapState, mapGetters } from 'client/libs/store';
|
import { mapState, mapGetters } from 'client/libs/store';
|
||||||
import gemIcon from 'assets/svg/gem.svg';
|
import gemIcon from 'assets/svg/gem.svg';
|
||||||
import goldIcon from 'assets/svg/gold.svg';
|
import goldIcon from 'assets/svg/gold.svg';
|
||||||
import notificationsIcon from 'assets/svg/notifications.svg';
|
|
||||||
import userIcon from 'assets/svg/user.svg';
|
import userIcon from 'assets/svg/user.svg';
|
||||||
import logo from 'assets/svg/logo.svg';
|
import logo from 'assets/svg/logo.svg';
|
||||||
import InboxModal from './userMenu/inbox.vue';
|
import InboxModal from './userMenu/inbox.vue';
|
||||||
|
import notificationMenu from './notificationMenu';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
InboxModal,
|
InboxModal,
|
||||||
|
notificationMenu,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
gem: gemIcon,
|
gem: gemIcon,
|
||||||
gold: goldIcon,
|
gold: goldIcon,
|
||||||
notifications: notificationsIcon,
|
|
||||||
user: userIcon,
|
user: userIcon,
|
||||||
logo,
|
logo,
|
||||||
}),
|
}),
|
||||||
|
|||||||
66
website/client/components/chat/autoComplete.vue
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
div.autocomplete-selection
|
||||||
|
div(v-for='result in searchResults', @click='select(result)') {{ result }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ['selections', 'text'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
currentSearch: '',
|
||||||
|
searchActive: false,
|
||||||
|
currentSearchPosition: 0,
|
||||||
|
// @TODO: HAve this passed
|
||||||
|
tmpSelections: [
|
||||||
|
'TheHollidayInn',
|
||||||
|
'Paglias',
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
searchResults () {
|
||||||
|
if (!this.searchActive) return [];
|
||||||
|
let currentSearch = this.text.substring(this.currentSearchPosition + 1, this.text.length);
|
||||||
|
return this.tmpSelections.filter((option) => {
|
||||||
|
return option.toLowerCase().indexOf(currentSearch.toLowerCase()) !== -1;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
text (newText) {
|
||||||
|
if (newText[newText.length - 1] !== '@') return;
|
||||||
|
this.searchActive = true;
|
||||||
|
this.currentSearchPosition = newText.length - 1;
|
||||||
|
},
|
||||||
|
// @TODO: implement position
|
||||||
|
// caretChanged = function(newCaretPos) {
|
||||||
|
// var relativeelement = $('.chat-form div:first');
|
||||||
|
// var textarea = $('.chat-form textarea');
|
||||||
|
// var userlist = $('.list-at-user');
|
||||||
|
// var offset = {
|
||||||
|
// x: textarea.offset().left - relativeelement.offset().left,
|
||||||
|
// y: textarea.offset().top - relativeelement.offset().top,
|
||||||
|
// };
|
||||||
|
// if(relativeelement) {
|
||||||
|
// var caretOffset = InputCaret.getPosition(textarea);
|
||||||
|
// userlist.css({
|
||||||
|
// left: caretOffset.left + offset.x,
|
||||||
|
// top: caretOffset.top + offset.y + 16
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
select (result) {
|
||||||
|
let newText = this.text.slice(0, this.currentSearchPosition + 1) + result;
|
||||||
|
this.searchActive = false;
|
||||||
|
this.$emit('select', newText);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
192
website/client/components/groups/groupPlan.vue
Normal file
@@ -0,0 +1,192 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.standard-page
|
||||||
|
div(v-if='activePage === PAGES.CREATE_GROUP')
|
||||||
|
h2.text-center {{ $t('createAGroup') }}
|
||||||
|
|
||||||
|
.col-xs-12
|
||||||
|
.col-md-12.form-horizontal
|
||||||
|
.form-group
|
||||||
|
label.control-label(for='new-group-name') {{ $t('newGroupName', {groupType: 'text'}) }}
|
||||||
|
input.form-control#new-group-name.input-medium.option-content(required, type='text', :placeholder="$t('newGroupName', {groupType: 'text'})", 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"')
|
||||||
|
.radio
|
||||||
|
label
|
||||||
|
input(type='radio', name='new-group-privacy', value='public', v-model='newGroup.privacy')
|
||||||
|
| {{ $t('public') }}
|
||||||
|
.radio
|
||||||
|
label
|
||||||
|
input(type='radio', name='new-group-privacy', value='private', v-model='newGroup.privacy')
|
||||||
|
| {{ $t('inviteOnly') }}
|
||||||
|
br
|
||||||
|
input.btn.btn-default(type='submit', :disabled='!newGroup.privacy && !newGroup.name', :value="$t('create')")
|
||||||
|
span.gem-cost {{ '4 ' + $t('gems') }}
|
||||||
|
p
|
||||||
|
small {{ $t('gemCost') }}
|
||||||
|
.form-group
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', v-model='newGroup.leaderOnly.challenges')
|
||||||
|
| {{ $t('leaderOnlyChallenges') }}
|
||||||
|
.form-group(v-if='type === "party"')
|
||||||
|
input.btn.btn-default.form-control(type='submit', :value="$t('create')")
|
||||||
|
br
|
||||||
|
br
|
||||||
|
.row
|
||||||
|
.col-sm-6.col-sm-offset-3
|
||||||
|
a.btn.btn-primary.btn-lg.btn-block(@click="createGroup()", :disabled="!newGroupIsReady") {{ $t('create') }}
|
||||||
|
|
||||||
|
div(v-if='activePage === PAGES.UPGRADE_GROUP')
|
||||||
|
h2.text-center {{ $t('upgradeTitle') }}
|
||||||
|
|
||||||
|
.row.text-center
|
||||||
|
.col-6.col-offset-3
|
||||||
|
a.purchase.btn.btn-primary(@click='upgradeGroup(PAYMENTS.STRIPE)') {{ $t('card') }}
|
||||||
|
a.purchase(@click='upgradeGroup(PAYMENTS.AMAZON)')
|
||||||
|
img(src='https://payments.amazon.com/gp/cba/button', :alt="$t('amazonPayments')")
|
||||||
|
// @TODO: Add paypal
|
||||||
|
.row
|
||||||
|
.col-md-6.col-md-offset-3
|
||||||
|
br
|
||||||
|
.text-center {{ $t('groupSubscriptionPrice') }}
|
||||||
|
|
||||||
|
div(v-if='activePage === PAGES.BENEFITS')
|
||||||
|
h2.text-center {{ $t('groupBenefitsTitle') }}
|
||||||
|
.row(style="font-size: 2rem;")
|
||||||
|
.col-md-6.col-md-offset-3.text-center {{ $t('groupBenefitsDescription') }}
|
||||||
|
.row.row-margin
|
||||||
|
.col-md-4
|
||||||
|
h2 {{ $t('teamBasedTasks') }}
|
||||||
|
div
|
||||||
|
// shared tasks
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitOneTitle') }}
|
||||||
|
span {{ $t('groupBenefitOneDescription') }}
|
||||||
|
div
|
||||||
|
// assign tasks
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitTwoTitle') }}
|
||||||
|
span {{ $t('groupBenefitTwoDescription') }}
|
||||||
|
div
|
||||||
|
// claim tasks
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitThreeTitle') }}
|
||||||
|
span {{ $t('groupBenefitThreeDescription') }}
|
||||||
|
div
|
||||||
|
// mark tasks
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitFourTitle') }}
|
||||||
|
span {{ $t('groupBenefitFourDescription') }}
|
||||||
|
div
|
||||||
|
// group managers
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitEightTitle') }}
|
||||||
|
span {{ $t('groupBenefitEightDescription') }}
|
||||||
|
|
||||||
|
.col-md-4
|
||||||
|
h2 {{ $t('specializedCommunication') }}
|
||||||
|
div
|
||||||
|
// chat privately
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitFiveTitle') }}
|
||||||
|
span {{ $t('groupBenefitFiveDescription') }}
|
||||||
|
div
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitMessageLimitTitle') }}
|
||||||
|
span {{ $t('groupBenefitMessageLimitDescription') }}
|
||||||
|
.col-md-4
|
||||||
|
h2 {{ $t('funExtras') }}
|
||||||
|
div
|
||||||
|
// free subscription
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitSixTitle') }}
|
||||||
|
span {{ $t('groupBenefitSixDescription') }}
|
||||||
|
div
|
||||||
|
// exclusive mount
|
||||||
|
h3
|
||||||
|
span.glyphicon.glyphicon-ok-circle(style='margin-right: 1.5rem;')
|
||||||
|
| {{ $t('groupBenefitSevenTitle') }}
|
||||||
|
|
||||||
|
br
|
||||||
|
br
|
||||||
|
.row
|
||||||
|
.col-sm-6.col-sm-offset-3
|
||||||
|
a.btn.btn-primary.btn-lg.btn-block(ui-sref="options.social.newGroup") {{ $t('createAGroup') }}
|
||||||
|
|
||||||
|
.row
|
||||||
|
.col-md-6.col-md-offset-3
|
||||||
|
br
|
||||||
|
.text-center {{ $t('groupSubscriptionPrice') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
PAGES: {
|
||||||
|
CREATE_GROUP: 'create-group',
|
||||||
|
UPGRADE_GROUP: 'upgrade-group',
|
||||||
|
},
|
||||||
|
// @TODO: Import from payment library?
|
||||||
|
PAYMENTS: {
|
||||||
|
AMAZON: 'amazon',
|
||||||
|
STRIPE: 'stripe',
|
||||||
|
},
|
||||||
|
newGroup: {
|
||||||
|
type: 'guild',
|
||||||
|
privacy: 'private',
|
||||||
|
name: '',
|
||||||
|
leaderOnly: {
|
||||||
|
challenges: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
activePage: '',
|
||||||
|
type: 'guild', // Guild or Party @TODO enum this
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.activePage = this.PAGES.CREATE_GROUP;
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
newGroupIsReady () {
|
||||||
|
return Boolean(this.newGroup.name);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
changePage (page) {
|
||||||
|
this.activePage = page;
|
||||||
|
window.scrollTo(0, 0);
|
||||||
|
},
|
||||||
|
createGroup () {
|
||||||
|
this.changePage(this.PAGES.UPGRADE_GROUP);
|
||||||
|
},
|
||||||
|
upgradeGroup (paymentType) {
|
||||||
|
// let subscriptionKey = 'group_monthly'; // @TODO: Get from content API?
|
||||||
|
if (paymentType === this.PAYMENTS.STRIPE) {
|
||||||
|
// Payments.showStripe({
|
||||||
|
// subscription: subscriptionKey,
|
||||||
|
// coupon: null,
|
||||||
|
// groupToCreate: this.newGroup
|
||||||
|
// });
|
||||||
|
} else if (paymentType === this.PAYMENTS.AMAZON) {
|
||||||
|
// Payments.amazonPayments.init({
|
||||||
|
// type: 'subscription',
|
||||||
|
// subscription: subscriptionKey,
|
||||||
|
// coupon: null,
|
||||||
|
// groupToCreate: this.newGroup
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.row(v-if="group")
|
.row(v-if="group")
|
||||||
group-form-modal
|
group-form-modal
|
||||||
|
invite-modal
|
||||||
.clearfix.col-8
|
.clearfix.col-8
|
||||||
.row
|
.row
|
||||||
.col-6.title-details
|
.col-6.title-details
|
||||||
@@ -40,7 +41,7 @@
|
|||||||
.button-container
|
.button-container
|
||||||
button.btn.btn-success(class='btn-success', v-if='!isMember') {{ $t('join') }}
|
button.btn.btn-success(class='btn-success', v-if='!isMember') {{ $t('join') }}
|
||||||
.button-container
|
.button-container
|
||||||
button.btn.btn-primary(v-once) {{$t('invite')}}
|
button.btn.btn-primary(v-once, @click='showInviteModal()') {{$t('invite')}}
|
||||||
.button-container
|
.button-container
|
||||||
button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
|
button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
|
||||||
.button-container
|
.button-container
|
||||||
@@ -355,6 +356,8 @@ import ownedQuestsModal from './ownedQuestsModal';
|
|||||||
import quests from 'common/script/content/quests';
|
import quests from 'common/script/content/quests';
|
||||||
import percent from 'common/script/libs/percent';
|
import percent from 'common/script/libs/percent';
|
||||||
import groupFormModal from './groupFormModal';
|
import groupFormModal from './groupFormModal';
|
||||||
|
import inviteModal from './inviteModal';
|
||||||
|
import memberModal from '../members/memberModal';
|
||||||
import chatMessage from '../chat/chatMessages';
|
import chatMessage from '../chat/chatMessages';
|
||||||
|
|
||||||
import bCollapse from 'bootstrap-vue/lib/components/collapse';
|
import bCollapse from 'bootstrap-vue/lib/components/collapse';
|
||||||
@@ -380,12 +383,14 @@ export default {
|
|||||||
props: ['groupId'],
|
props: ['groupId'],
|
||||||
components: {
|
components: {
|
||||||
membersModal,
|
membersModal,
|
||||||
|
memberModal,
|
||||||
ownedQuestsModal,
|
ownedQuestsModal,
|
||||||
bCollapse,
|
bCollapse,
|
||||||
bCard,
|
bCard,
|
||||||
bTooltip,
|
bTooltip,
|
||||||
groupFormModal,
|
groupFormModal,
|
||||||
chatMessage,
|
chatMessage,
|
||||||
|
inviteModal,
|
||||||
},
|
},
|
||||||
directives: {
|
directives: {
|
||||||
bToggle,
|
bToggle,
|
||||||
@@ -507,6 +512,9 @@ export default {
|
|||||||
this.$store.state.editingGroup = this.group;
|
this.$store.state.editingGroup = this.group;
|
||||||
this.$root.$emit('show::modal', 'guild-form');
|
this.$root.$emit('show::modal', 'guild-form');
|
||||||
},
|
},
|
||||||
|
showInviteModal () {
|
||||||
|
this.$root.$emit('show::modal', 'invite-modal');
|
||||||
|
},
|
||||||
async fetchGuild () {
|
async fetchGuild () {
|
||||||
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
|
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
|
||||||
if (this.isParty) {
|
if (this.isParty) {
|
||||||
147
website/client/components/groups/inviteModal.vue
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
b-modal#invite-modal(:title="$t('inviteFriends')", size='lg')
|
||||||
|
.modal-body
|
||||||
|
p.alert.alert-info(v-html="$t('inviteAlertInfo')")
|
||||||
|
.form-horizontal
|
||||||
|
table.table.table-striped
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th {{ $t('userId') }}
|
||||||
|
tbody
|
||||||
|
tr(v-for='user in invitees')
|
||||||
|
td
|
||||||
|
input.form-control(type='text', v-model='user.uuid')
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
button.btn.btn-xs.pull-right(@click='addUuid()')
|
||||||
|
i.glyphicon.glyphicon-plus
|
||||||
|
| +
|
||||||
|
tr
|
||||||
|
td
|
||||||
|
.col-6.col-offset-6
|
||||||
|
button.btn.btn-primary.btn-block(@click='inviteNewUsers("uuid")') {{sendInviteText}}
|
||||||
|
hr
|
||||||
|
p.alert.alert-info {{ $t('inviteByEmail') }}
|
||||||
|
.form-horizontal
|
||||||
|
table.table.table-striped
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th {{ $t('name') }}
|
||||||
|
th {{ $t('email') }}
|
||||||
|
tbody
|
||||||
|
tr(v-for='email in emails')
|
||||||
|
td
|
||||||
|
input.form-control(type='text', v-model='email.name')
|
||||||
|
td
|
||||||
|
input.form-control(type='email', v-model='email.email')
|
||||||
|
tr
|
||||||
|
td(colspan=2)
|
||||||
|
a.btn.btn-xs.pull-right(@click='addEmail()')
|
||||||
|
i.glyphicon.glyphicon-plus
|
||||||
|
| +
|
||||||
|
tr
|
||||||
|
td.form-group(colspan=2)
|
||||||
|
label.col-sm-1.control-label {{ $t('byColon') }}
|
||||||
|
.col-sm-5
|
||||||
|
input.form-control(type='text', v-model='inviter')
|
||||||
|
.col-sm-6
|
||||||
|
button.btn.btn-primary.btn-block(@click='inviteNewUsers("email")') {{sendInviteText}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
|
import filter from 'lodash/filter';
|
||||||
|
import map from 'lodash/map';
|
||||||
|
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: ['group'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
invitees: [],
|
||||||
|
emails: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
bModal,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
inviter () {
|
||||||
|
return this.user.profile.name;
|
||||||
|
},
|
||||||
|
sendInviteText () {
|
||||||
|
if (!this.group) return 'Send Invites';
|
||||||
|
return this.group.sendInviteText;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
addUuid () {
|
||||||
|
this.invitees.push({uuid: ''});
|
||||||
|
},
|
||||||
|
addEmail () {
|
||||||
|
this.emails.push({name: '', email: ''});
|
||||||
|
},
|
||||||
|
inviteNewUsers (inviteMethod) {
|
||||||
|
if (!this.group._id) {
|
||||||
|
if (!this.group.name) this.group.name = this.$t('possessiveParty', {name: this.user.profile.name});
|
||||||
|
|
||||||
|
// @TODO: Add dispatch
|
||||||
|
// return Groups.Group.create(this.group)
|
||||||
|
// .then(function(response) {
|
||||||
|
// this.group = response.data.data;
|
||||||
|
// _inviteByMethod(inviteMethod);
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inviteByMethod(inviteMethod);
|
||||||
|
},
|
||||||
|
// inviteByMethod (inviteMethod) {
|
||||||
|
// let invitationDetails;
|
||||||
|
//
|
||||||
|
// if (inviteMethod === 'email') {
|
||||||
|
// let emails = this.getEmails();
|
||||||
|
// invitationDetails = { inviter: this.inviter, emails };
|
||||||
|
// } else if (inviteMethod === 'uuid') {
|
||||||
|
// let uuids = this.getOnlyUuids();
|
||||||
|
// invitationDetails = { uuids };
|
||||||
|
// } else {
|
||||||
|
// return alert('Invalid invite method.');
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @TODO: Add dispatch
|
||||||
|
// Groups.Group.invite(this.group._id, invitationDetails)
|
||||||
|
// .then(function () {
|
||||||
|
// let invitesSent = invitationDetails.emails || invitationDetails.uuids;
|
||||||
|
// let invitationString = invitesSent.length > 1 ? 'invitationsSent' : 'invitationSent';
|
||||||
|
//
|
||||||
|
// Notification.text(window.env.t(invitationString));
|
||||||
|
//
|
||||||
|
// _resetInvitees();
|
||||||
|
//
|
||||||
|
// if (this.group.type === 'party') {
|
||||||
|
// $rootScope.hardRedirect('/#/options/groups/party');
|
||||||
|
// } else {
|
||||||
|
// $rootScope.hardRedirect('/#/options/groups/guilds/' + this.group._id);
|
||||||
|
// }
|
||||||
|
// }, function(){
|
||||||
|
// _resetInvitees();
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
getOnlyUuids () {
|
||||||
|
let uuids = map(this.invitees, 'uuid');
|
||||||
|
let filteredUuids = filter(uuids, (id) => {
|
||||||
|
return id !== '';
|
||||||
|
});
|
||||||
|
return filteredUuids;
|
||||||
|
},
|
||||||
|
getEmails () {
|
||||||
|
let emails = filter(this.emails, (obj) => {
|
||||||
|
return obj.email !== '';
|
||||||
|
});
|
||||||
|
return emails;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
h3(v-once) {{ $t('welcomeToTavern') }}
|
h3(v-once) {{ $t('welcomeToTavern') }}
|
||||||
|
|
||||||
textarea(:placeholder="$t('chatPlaceHolder')", v-model='newMessage')
|
textarea(:placeholder="$t('chatPlaceHolder')", v-model='newMessage')
|
||||||
|
autocomplete(:text='newMessage', v-on:select="selectedAutocomplete")
|
||||||
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
|
button.btn.btn-secondary.send-chat.float-right(v-once, @click='sendMessage()') {{ $t('send') }}
|
||||||
|
|
||||||
.container.community-guidelines(v-if='communityGuidelinesAccepted')
|
.container.community-guidelines(v-if='communityGuidelinesAccepted')
|
||||||
@@ -282,6 +283,7 @@ import { mapState } from 'client/libs/store';
|
|||||||
|
|
||||||
import { TAVERN_ID } from '../../../common/script/constants';
|
import { TAVERN_ID } from '../../../common/script/constants';
|
||||||
import chatMessage from '../chat/chatMessages';
|
import chatMessage from '../chat/chatMessages';
|
||||||
|
import autocomplete from '../chat/autoComplete';
|
||||||
|
|
||||||
import gemIcon from 'assets/svg/gem.svg';
|
import gemIcon from 'assets/svg/gem.svg';
|
||||||
import questIcon from 'assets/svg/quest.svg';
|
import questIcon from 'assets/svg/quest.svg';
|
||||||
@@ -294,6 +296,7 @@ import downIcon from 'assets/svg/down.svg';
|
|||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
chatMessage,
|
chatMessage,
|
||||||
|
autocomplete,
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -399,6 +402,9 @@ export default {
|
|||||||
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
|
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
selectedAutocomplete (newText) {
|
||||||
|
this.newMessage = newText;
|
||||||
|
},
|
||||||
aggreeToGuideLines () {
|
aggreeToGuideLines () {
|
||||||
// @TODO:
|
// @TODO:
|
||||||
},
|
},
|
||||||
175
website/client/components/hall/heroes.vue
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.row
|
||||||
|
small.muted(v-html="$t('blurbHallContributors')")
|
||||||
|
.well(v-if='user.contributor.admin')
|
||||||
|
h2 {{ $t('rewardUser') }}
|
||||||
|
form(v-submit='loadHero(_heroID)')
|
||||||
|
.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-show='hero', v-submit='saveHero(hero)')
|
||||||
|
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 <strong>item path</strong>. E.g., <code>items.pets.BearCub-Zombie</code> or <code>items.gear.owned.head_special_0</code> or <code>items.gear.equipped.head</code>. You can find all the item paths below.
|
||||||
|
br
|
||||||
|
input.form-control(type='text',placeholder='Value (eg, 5)',v-model='hero.itemVal')
|
||||||
|
small.muted Enter the <strong>item value</strong>. E.g., <code>5</code> or <code>false</code> or <code>head_warrior_3</code>. All values are listed in the All Item Paths section below.
|
||||||
|
accordion
|
||||||
|
accordion-group(heading='All Item Paths')
|
||||||
|
pre {{allItemPaths}}
|
||||||
|
accordion-group(heading='Current Items')
|
||||||
|
pre {{toJson(hero.items, true)}}
|
||||||
|
accordion-group(heading='Auth')
|
||||||
|
h4 Auth
|
||||||
|
pre {{toJson(hero.auth)}}
|
||||||
|
.form-group
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', v-model='hero.flags.chatRevoked')
|
||||||
|
| Chat Privileges Revoked
|
||||||
|
.form-group
|
||||||
|
.checkbox
|
||||||
|
label
|
||||||
|
input(type='checkbox', v-model='hero.auth.blocked')
|
||||||
|
| Blocked
|
||||||
|
|
||||||
|
// h4 Backer Status
|
||||||
|
// Add backer stuff like tier, disable adds, etcs
|
||||||
|
.form-group
|
||||||
|
input.form-control.btn.btn-primary(type='submit')
|
||||||
|
| {{ $t('save') }}
|
||||||
|
|
||||||
|
.table-responsive
|
||||||
|
table.table.table-striped
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th {{ $t('name') }}
|
||||||
|
th(v-if='user.contributor.admin') {{ $t('UUID') }}
|
||||||
|
th {{ $t('contribLevel') }}
|
||||||
|
th {{ $t('title') }}
|
||||||
|
th {{ $t('contributions') }}
|
||||||
|
tbody
|
||||||
|
tr(v-repeat='hero in heroes')
|
||||||
|
td
|
||||||
|
span(v-if='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)')
|
||||||
|
| {{hero.profile.name}}
|
||||||
|
span(v-class='userAdminGlyphiconStyle(hero)')
|
||||||
|
span(v-if='!hero.contributor.admin')
|
||||||
|
a.label.label-default(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}}
|
||||||
|
td {{hero.contributor.level}}
|
||||||
|
td {{hero.contributor.text}}
|
||||||
|
td
|
||||||
|
markdown(text='hero.contributor.contributions', target='_blank')
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import keys from 'lodash/keys';
|
||||||
|
import each from 'lodash/each';
|
||||||
|
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
import quests from 'common/script/content/quests';
|
||||||
|
import { mountInfo, petInfo } from 'common/script/content/stable';
|
||||||
|
import { food, hatchingPotions, special } from 'common/script/content';
|
||||||
|
import gear from 'common/script/content/gear';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
heroes: [],
|
||||||
|
hero: {},
|
||||||
|
currentHeroIndex: -1,
|
||||||
|
allItemPaths: this.getAllItemPaths(),
|
||||||
|
quests,
|
||||||
|
mountInfo,
|
||||||
|
petInfo,
|
||||||
|
food,
|
||||||
|
hatchingPotions,
|
||||||
|
special,
|
||||||
|
gear,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.heroes = await this.$store.dispatch('hall:getHeroes');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
getAllItemPaths () {
|
||||||
|
let questsFormat = this.getFormattedItemReference('items.quests', keys(this.quests), 'Numeric Quantity');
|
||||||
|
let mountsFormat = this.getFormattedItemReference('items.mounts', keys(this.mountInfo), 'Boolean');
|
||||||
|
let foodFormat = this.getFormattedItemReference('items.food', keys(this.food), 'Numeric Quantity');
|
||||||
|
let eggsFormat = this.getFormattedItemReference('items.eggs', keys(this.eggs), 'Numeric Quantity');
|
||||||
|
let hatchingPotionsFormat = this.getFormattedItemReference('items.hatchingPotions', keys(this.hatchingPotions), 'Numeric Quantity');
|
||||||
|
let petsFormat = this.getFormattedItemReference('items.pets', keys(this.petInfo), '-1: Owns Mount, 0: Not Owned, 1-49: Progress to mount');
|
||||||
|
let specialFormat = this.getFormattedItemReference('items.special', keys(this.special), 'Numeric Quantity');
|
||||||
|
let gearFormat = this.getFormattedItemReference('items.gear.owned', keys(this.gear.flat), 'Boolean');
|
||||||
|
|
||||||
|
let equippedGearFormat = '\nEquipped Gear:\n\titems.gear.{equipped/costume}.{head/headAccessory/eyewear/armor/body/back/shield/weapon}.{gearKey}\n';
|
||||||
|
let equippedPetFormat = '\nEquipped Pet:\n\titems.currentPet.{petKey}\n';
|
||||||
|
let equippedMountFormat = '\nEquipped Mount:\n\titems.currentMount.{mountKey}\n';
|
||||||
|
|
||||||
|
let data = questsFormat.concat(mountsFormat, foodFormat, eggsFormat, hatchingPotionsFormat, petsFormat, specialFormat, gearFormat, equippedGearFormat, equippedPetFormat, equippedMountFormat);
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
getFormattedItemReference (pathPrefix, itemKeys, values) {
|
||||||
|
let finishedString = '\n'.concat('path: ', pathPrefix, ', ', 'value: {', values, '}\n');
|
||||||
|
|
||||||
|
each(itemKeys, (key) => {
|
||||||
|
finishedString = finishedString.concat('\t', pathPrefix, '.', key, '\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
return finishedString;
|
||||||
|
},
|
||||||
|
async loadHero (uuid, heroIndex) {
|
||||||
|
this.currentHeroIndex = heroIndex;
|
||||||
|
let hero = await this.$store.dispatch('hall:getHero', { uuid });
|
||||||
|
this.hero = hero;
|
||||||
|
},
|
||||||
|
async saveHero (hero) {
|
||||||
|
this.hero.contributor.admin = this.hero.contributor.level > 7 ? true : false;
|
||||||
|
let heroUpdated = await this.$store.dispatch('hall:updateHero', { heroDetails: hero });
|
||||||
|
// @TODO: Import
|
||||||
|
// Notification.text("User updated");
|
||||||
|
this.hero = {};
|
||||||
|
this._heroID = -1;
|
||||||
|
this.heroes[this.currentHeroIndex] = heroUpdated;
|
||||||
|
this.currentHeroIndex = -1;
|
||||||
|
},
|
||||||
|
populateContributorInput (id, index) {
|
||||||
|
this._heroID = id;
|
||||||
|
window.scrollTo(0, 200);
|
||||||
|
this.loadHero(id, index);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
19
website/client/components/hall/index.vue
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.row
|
||||||
|
secondary-menu.col-12
|
||||||
|
router-link.nav-link(:to="{name: 'contributors'}", exact, :class="{'active': $route.name === 'contributors'}") {{ $t('hallContributors') }}
|
||||||
|
router-link.nav-link(:to="{name: 'patrons'}", :class="{'active': $route.name === 'patrons'}") {{ $t('hallPatrons') }}
|
||||||
|
|
||||||
|
.col-12
|
||||||
|
router-view
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import SecondaryMenu from 'client/components/secondaryMenu';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
SecondaryMenu,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
53
website/client/components/hall/patrons.vue
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.row
|
||||||
|
small.muted {{ $t('blurbHallPatrons') }}
|
||||||
|
.table-responsive
|
||||||
|
table.table.table-striped(infinite-scroll="loadMore()")
|
||||||
|
thead
|
||||||
|
tr
|
||||||
|
th {{ $t('name') }}
|
||||||
|
th(v-if='user.contributor.admin') {{ $t('UUID') }}
|
||||||
|
th {{ $t('backerTier') }}
|
||||||
|
tbody
|
||||||
|
tr(v-for='patron in patrons')
|
||||||
|
td
|
||||||
|
a.label.label-default(v-class='userLevelStyle(patron)', @click='clickMember(patron._id, true)')
|
||||||
|
| {{patron.profile.name}}
|
||||||
|
td(v-if='user.contributor.admin') {{patron._id}}
|
||||||
|
td {{patron.backer.tier}}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
patrons: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
this.patrons = await this.$store.dispatch('hall:getPatrons', { page: 0 });
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// @TODO: This is used to style usernames. WE should abstract this to helper mixer
|
||||||
|
userLevelStyle (user, style) {
|
||||||
|
style = style || '';
|
||||||
|
let npc = user && user.backer && user.backer.npc ? user.backer.npc : '';
|
||||||
|
let level = user && user.contributor && user.contributor.level ? user.contributor.level : '';
|
||||||
|
style += this.userLevelStyleFromLevel(level, npc, style);
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
userLevelStyleFromLevel (level, npc, style) {
|
||||||
|
style = style || '';
|
||||||
|
if (npc) style += ' label-npc';
|
||||||
|
if (level) style += ` label-contributor-${level}`;
|
||||||
|
return style;
|
||||||
|
},
|
||||||
|
//@TODO: Import member modal - clickMember()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
143
website/client/components/members/memberModal.vue
Normal file
@@ -0,0 +1,143 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
b-modal#member-detail-modal(title="Empty", size='lg')
|
||||||
|
.modal-header
|
||||||
|
h4
|
||||||
|
span {{profile.profile.name}}
|
||||||
|
span(v-if='contribText && profile.contributor.level') - {{contribText(profile.contributor, profile.backer)}}
|
||||||
|
.modal-body
|
||||||
|
.container-fluid
|
||||||
|
.row
|
||||||
|
.col-md-6
|
||||||
|
img.img-renderiv-auto(v-if='profile.profile.imageUrl', :src='profile.profile.imageUrl')
|
||||||
|
markdown(v-if='profile.profile.blurb', text='profile.profile.blurb')
|
||||||
|
ul.muted.list-unstyled(v-if='profile.auth.timestamps')
|
||||||
|
li {{profile._id}}
|
||||||
|
li(v-if='profile.auth.timestamps.created')
|
||||||
|
|
|
||||||
|
| {{ $t('memberSince') }}
|
||||||
|
|
|
||||||
|
| {{profile.auth.timestamps.created | date:user.preferences.dateFormat}} -
|
||||||
|
li(v-if='profile.auth.timestamps.loggedin')
|
||||||
|
|
|
||||||
|
| {{ $t('lastLoggedIn') }}
|
||||||
|
|
|
||||||
|
| {{profile.auth.timestamps.loggedin | date:user.preferences.dateFormat}} -
|
||||||
|
h3 {{ $t('stats') }}
|
||||||
|
// @TODO: Figure out why this isn't showing up in front page
|
||||||
|
.label.label-info {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[profile.stats.class] }}
|
||||||
|
// include ../profiles/stats_all
|
||||||
|
.col-md-6
|
||||||
|
.row
|
||||||
|
//@TODO: +herobox()
|
||||||
|
.row
|
||||||
|
h3 {{ $t('achievements') }}
|
||||||
|
//include ../profiles/achievements
|
||||||
|
.modal-footer
|
||||||
|
.btn-group.pull-left(v-if='user')
|
||||||
|
button.btn.btn-md.btn-default(v-if='user.inbox.blocks.indexOf(profile._id) !== -1', :tooltip="$t('unblock')", @click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
||||||
|
span.glyphicon.glyphicon-plus
|
||||||
|
button.btn.btn-md.btn-default(v-if='profile._id != user._id && !profile.contributor.admin && !(user.inbox.blocks | contains:profile._id)', tooltip {{ $t('block') }}, @click="User.blockUser({params:{uuid:profile._id}})", tooltip-placement='right')
|
||||||
|
span.glyphicon.glyphicon-ban-circle
|
||||||
|
button.btn.btn-md.btn-default(:tooltip="$t('sendPM')", @click="openModal('private-message',{controller:'MemberModalCtrl'})", tooltip-placement='right')
|
||||||
|
span.glyphicon.glyphicon-envelope
|
||||||
|
button.btn.btn-md.btn-default(:tooltip="$t('sendGift')", @click="openModal('send-gift',{controller:'MemberModalCtrl'})", tooltip-placement='right')
|
||||||
|
span.glyphicon.glyphicon-gift
|
||||||
|
button.btn.btn-default(@click='close()') {{ $t('close') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import bModal from 'bootstrap-vue/lib/components/modal';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
// @TODO: We should probably use a store. Only view one member at a time?
|
||||||
|
props: ['profile'],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
// @TODO: We don't send subscriptions so the structure has changed in the back. Update this when we update the views.
|
||||||
|
gift: {
|
||||||
|
type: 'gems',
|
||||||
|
gems: {amount: 0, fromBalance: true},
|
||||||
|
subscription: {key: ''},
|
||||||
|
message: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
bModal,
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
// @TODO: This.$store.selectmember
|
||||||
|
// if (member) {
|
||||||
|
// this.profile = member;
|
||||||
|
//
|
||||||
|
// this.achievements = Shared.achievements.getAchievementsForProfile(this.profile);
|
||||||
|
// this.achievPopoverPlacement = 'left';
|
||||||
|
// this.achievAppendToBody = 'false'; // append-to-body breaks popovers in modal windows
|
||||||
|
// }
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
timestamp (timestamp) {
|
||||||
|
return moment(timestamp).format(this.user.preferences.dateFormat.toUpperCase());
|
||||||
|
},
|
||||||
|
// @TODO: create mixin for stats: this.statCalc = Stats;
|
||||||
|
// @TODO: create mixin or library for constume functions this.costume = Costume;
|
||||||
|
keyDownListener (e) {
|
||||||
|
if (e.key === 'Enter' && (e.metaKey || e.ctrlKey)) {
|
||||||
|
this.sendPrivateMessage(this.profile._id, this._message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// @TODO: Inbox?
|
||||||
|
sendPrivateMessage (uuid, message) {
|
||||||
|
if (!message) return;
|
||||||
|
|
||||||
|
// Members.sendPrivateMessage(message, uuid)
|
||||||
|
// .then(function (response) {
|
||||||
|
// Notification.text(window.env.t('messageSentAlert'));
|
||||||
|
// $rootScope.User.sync();
|
||||||
|
// this.$close();
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
async sendGift (uuid) {
|
||||||
|
await this.$store.dispatch('members:transferGems', {
|
||||||
|
message: this.gift.message,
|
||||||
|
toUserId: uuid,
|
||||||
|
gemAmount: this.gift.gems.amount,
|
||||||
|
});
|
||||||
|
|
||||||
|
// @TODO: Notification.text(this.$t('sentGems'));
|
||||||
|
// @TODO: What needs to be synced? $rootScope.User.sync();
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
async reportAbuse (reporter, message, groupId) {
|
||||||
|
let response = await this.$store.dispatch('chat:flag', {
|
||||||
|
groupId,
|
||||||
|
chatId: message.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.flags = response.flags;
|
||||||
|
message.flagCount = response.flagCount;
|
||||||
|
// @TODO: Notification.text(this.$t('abuseReported'));
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
async clearFlagCount (message, groupId) {
|
||||||
|
await this.$store.dispatch('chat:clearFlagCount', {
|
||||||
|
groupId,
|
||||||
|
chatId: message.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
message.flagCount = 0;
|
||||||
|
// @TODO: Notification.text("Flags cleared");
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
close () {
|
||||||
|
this.$root.$emit('hide::modal', 'member-detail-modal');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
198
website/client/components/notificationMenu.vue
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
.item-with-icon.item-notifications.dropdown
|
||||||
|
.svg-icon(v-html="icons.notifications")
|
||||||
|
// span.glyphicon(:class='iconClasses()')
|
||||||
|
// span.notification-counter(v-if='getNotificationsCount()') {{getNotificationsCount()}}
|
||||||
|
.dropdown-menu.dropdown-menu-right.user-dropdown
|
||||||
|
a.dropdown-item test
|
||||||
|
h4 {{ $t('notifications') }}
|
||||||
|
div
|
||||||
|
ul.toolbar-notifs-notifs
|
||||||
|
li.toolbar-notifs-no-messages(v-if='hasNoNotifications()') {{ $t('noNotifications') }}
|
||||||
|
li(v-if='user.purchased.plan.mysteryItems.length')
|
||||||
|
a(@click='$state.go("options.inventory.drops"); ')
|
||||||
|
span.glyphicon.glyphicon-gift
|
||||||
|
span {{ $t('newSubscriberItem') }}
|
||||||
|
li(v-for='party in user.invitations.parties')
|
||||||
|
a(ui-sref='options.social.party')
|
||||||
|
span.glyphicon.glyphicon-user
|
||||||
|
span {{ $t('invitedTo', {name: party.name}) }}
|
||||||
|
li(v-if='user.flags.cardReceived')
|
||||||
|
a(@click='$state.go("options.inventory.drops"); ')
|
||||||
|
span.glyphicon.glyphicon-envelope
|
||||||
|
span {{ $t('cardReceived') }}
|
||||||
|
a(@click='clearCards()', :popover="$t('clear')", popover-placement='right', popover-trigger='mouseenter',popover-append-to-body='true')
|
||||||
|
span.glyphicon.glyphicon-remove-circle
|
||||||
|
li(v-for='guild in user.invitations.guilds')
|
||||||
|
a(ui-sref='options.social.guilds.public')
|
||||||
|
span.glyphicon.glyphicon-user
|
||||||
|
span {{ $t('invitedTo', {name: guild.name}) }}
|
||||||
|
li(v-if='user.flags.classSelected && !user.preferences.disableClasses && user.stats.points')
|
||||||
|
a(ui-sref='options.profile.stats')
|
||||||
|
span.glyphicon.glyphicon-plus-sign
|
||||||
|
span {{ $t('haveUnallocated', {points: user.stats.points}) }}
|
||||||
|
li(v-for='(k,v) in user.newMessages', v-if='v.value')
|
||||||
|
a(@click='(k === party._id || k === user.party._id) ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k}); ')
|
||||||
|
span.glyphicon.glyphicon-comment
|
||||||
|
span {{v.name}}
|
||||||
|
a(@click='clearMessages(k)', :popover="$t('clear')", popover-placement='right', popover-trigger='mouseenter',popover-append-to-body='true')
|
||||||
|
span.glyphicon.glyphicon-remove-circle
|
||||||
|
li(v-for='notification in user.groupNotifications')
|
||||||
|
a(@click='viewGroupApprovalNotification(notification, $index, true)')
|
||||||
|
span(:class="groupApprovalNotificationIcon(notification)")
|
||||||
|
span
|
||||||
|
| {{notification.data.message}}
|
||||||
|
a(@click='viewGroupApprovalNotification(notification, $index)',
|
||||||
|
:popover="$t('clear')",
|
||||||
|
popover-placement='right',
|
||||||
|
popover-trigger='mouseenter',
|
||||||
|
popover-append-to-body='true')
|
||||||
|
span.glyphicon.glyphicon-remove-circle
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import isEmpty from 'lodash/isEmpty';
|
||||||
|
import map from 'lodash/map';
|
||||||
|
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
import quests from 'common/script/content/quests';
|
||||||
|
import notificationsIcon from 'assets/svg/notifications.svg';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
icons: Object.freeze({
|
||||||
|
notifications: notificationsIcon,
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
party () {
|
||||||
|
return {name: ''};
|
||||||
|
// return this.user.party;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
// @TODO: I hate this function, we can do better with a hashmap
|
||||||
|
selectNotificationValue (mysteryValue, invitationValue, cardValue,
|
||||||
|
unallocatedValue, messageValue, noneValue, groupApprovalRequested, groupApproved) {
|
||||||
|
let user = this.user;
|
||||||
|
|
||||||
|
if (user.purchased && user.purchased.plan && user.purchased.plan.mysteryItems && user.purchased.plan.mysteryItems.length) {
|
||||||
|
return mysteryValue;
|
||||||
|
} else if (user.invitations.parties && user.invitations.parties.length > 0 || user.invitations.guilds && user.invitations.guilds.length > 0) {
|
||||||
|
return invitationValue;
|
||||||
|
} else if (user.flags.cardReceived) {
|
||||||
|
return cardValue;
|
||||||
|
} else if (user.flags.classSelected && !(user.preferences && user.preferences.disableClasses) && user.stats.points) {
|
||||||
|
return unallocatedValue;
|
||||||
|
} else if (!isEmpty(user.newMessages)) {
|
||||||
|
return messageValue;
|
||||||
|
} else if (!isEmpty(user.groupNotifications)) {
|
||||||
|
let groupNotificationTypes = map(user.groupNotifications, 'type');
|
||||||
|
if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVAL') !== -1) {
|
||||||
|
return groupApprovalRequested;
|
||||||
|
} else if (groupNotificationTypes.indexOf('GROUP_TASK_APPROVED') !== -1) {
|
||||||
|
return groupApproved;
|
||||||
|
}
|
||||||
|
return noneValue;
|
||||||
|
} else {
|
||||||
|
return noneValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hasQuestProgress () {
|
||||||
|
let user = this.user;
|
||||||
|
if (user.party.quest) {
|
||||||
|
let userQuest = quests[user.party.quest.key];
|
||||||
|
|
||||||
|
if (!userQuest) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (userQuest.boss && user.party.quest.progress.up > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (userQuest.collect && user.party.quest.progress.collectedItems > 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
getQuestInfo () {
|
||||||
|
let user = this.user;
|
||||||
|
let questInfo = {};
|
||||||
|
if (user.party.quest) {
|
||||||
|
let userQuest = quests[user.party.quest.key];
|
||||||
|
|
||||||
|
questInfo.title = userQuest.text();
|
||||||
|
|
||||||
|
if (userQuest.boss) {
|
||||||
|
questInfo.body = this.$t('questTaskDamage', { damage: user.party.quest.progress.up.toFixed(1) });
|
||||||
|
} else if (userQuest.collect) {
|
||||||
|
questInfo.body = this.$t('questTaskCollection', { items: user.party.quest.progress.collectedItems });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return questInfo;
|
||||||
|
},
|
||||||
|
clearMessages () {
|
||||||
|
this.$store.dispatch('chat:markChatSeen');
|
||||||
|
},
|
||||||
|
clearCards () {
|
||||||
|
this.$store.dispatch('chat:clearCards');
|
||||||
|
},
|
||||||
|
getNotificationsCount () {
|
||||||
|
let count = 0;
|
||||||
|
|
||||||
|
if (this.user.invitations.parties) {
|
||||||
|
count += this.user.invitations.parties.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.purchased.plan && this.user.purchased.plan.mysteryItems.length) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.invitations.guilds) {
|
||||||
|
count += this.user.invitations.guilds.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.flags.classSelected && !this.user.preferences.disableClasses && this.user.stats.points) {
|
||||||
|
count += this.user.stats.points > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.user.newMessages) {
|
||||||
|
count += Object.keys(this.user.newMessages).length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
iconClasses () {
|
||||||
|
return this.selectNotificationValue(
|
||||||
|
'glyphicon-gift',
|
||||||
|
'glyphicon-user',
|
||||||
|
'glyphicon-envelope',
|
||||||
|
'glyphicon-plus-sign',
|
||||||
|
'glyphicon-comment',
|
||||||
|
'glyphicon-comment inactive',
|
||||||
|
'glyphicon-question-sign',
|
||||||
|
'glyphicon-ok-sign'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
hasNoNotifications () {
|
||||||
|
return this.selectNotificationValue(false, false, false, false, false, true, false, false);
|
||||||
|
},
|
||||||
|
viewGroupApprovalNotification (notification, index, navigate) {
|
||||||
|
// @TODO: USe notifications: User.readNotification(notification.id);
|
||||||
|
this.user.groupNotifications.splice(index, 1);
|
||||||
|
return navigate; // @TODO: remove
|
||||||
|
// @TODO: this.$route.go if (navigate) $state.go('options.social.guilds.detail', {gid: notification.data.groupId});
|
||||||
|
},
|
||||||
|
groupApprovalNotificationIcon (notification) {
|
||||||
|
if (notification.type === 'GROUP_TASK_APPROVAL') {
|
||||||
|
return 'glyphicon glyphicon-question-sign';
|
||||||
|
} else if (notification.type === 'GROUP_TASK_APPROVED') {
|
||||||
|
return 'glyphicon glyphicon-ok-sign';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
401
website/client/components/notifications.vue
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import moment from 'moment';
|
||||||
|
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
data () {
|
||||||
|
// Levels that already display modals and should not trigger generic Level Up
|
||||||
|
let unlockLevels = {
|
||||||
|
3: 'drop system',
|
||||||
|
10: 'class system',
|
||||||
|
50: 'Orb of Rebirth',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Avoid showing the same notiication more than once
|
||||||
|
let lastShownNotifications = [];
|
||||||
|
let alreadyReadNotification = [];
|
||||||
|
|
||||||
|
return {
|
||||||
|
unlockLevels,
|
||||||
|
lastShownNotifications,
|
||||||
|
alreadyReadNotification,
|
||||||
|
isRunningYesterdailies: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({user: 'user.data'}),
|
||||||
|
// https://stackoverflow.com/questions/42133894/vue-js-how-to-properly-watch-for-nested-properties/42134176#42134176
|
||||||
|
baileyShouldShow () {
|
||||||
|
return this.user.flags.newStuff;
|
||||||
|
},
|
||||||
|
userHp () {
|
||||||
|
return this.user.stats.hp;
|
||||||
|
},
|
||||||
|
userExp () {
|
||||||
|
return this.user.stats.exp;
|
||||||
|
},
|
||||||
|
userGp () {
|
||||||
|
return this.user.stats.gp;
|
||||||
|
},
|
||||||
|
userMp () {
|
||||||
|
return this.user.stats.mp;
|
||||||
|
},
|
||||||
|
userLvl () {
|
||||||
|
return this.user.stats.lvl;
|
||||||
|
},
|
||||||
|
userClassSelect () {
|
||||||
|
return !this.user.flags.classSelected && this.user.stats.lvl >= 10;
|
||||||
|
},
|
||||||
|
userNotifications () {
|
||||||
|
return this.user.notifications;
|
||||||
|
},
|
||||||
|
userAchievements () {
|
||||||
|
// @TODO: does this watch deeply?
|
||||||
|
return this.user.achievements;
|
||||||
|
},
|
||||||
|
armoireEmpty () {
|
||||||
|
return this.user.flags.armoireEmpty;
|
||||||
|
},
|
||||||
|
questCompleted () {
|
||||||
|
return this.user.party.quest.completed;
|
||||||
|
},
|
||||||
|
invitedToQuest () {
|
||||||
|
return this.user.party.quest.RSVPNeeded && !this.user.party.quest.completed;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
baileyShouldShow () {
|
||||||
|
// @TODO: this.openModal('newStuff', {size:'lg'});
|
||||||
|
},
|
||||||
|
userHp (after, before) {
|
||||||
|
if (after <= 0) {
|
||||||
|
this.playSound('Death');
|
||||||
|
// @TODO: this.openModal('death', {keyboard:false, backdrop:'static'});
|
||||||
|
} else if (after <= 30 && !this.user.flags.warnedLowHealth) {
|
||||||
|
// @TODO: this.openModal('lowHealth', {keyboard:false, backdrop:'static', controller:'UserCtrl', track:'Health Warning'});
|
||||||
|
}
|
||||||
|
if (after === before) return;
|
||||||
|
if (this.user.stats.lvl === 0) return;
|
||||||
|
// @TODO: Notification.hp(after - before, 'hp');
|
||||||
|
|
||||||
|
// @TODO: I am pretty sure we no long need this with $store
|
||||||
|
// this.$broadcast('syncPartyRequest', {
|
||||||
|
// type: 'user_update',
|
||||||
|
// user: this.user,
|
||||||
|
// });
|
||||||
|
|
||||||
|
if (after < 0) this.playSound('Minus_Habit');
|
||||||
|
},
|
||||||
|
userExp (after, before) {
|
||||||
|
if (after === before) return;
|
||||||
|
if (this.user.stats.lvl === 0) return;
|
||||||
|
// @TODO: Notification.exp(after - before);
|
||||||
|
},
|
||||||
|
userGp (after, before) {
|
||||||
|
if (after === before) return;
|
||||||
|
if (this.user.stats.lvl === 0) return;
|
||||||
|
|
||||||
|
let money = after - before;
|
||||||
|
let bonus;
|
||||||
|
if (this.user._tmp) {
|
||||||
|
bonus = this.user._tmp.streakBonus || 0;
|
||||||
|
}
|
||||||
|
// @TODO: Notification.gp(money, bonus || 0);
|
||||||
|
|
||||||
|
// Append Bonus
|
||||||
|
if (money > 0 && Boolean(bonus)) {
|
||||||
|
if (bonus < 0.01) bonus = 0.01;
|
||||||
|
// @TODO: Notification.text("+ " + Notification.coins(bonus) + ' ' + window.env.t('streakCoins'));
|
||||||
|
delete this.user._tmp.streakBonus;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
userMp (after, before) {
|
||||||
|
if (after === before) return;
|
||||||
|
if (!this.user.flags.classSelected || this.user.preferences.disableClasses) return;
|
||||||
|
// let mana = after - before;
|
||||||
|
// @TODO: Notification.mp(mana);
|
||||||
|
},
|
||||||
|
userLvl (after, before) {
|
||||||
|
if (after <= before) return;
|
||||||
|
// @TODO: Notification.lvl();
|
||||||
|
this.playSound('Level_Up');
|
||||||
|
if (this.user._tmp && this.user._tmp.drop && this.user._tmp.drop.type === 'Quest') return;
|
||||||
|
if (this.unlockLevels[`${after}`]) return;
|
||||||
|
// @TODO: if (!this.user.preferences.suppressModals.levelUp) this.openModal('levelUp', {controller:'UserCtrl', size:'sm'});
|
||||||
|
},
|
||||||
|
userClassSelect (after) {
|
||||||
|
if (!after) return;
|
||||||
|
// @TODO: this.openModal('chooseClass', {controller:'UserCtrl', keyboard:false, backdrop:'static'});
|
||||||
|
},
|
||||||
|
userNotifications (after) {
|
||||||
|
if (!this.user._wrapped) return;
|
||||||
|
if (this.user.needsCron) return;
|
||||||
|
this.handleUserNotifications(after);
|
||||||
|
},
|
||||||
|
userAchievements () {
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
},
|
||||||
|
armoireEmpty (after, before) {
|
||||||
|
if (after === before || after === false) return;
|
||||||
|
// @TODO: this.openModal('armoireEmpty');
|
||||||
|
},
|
||||||
|
questCompleted (after) {
|
||||||
|
if (!after) return;
|
||||||
|
// @TODO: this.openModal('questCompleted', {controller:'InventoryCtrl'});
|
||||||
|
},
|
||||||
|
invitedToQuest (after) {
|
||||||
|
if (after !== true) return;
|
||||||
|
// @TODO: this.openModal('questInvitation', {controller:'PartyCtrl'});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
playSound () {
|
||||||
|
// @TODO:
|
||||||
|
},
|
||||||
|
runYesterDailies () {
|
||||||
|
// @TODO: Hopefully we don't need this even we load correctly
|
||||||
|
if (this.isRunningYesterdailies) return;
|
||||||
|
|
||||||
|
// let userLastCron = moment(this.user.lastCron).local();
|
||||||
|
// let userDayStart = moment().startOf('day').add({ hours: this.user.preferences.dayStart });
|
||||||
|
|
||||||
|
if (!this.user.needsCron) return;
|
||||||
|
let dailys = this.user.dailys;
|
||||||
|
|
||||||
|
if (!this.appLoaded) return;
|
||||||
|
|
||||||
|
this.isRunningYesterdailies = true;
|
||||||
|
|
||||||
|
// let yesterDay = moment().subtract('1', 'day').startOf('day').add({ hours: this.user.preferences.dayStart });
|
||||||
|
let yesterDailies = [];
|
||||||
|
dailys.forEach((task) => {
|
||||||
|
if (task && task.group.approval && task.group.approval.requested) return;
|
||||||
|
if (task.completed) return;
|
||||||
|
// @TODO: let shouldDo = Shared.shouldDo(yesterDay, task);
|
||||||
|
let shouldDo = false;
|
||||||
|
|
||||||
|
if (task.yesterDaily && shouldDo) yesterDailies.push(task);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (yesterDailies.length === 0) {
|
||||||
|
// @TODO:
|
||||||
|
// User.runCron().then(function () {
|
||||||
|
// isRunningYesterdailies = false;
|
||||||
|
// handleUserNotifications(this.user);
|
||||||
|
// });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @TODO:
|
||||||
|
// let modalScope = this.$new();
|
||||||
|
// modalScope.obj = this.user;
|
||||||
|
// modalScope.taskList = yesterDailies;
|
||||||
|
// modalScope.list = {
|
||||||
|
// showCompleted: false,
|
||||||
|
// type: 'daily',
|
||||||
|
// };
|
||||||
|
// modalScope.processingYesterdailies = true;
|
||||||
|
//
|
||||||
|
// $scope.yesterDailiesModalOpen = true;
|
||||||
|
// $modal.open({
|
||||||
|
// templateUrl: 'modals/yesterDailies.html',
|
||||||
|
// scope: modalScope,
|
||||||
|
// backdrop: 'static',
|
||||||
|
// controller: ['$scope', 'Tasks', 'User', '$rootScope', function ($scope, Tasks, User, $rootScope) {
|
||||||
|
// this.$on('task:scored', function (event, data) {
|
||||||
|
// let task = data.task;
|
||||||
|
// let indexOfTask = _.findIndex($scope.taskList, function (taskInList) {
|
||||||
|
// return taskInList._id === task._id;
|
||||||
|
// });
|
||||||
|
// if (!$scope.taskList[indexOfTask]) return;
|
||||||
|
// $scope.taskList[indexOfTask].group.approval.requested = task.group.approval.requested;
|
||||||
|
// if ($scope.taskList[indexOfTask].group.approval.requested) return;
|
||||||
|
// $scope.taskList[indexOfTask].completed = task.completed;
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// $scope.ageDailies = function () {
|
||||||
|
// User.runCron()
|
||||||
|
// .then(function () {
|
||||||
|
// isRunningYesterdailies = false;
|
||||||
|
// handleUserNotifications(this.user);
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
// }],
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
transferGroupNotification (notification) {
|
||||||
|
if (!this.user.groupNotifications) this.user.groupNotifications = [];
|
||||||
|
this.user.groupNotifications.push(notification);
|
||||||
|
},
|
||||||
|
handleUserNotifications (after) {
|
||||||
|
if (!after || after.length === 0) return;
|
||||||
|
|
||||||
|
let notificationsToRead = [];
|
||||||
|
let scoreTaskNotification = [];
|
||||||
|
|
||||||
|
this.user.groupNotifications = []; // Flush group notifictions
|
||||||
|
|
||||||
|
after.forEach((notification) => {
|
||||||
|
if (this.lastShownNotifications.indexOf(notification.id) !== -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some notifications are not marked read here, so we need to fix this system
|
||||||
|
// to handle notifications differently
|
||||||
|
if (['GROUP_TASK_APPROVED', 'GROUP_TASK_APPROVAL'].indexOf(notification.type) === -1) {
|
||||||
|
this.lastShownNotifications.push(notification.id);
|
||||||
|
if (this.lastShownNotifications.length > 10) {
|
||||||
|
this.lastShownNotifications.splice(0, 9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let markAsRead = true;
|
||||||
|
// @TODO: Use factory function instead
|
||||||
|
switch (notification.type) {
|
||||||
|
case 'GUILD_PROMPT':
|
||||||
|
if (notification.data.textletiant === -1) {
|
||||||
|
// @TODO: this.openModal('testing');
|
||||||
|
} else {
|
||||||
|
// @TODO: this.openModal('testingletiant');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'DROPS_ENABLED':
|
||||||
|
// @TODO: this.openModal('dropsEnabled');
|
||||||
|
break;
|
||||||
|
case 'REBIRTH_ENABLED':
|
||||||
|
// @TODO: this.openModal('rebirthEnabled');
|
||||||
|
break;
|
||||||
|
case 'WON_CHALLENGE':
|
||||||
|
// @TODO:
|
||||||
|
// User.sync().then( function() {
|
||||||
|
// Achievement.displayAchievement('wonChallenge');
|
||||||
|
// });
|
||||||
|
break;
|
||||||
|
case 'STREAK_ACHIEVEMENT':
|
||||||
|
// @TODO: Notification.streak(this.user.achievements.streak);
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
if (!this.user.preferences.suppressModals.streak) {
|
||||||
|
// @TODO: Achievement.displayAchievement('streak', {size: 'md'});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'ULTIMATE_GEAR_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('ultimateGear', {size: 'md'});
|
||||||
|
break;
|
||||||
|
case 'REBIRTH_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('rebirth');
|
||||||
|
break;
|
||||||
|
case 'GUILD_JOINED_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('joinedGuild', {size: 'md'});
|
||||||
|
break;
|
||||||
|
case 'CHALLENGE_JOINED_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('joinedChallenge', {size: 'md'});
|
||||||
|
break;
|
||||||
|
case 'INVITED_FRIEND_ACHIEVEMENT':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('invitedFriend', {size: 'md'});
|
||||||
|
break;
|
||||||
|
case 'NEW_CONTRIBUTOR_LEVEL':
|
||||||
|
this.playSound('Achievement_Unlocked');
|
||||||
|
// @TODO: Achievement.displayAchievement('contributor', {size: 'md'});
|
||||||
|
break;
|
||||||
|
case 'CRON':
|
||||||
|
if (notification.data) {
|
||||||
|
// @TODO: if (notification.data.hp) Notification.hp(notification.data.hp, 'hp');
|
||||||
|
// @TODO: if (notification.data.mp) Notification.mp(notification.data.mp);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'GROUP_TASK_APPROVAL':
|
||||||
|
this.transferGroupNotification(notification);
|
||||||
|
markAsRead = false;
|
||||||
|
break;
|
||||||
|
case 'GROUP_TASK_APPROVED':
|
||||||
|
this.transferGroupNotification(notification);
|
||||||
|
markAsRead = false;
|
||||||
|
break;
|
||||||
|
case 'SCORED_TASK':
|
||||||
|
// Search if it is a read notification
|
||||||
|
for (let i = 0; i < this.alreadyReadNotification.length; i++) {
|
||||||
|
if (this.alreadyReadNotification[i] === notification.id) {
|
||||||
|
markAsRead = false; // Do not let it be read again
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process the notification if it is an unread notification
|
||||||
|
if (markAsRead) {
|
||||||
|
scoreTaskNotification.push(notification);
|
||||||
|
|
||||||
|
// Add to array of read notifications
|
||||||
|
this.alreadyReadNotification.push(notification.id);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'LOGIN_INCENTIVE':
|
||||||
|
// @TODO: Notification.showLoginIncentive(this.user, notification.data, Social.loadWidgets);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (notification.data.headerText && notification.data.bodyText) {
|
||||||
|
// @TODO:
|
||||||
|
// let modalScope = this.$new();
|
||||||
|
// modalScope.data = notification.data;
|
||||||
|
// this.openModal('generic', {scope: modalScope});
|
||||||
|
} else {
|
||||||
|
markAsRead = false; // If the notification is not implemented, skip it
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (markAsRead) notificationsToRead.push(notification.id);
|
||||||
|
});
|
||||||
|
|
||||||
|
let userReadNotifsPromise = false;
|
||||||
|
// @TODO: User.readNotifications(notificationsToRead);
|
||||||
|
|
||||||
|
if (userReadNotifsPromise) {
|
||||||
|
userReadNotifsPromise.then(() => {
|
||||||
|
// Only run this code for scoring approved tasks
|
||||||
|
if (scoreTaskNotification.length > 0) {
|
||||||
|
let approvedTasks = [];
|
||||||
|
for (let i = 0; i < scoreTaskNotification.length; i++) {
|
||||||
|
// Array with all approved tasks
|
||||||
|
approvedTasks.push({
|
||||||
|
params: {
|
||||||
|
task: scoreTaskNotification[i].data.scoreTask,
|
||||||
|
direction: 'up',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show notification of task approved
|
||||||
|
// @TODO: Notification.markdown(scoreTaskNotification[i].data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score approved tasks
|
||||||
|
// TODO: User.bulkScore(approvedTasks);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.user.notifications = []; // reset the notifications
|
||||||
|
},
|
||||||
|
// @TODO: I think I have these handled in the http interceptor
|
||||||
|
// this.$on('responseError500', function(ev, error){
|
||||||
|
// Notification.error(error);
|
||||||
|
// });
|
||||||
|
// this.$on('responseError', function(ev, error){
|
||||||
|
// Notification.error(error, true);
|
||||||
|
// });
|
||||||
|
// this.$on('responseText', function(ev, error){
|
||||||
|
// Notification.text(error);
|
||||||
|
// });
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -15,5 +15,43 @@
|
|||||||
</script>
|
</script>
|
||||||
<script src="https://checkout.stripe.com/v2/checkout.js"></script>
|
<script src="https://checkout.stripe.com/v2/checkout.js"></script>
|
||||||
</script>
|
</script>
|
||||||
|
<script>
|
||||||
|
// Amplitude
|
||||||
|
// var r = window.amplitude || {};
|
||||||
|
// r._q = [];
|
||||||
|
// function a(window) {r[window] = function() {r._q.push([window].concat(Array.prototype.slice.call(arguments, 0)));}}
|
||||||
|
// var i = ["init", "logEvent", "logRevenue", "setUserId", "setUserProperties", "setOptOut", "setVersionName", "setDomain", "setDeviceId", "setGlobalUserProperties"];
|
||||||
|
// for (var o = 0; o < i.length; o++) {a(i[o])}
|
||||||
|
// window.amplitude = r;
|
||||||
|
// amplitude.init(window.env.AMPLITUDE_KEY, user ? user._id : undefined);
|
||||||
|
//
|
||||||
|
// // Google Analytics (aka Universal Analytics)
|
||||||
|
// window['GoogleAnalyticsObject'] = 'ga';
|
||||||
|
// window['ga'] = window['ga'] || function() {
|
||||||
|
// (window['ga'].q = window['ga'].q || []).push(arguments)
|
||||||
|
// }, window['ga'].l = 1 * new Date();
|
||||||
|
// ga('create', window.env.GA_ID, user ? {'userId': user._id} : undefined);
|
||||||
|
//
|
||||||
|
// function loadScripts() {
|
||||||
|
// setTimeout(function() {
|
||||||
|
// // Amplitude
|
||||||
|
// var n = document.createElement("script");
|
||||||
|
// var s = document.getElementsByTagName("script")[0];
|
||||||
|
// n.type = "text/javascript";
|
||||||
|
// n.async = true;
|
||||||
|
// n.src = "https://d24n15hnbwhuhn.cloudfront.net/libs/amplitude-2.2.0-min.gz.js";
|
||||||
|
// s.parentNode.insertBefore(n, s);
|
||||||
|
//
|
||||||
|
// // Google Analytics
|
||||||
|
// var a = document.createElement('script');
|
||||||
|
// var m = document.getElementsByTagName('script')[0];
|
||||||
|
// a.async = 1;
|
||||||
|
// a.src = '//www.google-analytics.com/analytics.js';
|
||||||
|
// m.parentNode.insertBefore(a, m);
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (window.env.NODE_ENV === 'production') loadScripts();
|
||||||
|
//}
|
||||||
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import getStore from './store';
|
|||||||
import StoreModule from './libs/store';
|
import StoreModule from './libs/store';
|
||||||
import './filters/registerGlobals';
|
import './filters/registerGlobals';
|
||||||
import i18n from './libs/i18n';
|
import i18n from './libs/i18n';
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
|
const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env
|
||||||
|
|
||||||
@@ -22,6 +23,16 @@ Vue.config.performance = !IS_PRODUCTION;
|
|||||||
// Disable annoying reminder abour production build in dev mode
|
// Disable annoying reminder abour production build in dev mode
|
||||||
Vue.config.productionTip = IS_PRODUCTION;
|
Vue.config.productionTip = IS_PRODUCTION;
|
||||||
|
|
||||||
|
axios.interceptors.response.use((response) => {
|
||||||
|
return response;
|
||||||
|
}, (error) => {
|
||||||
|
if (error.response.status >= 400) {
|
||||||
|
alert(error.response.data.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
Vue.use(i18n);
|
Vue.use(i18n);
|
||||||
Vue.use(StoreModule);
|
Vue.use(StoreModule);
|
||||||
|
|
||||||
|
|||||||
BIN
website/client/merch/stickermule-logo.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
1
website/client/merch/stickermule-logo.svg
Normal file
|
After Width: | Height: | Size: 5.5 KiB |
BIN
website/client/merch/stickermule.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
website/client/merch/teespring-eu-logo.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
website/client/merch/teespring-eu.png
Normal file
|
After Width: | Height: | Size: 306 KiB |
BIN
website/client/merch/teespring-logo.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
63
website/client/merch/teespring-logo.svg
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="206.8 158.6 1132.2 280" style="enable-background:new 206.8 158.6 1132.2 280;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#30ABE9;}
|
||||||
|
</style>
|
||||||
|
<path class="st0" d="M500.9,182.9c-0.3-0.1-0.6-0.2-0.8-0.2H500c-1.3-0.3-2.5-0.6-3.7-0.9l0,0h-0.1l0,0l-0.3-0.1l-0.6-0.1l0,0l0,0
|
||||||
|
l0,0h-0.1l0,0l0,0l0,0h-0.1l0,0l0,0l-0.5-0.1l0,0c-10.4-2.4-21.1-4.4-31.8-5.7c-11.1-1.4-22.5-2.3-33.8-2.5c-2.2,0-4.5-0.1-6.7-0.1
|
||||||
|
c-7.1,0-14.2,0.2-21.2,0.7h-0.5l0.1,0.5c1.1,9.2-1.8,18.4-8,25.3c-5.4,6-13.1,9.8-21.1,10.4c-0.8,0.1-1.6,0.1-2.5,0.1
|
||||||
|
c-8,0-15.7-3-21.6-8.4c-4.8-4.4-8.3-10.4-9.7-16.7l-0.1-0.5l-0.4,0.1c-10.6,3-21.2,6.5-31.5,10.6c-10,4-19.9,8.5-29.4,13.5
|
||||||
|
c-4.8,2.5-9.5,5.1-14.1,7.8c-2.4,1.4-4.9,2.9-7.3,4.4c-0.1,0-0.2,0.1-0.4,0.2c-0.5,0.3-1.2,0.8-1.9,1.2c-1.3,0.8-1.5,0.9-1.3,1.3
|
||||||
|
l27.3,60l0.2,0.4l0.4-0.2c9.8-5.7,20-10.9,30.3-15.5c0.7-0.3,1.3-0.6,2-0.9l21.4,89.2l0.1,0.6l0.5-0.3c5.7-3.1,10.4-5.4,15-7.3
|
||||||
|
c5.7-2.3,11-4.2,16.1-5.6c9.5-2.7,19.2-4,29.1-4c1.5,0,3.1,0,4.6,0.1c4.7,0.2,10.7,0.7,16.3,2.9c2.6,1,4.6,2.2,6.1,3.7
|
||||||
|
c1.7,1.7,2.6,4,2.5,6.5c0,5.2-4.1,9.4-7.4,12.1c-2.2,1.7-4.6,3.1-6.4,4.1c-2.3,1.3-4.8,2.4-7.3,3.4c-4.9,2-10.1,3.5-16,4.7
|
||||||
|
c-5.8,1.2-11.5,1.9-16.9,2.1c-0.5,0-1,0-1.6,0c-0.7,0-1.3,0-2,0h-0.5c-1.4,0-2.8-0.1-4.2-0.3c-1.5-0.2-2.7-0.3-4-0.7h-0.1
|
||||||
|
c-1.3-0.3-1.7-0.6-1.9-0.7c0.6-0.9,1.6-1.6,2.5-2.2c1-0.6,2.1-1.3,3.4-1.9c2.2-1.1,4.7-2.1,7.7-3c5.1-1.6,10.6-2.7,16.5-3.2
|
||||||
|
c2.8-0.3,5.6-0.4,8.4-0.4c0.3,0,0.7,0,1,0c0.9,0,2.1,0,3.2,0.1c0.2,0,0.5,0,0.7,0.1c0.4,0,0.8,0.1,1.2,0.1h0.1h0.1
|
||||||
|
c3-1,13.1-4.9,15.9-11.4l0.2-0.4l-0.4-0.2c-0.1-0.1-0.3-0.1-0.5-0.2c-0.1,0-0.1,0-0.2-0.1c-1.1-0.3-2.5-0.8-4-1.1
|
||||||
|
c-2.6-0.6-5.4-1.1-8.4-1.5c-3.1-0.4-6.3-0.5-9.5-0.5c-2.6,0-5.2,0.1-7.8,0.4c-6.3,0.6-11.8,1.5-16.7,2.8c-6,1.5-11.1,3.4-15.5,5.8
|
||||||
|
c-5.5,2.9-9.3,6.4-11.5,10.4c-2.4,4.4-2.5,9.6-0.3,13.7c2.1,4,6.3,6.8,12.3,8.4c4,1,8.5,1.6,13.7,1.6c0.9,0,1.9,0,2.9,0
|
||||||
|
c5.4-0.2,11.1-0.8,16.9-1.9c5.6-1.1,11.1-2.5,16.3-4.3c5.7-2,10.7-4.2,15.1-6.7c0.7-0.4,1.2-0.7,1.7-1l1.3-0.8c0,0.2,0,0.4,0,0.6
|
||||||
|
c0,0.6,0,1.1-0.2,1.8c-0.4,2.3-1.3,4.5-2.9,6.6c-2.5,3.4-6.3,6.3-11.6,9c-4.4,2.2-9.4,4.1-15.2,5.8c-5.3,1.5-10.9,2.7-16.4,3.5
|
||||||
|
c-1.2,0.2-2.4,0.3-3.5,0.5l-0.7,0.1c-0.3,0-0.7,0.1-1,0.1c-0.3,0-0.8,0.1-0.8,0.1h-0.4v0.4l0.8,14.4v0.5h0.5l2.3-0.2
|
||||||
|
c2.7-0.3,5.6-0.7,8.5-1.2c12.6-2.2,23-5.4,31.8-9.7c11.4-5.7,18.9-13,22.2-21.7c2-5.4,2.5-11.4,1.3-16.7c-0.3-1.3-0.7-2.6-1.2-3.8
|
||||||
|
c-0.1-0.2-0.2-0.4-0.3-0.7c-0.2-0.3-0.4-0.7-0.4-1c-0.1-0.2,0.2-0.7,0.5-1c0.1-0.1,0.2-0.3,0.3-0.4c1.4-2.3,2.5-4.8,3.2-7.5
|
||||||
|
c1.3-5.2,1-10.8-0.8-15.6c-1.8-4.7-5.2-8.8-9.9-11.8c-4.2-2.7-9.2-4.7-15.4-6c-5.2-1.1-10.7-1.6-16.9-1.6c-11.7,0-23.2,1.5-34,4.6
|
||||||
|
c-5.6,1.6-11.4,3.6-18.4,6.4l-20.9-86.8l-0.1-0.5l-0.5,0.2c-10.6,4-21.1,8.5-31.3,13.6c-1.5,0.7-2.9,1.5-4.4,2.2l-16.3-35.7
|
||||||
|
c0.3-0.2,0.7-0.4,1.4-0.8c0.1-0.1,0.2-0.1,0.2-0.1c4.7-2.7,9.5-5.3,14.3-7.8c9.6-4.9,19.5-9.3,29.6-13.2c4.9-1.9,9.9-3.6,14.9-5.3
|
||||||
|
c2.1,3.6,4.7,6.8,7.7,9.7c6.3,6,14.1,10.1,22.6,11.9c3,0.6,6,0.9,9.1,0.9c5.6,0,11-1,16.1-2.9c7.5-2.8,14.2-7.6,19.3-13.8
|
||||||
|
c2.6-3.1,4.7-6.5,6.4-10.1c0.8-1.8,1.6-3.7,2.2-5.7c0.3-1,0.6-2,0.8-3c0.1-0.5,0.2-1,0.3-1.4l0,0h0.2c0.4,0,0.8,0,1.2,0
|
||||||
|
c2.3-0.1,4.6-0.1,6.9-0.1c9.1,0,18.3,0.4,27.3,1.2c11.3,1,22.6,2.7,33.7,4.9h0.2c1,0.2,1.6,0.3,1.9,0.4l-7.9,38.7
|
||||||
|
c-0.9-0.1-3.2-0.4-4.9-0.7c-1.2-0.2-2.1-0.3-2.4-0.3c-2.8-0.4-5.7-0.7-8.6-0.9c-5.7-0.6-11.5-1-17.3-1.2c-2.9-0.1-5.8-0.2-8.7-0.2
|
||||||
|
c-0.1,0-0.7,0-0.7,0h-0.4v0.4l-3.6,85.5c0,0.2,0.1,0.5,0.5,0.6c0,0,0,0,0.1,0c1.2,0.6,2.4,1.2,3.6,1.8c2.5,1.4,4.7,3,6.5,4.7
|
||||||
|
c0.5,0.4,0.9,0.9,1.3,1.3c0.2,0.2,0.4,0.5,0.6,0.7c0.1,0.1,0.2,0.3,0.3,0.4l0.7,1.4l0.1-1.6l3.8-80.9c11.2,0.6,22.2,1.9,33.6,3.8
|
||||||
|
c1.4,0.2,2.8,0.5,4.3,0.8l0.5,0.1c1.4,0.3,1.4,0.3,1.4,0.3h0.4l0.1-0.4l13.1-64.5C501.9,183.1,501.5,183,500.9,182.9z"/>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M522.4,339.3v-53h-15.2V269h15.2v-25.1h20V269H561v17.3h-18.6v48.1c0,6.3,3,11,8.7,11c3.8,0,7.2-1.7,8.7-3.4
|
||||||
|
l4.8,15.2c-3.6,3.2-9.5,5.9-18.6,5.9C530.4,363.1,522.4,354.7,522.4,339.3z"/>
|
||||||
|
<path class="st0" d="M573.3,314.8c0-26.6,19.4-48.1,46.6-48.1c27.5,0,45,20.9,45,49.8v4.9h-70.7c1.5,13.7,11.8,25.3,29.1,25.3
|
||||||
|
c9.1,0,19.6-3.6,26.2-10.3l9.1,13.1c-9.3,8.7-22.8,13.5-37.2,13.5C593.6,363.1,573.3,343.9,573.3,314.8z M619.7,283.1
|
||||||
|
c-16.9,0-24.9,13.1-25.7,24.1h51.7C645.1,296.4,637.7,283.1,619.7,283.1z"/>
|
||||||
|
<path class="st0" d="M679.1,314.8c0-26.6,19.4-48.1,46.6-48.1c27.5,0,45,20.9,45,49.8v4.9H700c1.5,13.7,11.8,25.3,29.1,25.3
|
||||||
|
c9.1,0,19.6-3.6,26.2-10.3l9.1,13.1c-9.3,8.7-22.8,13.5-37.2,13.5C699.5,363.1,679.1,343.9,679.1,314.8z M725.5,283.1
|
||||||
|
c-16.9,0-24.9,13.1-25.7,24.1h51.7C751,296.4,743.5,283.1,725.5,283.1z"/>
|
||||||
|
<path class="st0" d="M782.7,348.6l9.1-14.2c6.5,6.6,19.2,12.7,31,12.7c12,0,18.2-4.9,18.2-12.2c0-18.1-55.5-5.1-55.5-40.5
|
||||||
|
c0-15,12.9-27.7,36.1-27.7c15.6,0,27.4,5.5,35.5,12.7l-8.4,14.1c-5.5-6.1-15.6-11-27.2-11c-10.4,0-16.9,4.9-16.9,11.4
|
||||||
|
c0,16.2,55.5,4,55.5,40.5c0,16.3-13.5,28.7-38.2,28.7C806.2,363.1,792.2,357.9,782.7,348.6z"/>
|
||||||
|
<path class="st0" d="M900.1,348.1v47.7h-20V269h20v12.5c6.8-9.1,17.5-14.8,29.5-14.8c23.8,0,40.7,17.9,40.7,48.1
|
||||||
|
s-16.9,48.3-40.7,48.3C918,363.1,907.7,357.8,900.1,348.1z M949.7,314.8c0-17.9-10.3-30.4-26-30.4c-9.3,0-19,5.3-23.6,12.2v36.3
|
||||||
|
c4.4,6.8,14.3,12.5,23.6,12.5C939.4,345.4,949.7,332.7,949.7,314.8z"/>
|
||||||
|
<path class="st0" d="M990.2,360.8V269h20v13.3c7-8.7,18.1-15.6,29.8-15.6v19.8c-1.7-0.4-3.6-0.6-6.1-0.6c-8.6,0-19.8,5.7-23.8,12.4
|
||||||
|
v62.5H990.2z"/>
|
||||||
|
<path class="st0" d="M1053.8,245.5c0-6.8,5.5-12.4,12.3-12.4c6.8,0,12.4,5.5,12.4,12.4c0,6.8-5.5,12.4-12.4,12.4
|
||||||
|
S1053.8,252.3,1053.8,245.5z M1056.1,360.8V269h20v91.8H1056.1z"/>
|
||||||
|
<path class="st0" d="M1164,360.8v-57.4c0-14.4-7.4-19-18.6-19c-10.1,0-18.8,6.1-23.6,12.2v64.2h-20V269h20v12.4
|
||||||
|
c6.1-7.2,18.1-14.6,32.5-14.6c19.8,0,29.6,10.6,29.6,29.5v64.6L1164,360.8L1164,360.8z"/>
|
||||||
|
<path class="st0" d="M1208.7,384.4l9.3-14.4c7.6,8.6,17.1,11.8,29.3,11.8c13.1,0,27.2-6.1,27.2-25.7v-10.5
|
||||||
|
c-7,9.3-17.5,15.4-29.5,15.4c-23.4,0-40.7-16.9-40.7-47.1c0-29.8,16.9-47.1,40.7-47.1c11.6,0,22,5.1,29.5,15v-12.7h20v87
|
||||||
|
c0,33.1-24.7,42-47.1,42C1231.6,398,1220.1,394.6,1208.7,384.4z M1274.4,331v-34.4c-4.6-6.7-14.2-12.2-23.6-12.2
|
||||||
|
c-16,0-26,11.8-26,29.5c0,17.7,10.1,29.3,26,29.3C1260.1,343.1,1269.8,337.6,1274.4,331z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 6.2 KiB |
BIN
website/client/merch/teespring-mug-eu-logo.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
website/client/merch/teespring-mug-eu.png
Normal file
|
After Width: | Height: | Size: 99 KiB |
BIN
website/client/merch/teespring-mug-logo.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
website/client/merch/teespring-mug.png
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
website/client/merch/teespring.png
Normal file
|
After Width: | Height: | Size: 394 KiB |
86
website/client/mixins/analytics.js
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
|
import keys from 'lodash/keys';
|
||||||
|
import pick from 'lodash/pick';
|
||||||
|
import includes from 'lodash/includes';
|
||||||
|
|
||||||
|
let REQUIRED_FIELDS = ['hitType', 'eventCategory', 'eventAction'];
|
||||||
|
let ALLOWED_HIT_TYPES = [
|
||||||
|
'pageview',
|
||||||
|
'screenview',
|
||||||
|
'event',
|
||||||
|
'transaction',
|
||||||
|
'item',
|
||||||
|
'social',
|
||||||
|
'exception',
|
||||||
|
'timing',
|
||||||
|
];
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
register (user) {
|
||||||
|
// @TODO: What is was the timeout for?
|
||||||
|
window.amplitude.setUserId(user._id);
|
||||||
|
window.ga('set', {userId: user._id});
|
||||||
|
},
|
||||||
|
login (user) {
|
||||||
|
window.amplitude.setUserId(user._id);
|
||||||
|
window.ga('set', {userId: user._id});
|
||||||
|
},
|
||||||
|
track (properties) {
|
||||||
|
if (this.doesNotHaveRequiredFields(properties)) return false;
|
||||||
|
if (this.doesNotHaveAllowedHitType(properties)) return false;
|
||||||
|
|
||||||
|
window.amplitude.logEvent(properties.eventAction, properties);
|
||||||
|
window.ga('send', properties);
|
||||||
|
},
|
||||||
|
updateUser (properties, user) {
|
||||||
|
properties = properties || {};
|
||||||
|
|
||||||
|
this.gatherUserStats(user, properties);
|
||||||
|
|
||||||
|
window.amplitude.setUserProperties(properties);
|
||||||
|
window.ga('set', properties);
|
||||||
|
},
|
||||||
|
gatherUserStats (user, properties) {
|
||||||
|
if (user._id) properties.UUID = user._id;
|
||||||
|
if (user.stats) {
|
||||||
|
properties.Class = user.stats.class;
|
||||||
|
properties.Experience = Math.floor(user.stats.exp);
|
||||||
|
properties.Gold = Math.floor(user.stats.gp);
|
||||||
|
properties.Health = Math.ceil(user.stats.hp);
|
||||||
|
properties.Level = user.stats.lvl;
|
||||||
|
properties.Mana = Math.floor(user.stats.mp);
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.balance = user.balance;
|
||||||
|
properties.balanceGemAmount = properties.balance * 4;
|
||||||
|
|
||||||
|
properties.tutorialComplete = user.flags && user.flags.tour && user.flags.tour.intro === -2;
|
||||||
|
if (user.habits && user.dailys && user.todos && user.rewards) {
|
||||||
|
properties['Number Of Tasks'] = {
|
||||||
|
habits: user.habits.length,
|
||||||
|
dailys: user.dailys.length,
|
||||||
|
todos: user.todos.length,
|
||||||
|
rewards: user.rewards.length,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (user.contributor && user.contributor.level) properties.contributorLevel = user.contributor.level;
|
||||||
|
if (user.purchased && user.purchased.plan.planId) properties.subscription = user.purchased.plan.planId;
|
||||||
|
},
|
||||||
|
doesNotHaveRequiredFields (properties) {
|
||||||
|
if (!isEqual(keys(pick(properties, REQUIRED_FIELDS)), REQUIRED_FIELDS)) {
|
||||||
|
// @TODO: Log with Winston?
|
||||||
|
// console.log('Analytics tracking calls must include the following properties: ' + JSON.stringify(REQUIRED_FIELDS));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
doesNotHaveAllowedHitType (properties) {
|
||||||
|
if (!includes(ALLOWED_HIT_TYPES, properties.hitType)) {
|
||||||
|
// @TODO: Log with Winston?
|
||||||
|
// console.log('Hit type of Analytics event must be one of the following: ' + JSON.stringify(ALLOWED_HIT_TYPES));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
124
website/client/mixins/notifications.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
export default {
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
Show '+ 5 {gold_coin} 3 {silver_coin}'
|
||||||
|
*/
|
||||||
|
coins (money) {
|
||||||
|
let absolute;
|
||||||
|
let gold;
|
||||||
|
let silver;
|
||||||
|
absolute = Math.abs(money);
|
||||||
|
gold = Math.floor(absolute);
|
||||||
|
silver = Math.floor((absolute - gold) * 100);
|
||||||
|
if (gold && silver > 0) {
|
||||||
|
return `${gold} <span class='notification-icon shop_gold'></span> ${silver} <span class='notification-icon shop_silver'></span>`;
|
||||||
|
} else if (gold > 0) {
|
||||||
|
return `${gold} <span class='notification-icon shop_gold'></span>`;
|
||||||
|
} else if (silver > 0) {
|
||||||
|
return `${silver} <span class='notification-icon shop_silver'></span>`;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
crit (val) {
|
||||||
|
let message = `${this.$t('critBonus')} ${Math.round(val)} %`;
|
||||||
|
this.notify(message, 'crit', 'glyphicon glyphicon-certificate');
|
||||||
|
},
|
||||||
|
drop (val, item) {
|
||||||
|
let dropClass = '';
|
||||||
|
if (item) {
|
||||||
|
switch (item.type) {
|
||||||
|
case 'Egg':
|
||||||
|
dropClass = `Pet_Egg_${item.key}`;
|
||||||
|
break;
|
||||||
|
case 'HatchingPotion':
|
||||||
|
dropClass = `Pet_HatchingPotion_${item.key}`;
|
||||||
|
break;
|
||||||
|
case 'Food':
|
||||||
|
dropClass = `Pet_Food_${item.key}`;
|
||||||
|
break;
|
||||||
|
case 'armor':
|
||||||
|
case 'back':
|
||||||
|
case 'body':
|
||||||
|
case 'eyewear':
|
||||||
|
case 'head':
|
||||||
|
case 'headAccessory':
|
||||||
|
case 'shield':
|
||||||
|
case 'weapon':
|
||||||
|
dropClass = `shop_${item.key}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
dropClass = 'glyphicon glyphicon-gift';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.notify(val, 'drop', dropClass);
|
||||||
|
},
|
||||||
|
quest (type, val) {
|
||||||
|
this.notify(this.$t(type, { val }), 'success');
|
||||||
|
},
|
||||||
|
exp (val) {
|
||||||
|
if (val < -50) return; // don't show when they level up (resetting their exp)
|
||||||
|
let message = `${this.sign(val)} ${this.round(val)} + ${this.$t('experience')}`;
|
||||||
|
this.notify(message, 'xp', 'glyphicon glyphicon-star');
|
||||||
|
},
|
||||||
|
error (error, canHide) {
|
||||||
|
this.notify(error, 'danger', 'glyphicon glyphicon-exclamation-sign', canHide);
|
||||||
|
},
|
||||||
|
gp (val, bonus) {
|
||||||
|
this.notify(`${this.sign(val)} ${this.coins(val - bonus)}`, 'gp');
|
||||||
|
},
|
||||||
|
hp (val) {
|
||||||
|
// don't show notifications if user dead
|
||||||
|
this.notify(`${this.sign(val)} ${this.round(val)} ${this.$t('health')}`, 'hp', 'glyphicon glyphicon-heart');
|
||||||
|
},
|
||||||
|
lvl () {
|
||||||
|
this.notify(this.$t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up');
|
||||||
|
},
|
||||||
|
markdown (val) {
|
||||||
|
if (!val) return;
|
||||||
|
// @TODO: Implement markdown library
|
||||||
|
// let parsed_markdown = $filter("markdown")(val);
|
||||||
|
// this.notify(parsed_markdown, 'info');
|
||||||
|
},
|
||||||
|
mp (val) {
|
||||||
|
this.notify(`${this.sign(val)} ${this.round(val)} ${this.$t('mana')}`, 'mp', 'glyphicon glyphicon-fire');
|
||||||
|
},
|
||||||
|
streak (val) {
|
||||||
|
this.notify(`${this.$t('streaks')}: ${val}`, 'streak', 'glyphicon glyphicon-repeat');
|
||||||
|
},
|
||||||
|
text (val, onClick) {
|
||||||
|
if (!val) return;
|
||||||
|
this.notify(val, 'info', null, null, onClick);
|
||||||
|
},
|
||||||
|
sign (number) {
|
||||||
|
let sign = '+';
|
||||||
|
if (number && number < 0) {
|
||||||
|
sign = '-';
|
||||||
|
}
|
||||||
|
return sign;
|
||||||
|
},
|
||||||
|
round (number) {
|
||||||
|
return Math.abs(number.toFixed(1));
|
||||||
|
},
|
||||||
|
// @TODO: Implement when we have a notify library
|
||||||
|
//notify (html, type, icon, canHide, onClick) {
|
||||||
|
// let stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60};
|
||||||
|
// let notice = $.pnotify({
|
||||||
|
// type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
|
||||||
|
// text: html,
|
||||||
|
// opacity: 1,
|
||||||
|
// addclass: 'alert-' + type,
|
||||||
|
// delay: 7000,
|
||||||
|
// hide: ((type == 'error' || type == 'danger') && !canHide) ? false : true,
|
||||||
|
// mouse_reset: false,
|
||||||
|
// width: "250px",
|
||||||
|
// stack: stack_topright,
|
||||||
|
// icon: icon || false
|
||||||
|
// }).click(function() {
|
||||||
|
// notice.pnotify_remove();
|
||||||
|
//
|
||||||
|
// if (onClick) {
|
||||||
|
// onClick();
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
//},
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -46,6 +46,11 @@ const PromoCode = () => import(/* webpackChunkName: "settings" */'./components/s
|
|||||||
const Site = () => import(/* webpackChunkName: "settings" */'./components/settings/site');
|
const Site = () => import(/* webpackChunkName: "settings" */'./components/settings/site');
|
||||||
const Subscription = () => import(/* webpackChunkName: "settings" */'./components/settings/subscription');
|
const Subscription = () => import(/* webpackChunkName: "settings" */'./components/settings/subscription');
|
||||||
|
|
||||||
|
// Hall
|
||||||
|
const HallPage = () => import(/* webpackChunkName: "hall" */'./components/hall/index');
|
||||||
|
const PatronsPage = () => import(/* webpackChunkName: "hall" */'./components/hall/patrons');
|
||||||
|
const HeroesPage = () => import(/* webpackChunkName: "hall" */'./components/hall/heroes');
|
||||||
|
|
||||||
// Except for tasks that are always loaded all the other main level
|
// Except for tasks that are always loaded all the other main level
|
||||||
// All the main level
|
// All the main level
|
||||||
// components are loaded in separate webpack chunks.
|
// components are loaded in separate webpack chunks.
|
||||||
@@ -66,11 +71,12 @@ const InboxPage = () => import(/* webpackChunkName: "inbox" */ './components/soc
|
|||||||
const InboxConversationPage = () => import(/* webpackChunkName: "inbox" */ './components/social/inbox/conversationPage');
|
const InboxConversationPage = () => import(/* webpackChunkName: "inbox" */ './components/social/inbox/conversationPage');
|
||||||
|
|
||||||
// Guilds
|
// Guilds
|
||||||
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/guilds/index');
|
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/groups/index');
|
||||||
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
|
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/tavern');
|
||||||
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/guilds/myGuilds');
|
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/groups/myGuilds');
|
||||||
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/discovery');
|
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/discovery');
|
||||||
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/guild');
|
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/guild');
|
||||||
|
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/groupPlan');
|
||||||
|
|
||||||
// Challenges
|
// Challenges
|
||||||
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
|
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
|
||||||
@@ -107,8 +113,9 @@ const router = new VueRouter({
|
|||||||
},
|
},
|
||||||
{ name: 'shops', path: '/shops', component: Page },
|
{ name: 'shops', path: '/shops', component: Page },
|
||||||
{ name: 'party', path: '/party', component: GuildPage },
|
{ name: 'party', path: '/party', component: GuildPage },
|
||||||
|
{ name: 'groupPlan', path: '/group-plans', component: GroupPlansAppPage },
|
||||||
{
|
{
|
||||||
path: '/guilds',
|
path: '/groups',
|
||||||
component: GuildIndex,
|
component: GuildIndex,
|
||||||
children: [
|
children: [
|
||||||
{ name: 'tavern', path: 'tavern', component: TavernPage },
|
{ name: 'tavern', path: 'tavern', component: TavernPage },
|
||||||
@@ -241,6 +248,14 @@ const router = new VueRouter({
|
|||||||
{ name: 'videos', path: 'videos', component: VideosPage },
|
{ name: 'videos', path: 'videos', component: VideosPage },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/hall',
|
||||||
|
component: HallPage,
|
||||||
|
children: [
|
||||||
|
{ name: 'patrons', path: 'patrons', component: PatronsPage },
|
||||||
|
{ name: 'contributors', path: 'contributors', component: HeroesPage },
|
||||||
|
],
|
||||||
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
30
website/client/store/actions/hall.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export async function getHeroes () {
|
||||||
|
let url = '/api/v3/hall/heroes';
|
||||||
|
let response = await axios.get(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getHero (store, payload) {
|
||||||
|
let url = `/api/v3/hall/heroes/${payload.uuid}`;
|
||||||
|
let response = await axios.get(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateHero (store, payload) {
|
||||||
|
let url = `/api/v3/hall/heroes/${payload.heroDetails._id}`;
|
||||||
|
let response = await axios.put(url, {
|
||||||
|
heroDetails: payload.heroDetails,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPatrons (store, payload) {
|
||||||
|
let page = 0;
|
||||||
|
if (payload.page) page = payload.page;
|
||||||
|
|
||||||
|
let url = `/api/v3/hall/patrons/?page=${page}`;
|
||||||
|
let response = await axios.get(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
@@ -10,6 +10,9 @@ import * as auth from './auth';
|
|||||||
import * as quests from './quests';
|
import * as quests from './quests';
|
||||||
import * as challenges from './challenges';
|
import * as challenges from './challenges';
|
||||||
import * as chat from './chat';
|
import * as chat from './chat';
|
||||||
|
import * as notifications from './notifications';
|
||||||
|
import * as tags from './tags';
|
||||||
|
import * as hall from './hall';
|
||||||
|
|
||||||
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
|
// Actions should be named as 'actionName' and can be accessed as 'namespace:actionName'
|
||||||
// Example: fetch in user.js -> 'user:fetch'
|
// Example: fetch in user.js -> 'user:fetch'
|
||||||
@@ -25,6 +28,9 @@ const actions = flattenAndNamespace({
|
|||||||
quests,
|
quests,
|
||||||
challenges,
|
challenges,
|
||||||
chat,
|
chat,
|
||||||
|
notifications,
|
||||||
|
tags,
|
||||||
|
hall,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default actions;
|
export default actions;
|
||||||
|
|||||||
15
website/client/store/actions/notifications.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export async function readNotification (store, payload) {
|
||||||
|
let url = `api/v3/notifications/${payload.notificationId}/read`;
|
||||||
|
let response = await axios.post(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function readNotifications (store, payload) {
|
||||||
|
let url = 'api/v3/notifications/read';
|
||||||
|
let response = await axios.post(url, {
|
||||||
|
notificationIds: payload.notificationIds,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
44
website/client/store/actions/tags.js
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export async function getTags () {
|
||||||
|
let url = 'api/v3/tags';
|
||||||
|
let response = await axios.get(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function createTag (store, payload) {
|
||||||
|
let url = 'api/v3/tags';
|
||||||
|
let response = await axios.post(url, {
|
||||||
|
tagDetails: payload.tagDetails,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getTag (store, payload) {
|
||||||
|
let url = `api/v3/tags/${payload.tagId}`;
|
||||||
|
let response = await axios.get(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function updateTag (store, payload) {
|
||||||
|
let url = `api/v3/tags/${payload.tagId}`;
|
||||||
|
let response = await axios.put(url, {
|
||||||
|
tagDetails: payload.tagDetails,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function sortTag (store, payload) {
|
||||||
|
let url = 'api/v3/reorder-tags';
|
||||||
|
let response = await axios.post(url, {
|
||||||
|
tagDetails: payload.tagDetails,
|
||||||
|
to: payload.to,
|
||||||
|
});
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteTag (store, payload) {
|
||||||
|
let url = `api/v3/tags/${payload.tagId}`;
|
||||||
|
let response = await axios.delete(url);
|
||||||
|
return response.data.data;
|
||||||
|
}
|
||||||
@@ -212,5 +212,6 @@
|
|||||||
"markdownFormattingHelp": "Markdown formatting help",
|
"markdownFormattingHelp": "Markdown formatting help",
|
||||||
"challengeInformationPlaceHolder": "Write a short description advertising your Challenge to other Habiticans. What is the main purpose of your Challenge and why should people join it? Try to include useful keywords in the description so that Habiticans can easily find it when they search!",
|
"challengeInformationPlaceHolder": "Write a short description advertising your Challenge to other Habiticans. What is the main purpose of your Challenge and why should people join it? Try to include useful keywords in the description so that Habiticans can easily find it when they search!",
|
||||||
"where": "Where*",
|
"where": "Where*",
|
||||||
"challengeMinimum": "Minimum 1 Gem for public Challenges (helps prevent spam, it really does)."
|
"challengeMinimum": "Minimum 1 Gem for public Challenges (helps prevent spam, it really does).",
|
||||||
|
"group": "Group"
|
||||||
}
|
}
|
||||||
|
|||||||