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
This commit is contained in:
Keith Holliday
2017-07-25 08:24:40 -06:00
committed by GitHub
parent 86a07a4949
commit 16b244d5c6
49 changed files with 2126 additions and 54 deletions

View File

@@ -5,15 +5,19 @@
#loading-screen.h-100.w-100.d-flex.justify-content-center.align-items-center(v-if="!isUserLoaded")
p Loading...
template(v-else)
notifications
app-menu
.container-fluid
app-header
router-view
app-footer
</template>
<script>
import AppMenu from './components/appMenu';
import AppHeader from './components/appHeader';
import AppFooter from './components/appFooter';
import notifications from './components/notifications';
import { mapState } from 'client/libs/store';
export default {
@@ -21,6 +25,8 @@ export default {
components: {
AppMenu,
AppHeader,
AppFooter,
notifications,
},
data () {
return {

View File

@@ -1,3 +1,3 @@
<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>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View 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
| Were 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>

View File

@@ -24,59 +24,59 @@
</template>
<style lang="scss" scoped>
@import '~client/assets/scss/colors.scss';
@import '~client/assets/scss/colors.scss';
#app-header {
padding-left: 14px;
margin-top: 56px;
background: $purple-50;
height: 204px;
color: $header-color;
flex-wrap: nowrap;
position: relative;
}
.no-party, .party-members {
flex-grow: 1;
}
.party-members {
}
.view-party {
position: absolute;
z-index: 10;
right: 0;
padding-right: 40px;
height: 100%;
background-image: linear-gradient(to right, rgba($purple-50, 0), $purple-50);
.btn {
margin-top: 75%;
}
}
.no-party {
.small-text {
#app-header {
padding-left: 14px;
margin-top: 56px;
background: $purple-50;
height: 204px;
color: $header-color;
flex-wrap: nowrap;
position: relative;
}
h3 {
color: $white;
margin-bottom: 4px;
.no-party, .party-members {
flex-grow: 1;
}
.btn {
margin-top: 16px;
.party-members {
}
.view-party {
position: absolute;
z-index: 10;
right: 0;
padding-right: 40px;
height: 100%;
background-image: linear-gradient(to right, rgba($purple-50, 0), $purple-50);
.btn {
margin-top: 75%;
}
}
.no-party {
.small-text {
color: $header-color;
flex-wrap: nowrap;
}
h3 {
color: $white;
margin-bottom: 4px;
}
.btn {
margin-top: 16px;
}
}
}
</style>
<script>
import { mapGetters, mapActions } from 'client/libs/store';
import MemberDetails from './memberDetails';
import createPartyModal from './guilds/createPartyModal';
import createPartyModal from './groups/createPartyModal';
export default {
components: {

View File

@@ -24,6 +24,8 @@ div
router-link.dropdown-item(:to="{name: 'tavern'}") {{ $t('tavern') }}
router-link.dropdown-item(:to="{name: 'myGuilds'}") {{ $t('myGuilds') }}
router-link.dropdown-item(:to="{name: 'guildsDiscovery'}") {{ $t('guildsDiscovery') }}
router-link.nav-item.dropdown(tag="li", :to="{name: 'groupPlan'}", :class="{'active': $route.path.startsWith('/group-plan')}")
a.nav-link(v-once) {{ $t('group') }}
router-link.nav-item(tag="li", :to="{name: 'myChallenges'}", exact)
a.nav-link(v-once) {{ $t('challenges') }}
router-link.nav-item.dropdown(tag="li", to="/help", :class="{'active': $route.path.startsWith('/help')}")
@@ -42,8 +44,7 @@ div
.item-with-icon
.svg-icon(v-html="icons.gold")
span {{user.stats.gp | roundBigNumber}}
.item-with-icon.item-notifications
.svg-icon(v-html="icons.notifications")
notification-menu
router-link.dropdown.item-with-icon.item-user(:to="{name: 'avatar'}")
.svg-icon(v-html="icons.user")
.dropdown-menu.dropdown-menu-right.user-dropdown
@@ -203,21 +204,21 @@ div
import { mapState, mapGetters } from 'client/libs/store';
import gemIcon from 'assets/svg/gem.svg';
import goldIcon from 'assets/svg/gold.svg';
import notificationsIcon from 'assets/svg/notifications.svg';
import userIcon from 'assets/svg/user.svg';
import logo from 'assets/svg/logo.svg';
import InboxModal from './userMenu/inbox.vue';
import notificationMenu from './notificationMenu';
export default {
components: {
InboxModal,
notificationMenu,
},
data () {
return {
icons: Object.freeze({
gem: gemIcon,
gold: goldIcon,
notifications: notificationsIcon,
user: userIcon,
logo,
}),

View 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>

View 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>

View File

@@ -1,6 +1,7 @@
<template lang="pug">
.row(v-if="group")
group-form-modal
invite-modal
.clearfix.col-8
.row
.col-6.title-details
@@ -40,7 +41,7 @@
.button-container
button.btn.btn-success(class='btn-success', v-if='!isMember') {{ $t('join') }}
.button-container
button.btn.btn-primary(v-once) {{$t('invite')}}
button.btn.btn-primary(v-once, @click='showInviteModal()') {{$t('invite')}}
.button-container
button.btn.btn-primary(v-once, v-if='!isLeader') {{$t('messageGuildLeader')}}
.button-container
@@ -355,6 +356,8 @@ import ownedQuestsModal from './ownedQuestsModal';
import quests from 'common/script/content/quests';
import percent from 'common/script/libs/percent';
import groupFormModal from './groupFormModal';
import inviteModal from './inviteModal';
import memberModal from '../members/memberModal';
import chatMessage from '../chat/chatMessages';
import bCollapse from 'bootstrap-vue/lib/components/collapse';
@@ -380,12 +383,14 @@ export default {
props: ['groupId'],
components: {
membersModal,
memberModal,
ownedQuestsModal,
bCollapse,
bCard,
bTooltip,
groupFormModal,
chatMessage,
inviteModal,
},
directives: {
bToggle,
@@ -507,6 +512,9 @@ export default {
this.$store.state.editingGroup = this.group;
this.$root.$emit('show::modal', 'guild-form');
},
showInviteModal () {
this.$root.$emit('show::modal', 'invite-modal');
},
async fetchGuild () {
let group = await this.$store.dispatch('guilds:getGroup', {groupId: this.groupId});
if (this.isParty) {

View 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>

View File

@@ -10,6 +10,7 @@
h3(v-once) {{ $t('welcomeToTavern') }}
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') }}
.container.community-guidelines(v-if='communityGuidelinesAccepted')
@@ -282,6 +283,7 @@ import { mapState } from 'client/libs/store';
import { TAVERN_ID } from '../../../common/script/constants';
import chatMessage from '../chat/chatMessages';
import autocomplete from '../chat/autoComplete';
import gemIcon from 'assets/svg/gem.svg';
import questIcon from 'assets/svg/quest.svg';
@@ -294,6 +296,7 @@ import downIcon from 'assets/svg/down.svg';
export default {
components: {
chatMessage,
autocomplete,
},
data () {
return {
@@ -399,6 +402,9 @@ export default {
this.group = await this.$store.dispatch('guilds:getGroup', {groupId: TAVERN_ID});
},
methods: {
selectedAutocomplete (newText) {
this.newMessage = newText;
},
aggreeToGuideLines () {
// @TODO:
},

View 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') }}
|&nbsp;
a(target='_blank', href='https://trello.com/c/wkFzONhE/277-contributor-gear') {{ $t('moreDetails') }}
|,&nbsp;
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}}&nbsp;
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>

View 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>

View 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>

View 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')
|&nbsp;
| {{ $t('memberSince') }}
|&nbsp;
| {{profile.auth.timestamps.created | date:user.preferences.dateFormat}} -
li(v-if='profile.auth.timestamps.loggedin')
|&nbsp;
| {{ $t('lastLoggedIn') }}
|&nbsp;
| {{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>

View 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>

View 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>

View File

@@ -14,6 +14,44 @@
src='https://static-na.payments-amazon.com/OffAmazonPayments/us/sandbox/js/Widgets.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>
</html>

View File

@@ -9,6 +9,7 @@ import getStore from './store';
import StoreModule from './libs/store';
import './filters/registerGlobals';
import i18n from './libs/i18n';
import axios from 'axios';
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
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(StoreModule);

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 394 KiB

View 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;
}
},
},
};

View 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();
// }
// });
//},
},
};

View File

@@ -46,6 +46,11 @@ const PromoCode = () => import(/* webpackChunkName: "settings" */'./components/s
const Site = () => import(/* webpackChunkName: "settings" */'./components/settings/site');
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
// All the main level
// 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');
// Guilds
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/guilds/index');
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/tavern');
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/guilds/myGuilds');
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/discovery');
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/guilds/guild');
const GuildIndex = () => import(/* webpackChunkName: "guilds" */ './components/groups/index');
const TavernPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/tavern');
const MyGuilds = () => import(/* webpackChunkName: "guilds" */ './components/groups/myGuilds');
const GuildsDiscoveryPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/discovery');
const GuildPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/guild');
const GroupPlansAppPage = () => import(/* webpackChunkName: "guilds" */ './components/groups/groupPlan');
// Challenges
const ChallengeIndex = () => import(/* webpackChunkName: "challenges" */ './components/challenges/index');
@@ -107,8 +113,9 @@ const router = new VueRouter({
},
{ name: 'shops', path: '/shops', component: Page },
{ name: 'party', path: '/party', component: GuildPage },
{ name: 'groupPlan', path: '/group-plans', component: GroupPlansAppPage },
{
path: '/guilds',
path: '/groups',
component: GuildIndex,
children: [
{ name: 'tavern', path: 'tavern', component: TavernPage },
@@ -241,6 +248,14 @@ const router = new VueRouter({
{ name: 'videos', path: 'videos', component: VideosPage },
],
},
{
path: '/hall',
component: HallPage,
children: [
{ name: 'patrons', path: 'patrons', component: PatronsPage },
{ name: 'contributors', path: 'contributors', component: HeroesPage },
],
},
],
});

View 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;
}

View File

@@ -10,6 +10,9 @@ import * as auth from './auth';
import * as quests from './quests';
import * as challenges from './challenges';
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'
// Example: fetch in user.js -> 'user:fetch'
@@ -25,6 +28,9 @@ const actions = flattenAndNamespace({
quests,
challenges,
chat,
notifications,
tags,
hall,
});
export default actions;

View 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;
}

View 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;
}

View File

@@ -212,5 +212,6 @@
"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!",
"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"
}