mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
* log armoire, quoest response and cron events to history * show user history in admin panel * allow stats to be edited from admin panel * Improve admin panel stats input * improve setting client in history * fix tests * fix lint * fix armoire buying issue * Improve hero saving * Formatting fix * Improve user history logging * allow class to be changed from admin panel * make terminating subscriptions easier * support decimal extraMonths * Fix editing some achievements in admin panel * log if a user invites party to quest * Log more quest events into user history * make userhistory length configurable * fix some numbered achievements * fix extraMonths field * Automatically set up group plan subs with admin panel * show party info nicer in admin panel * improve admin panel sub handling * add missing brace * display when there are unsaved changes * fix setting group plan * fix showing group id * Display group plan info in admin panel * fix setting hourglass promo date * Improve termination handling in admin panel * reload data after certain save events in admin panel * remove console * fix plan.extraMonths not being reset if terminating a sub * add more options when cancelling subs * reload data after group plan change * Add a way to remove users from a party * fix issue with removing user from party * pass party id correctly * correctly call async function * Improve sub display in admin panel * fix line length * fix line * shorter * plaid * fix(lint): vue code style --------- Co-authored-by: Kalista Payne <sabrecat@gmail.com>
277 lines
7.5 KiB
Vue
277 lines
7.5 KiB
Vue
<template>
|
|
<div v-if="hasPermission(user, 'userSupport')">
|
|
<div
|
|
v-if="hero && hero.profile"
|
|
class="row"
|
|
>
|
|
<div class="form col-12">
|
|
<basic-details
|
|
:user-id="hero._id"
|
|
:auth="hero.auth"
|
|
:preferences="hero.preferences"
|
|
:profile="hero.profile"
|
|
/>
|
|
|
|
<privileges-and-gems
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
:has-unsaved-changes="hasUnsavedChanges([hero.flags, unModifiedHero.flags],
|
|
[hero.auth, unModifiedHero.auth],
|
|
[hero.balance, unModifiedHero.balance],
|
|
[hero.secret, unModifiedHero.secret])"
|
|
/>
|
|
|
|
<subscription-and-perks
|
|
:hero="hero"
|
|
:group-plans="groupPlans"
|
|
:has-unsaved-changes="hasUnsavedChanges([hero.purchased.plan,
|
|
unModifiedHero.purchased.plan])"
|
|
/>
|
|
|
|
<cron-and-auth
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<user-profile
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
:has-unsaved-changes="hasUnsavedChanges([hero.profile, unModifiedHero.profile])"
|
|
/>
|
|
|
|
<party-and-quest
|
|
v-if="adminHasPrivForParty"
|
|
:user-id="hero._id"
|
|
:username="hero.auth.local.username"
|
|
:user-has-party="hasParty"
|
|
:party-not-exist-error="partyNotExistError"
|
|
:user-party-data="hero.party"
|
|
:group-party-data="party"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<avatar-and-drops
|
|
:items="hero.items"
|
|
:preferences="hero.preferences"
|
|
/>
|
|
|
|
<stats
|
|
:hero="hero"
|
|
:has-unsaved-changes="hasUnsavedChanges([hero.stats, unModifiedHero.stats])"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<items-owned
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<customizations-owned
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<achievements
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<transactions
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<user-history
|
|
:hero="hero"
|
|
:reset-counter="resetCounter"
|
|
/>
|
|
|
|
<contributor-details
|
|
:hero="hero"
|
|
:hasUnsavedChanges="hasUnsavedChanges(
|
|
[hero.contributor, unModifiedHero.contributor],
|
|
[hero.permissions, unModifiedHero.permissions],
|
|
[hero.secret, unModifiedHero.secret],
|
|
)"
|
|
:reset-counter="resetCounter"
|
|
@clear-data="clearData"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="scss" scoped>
|
|
::v-deep .accordion-group .accordion-group {
|
|
margin-left: 1em;
|
|
}
|
|
::v-deep h3 {
|
|
margin-top: 2em;
|
|
}
|
|
::v-deep h4 {
|
|
margin-top: 1em;
|
|
}
|
|
::v-deep .expand-toggle::after {
|
|
margin-left: 5px;
|
|
}
|
|
::v-deep .subsection-start {
|
|
margin-top: 1em;
|
|
}
|
|
::v-deep .form-inline {
|
|
margin-bottom: 1em;
|
|
input, span {
|
|
margin-left: 5px;
|
|
}
|
|
}
|
|
::v-deep .errorMessage {
|
|
font-weight: bold;
|
|
}
|
|
::v-deep .markdownPreview {
|
|
margin-left: 3em;
|
|
margin-top: 1em;
|
|
}
|
|
</style>
|
|
|
|
<script>
|
|
import isEqualWith from 'lodash/isEqualWith';
|
|
import BasicDetails from './basicDetails';
|
|
import ItemsOwned from './itemsOwned';
|
|
import CronAndAuth from './cronAndAuth';
|
|
import UserProfile from './userProfile';
|
|
import PartyAndQuest from './partyAndQuest';
|
|
import AvatarAndDrops from './avatarAndDrops';
|
|
import PrivilegesAndGems from './privilegesAndGems';
|
|
import ContributorDetails from './contributorDetails';
|
|
import Transactions from './transactions';
|
|
import SubscriptionAndPerks from './subscriptionAndPerks';
|
|
import CustomizationsOwned from './customizationsOwned.vue';
|
|
import Achievements from './achievements.vue';
|
|
import UserHistory from './userHistory.vue';
|
|
import Stats from './stats.vue';
|
|
|
|
import { userStateMixin } from '../../../mixins/userState';
|
|
|
|
export default {
|
|
components: {
|
|
BasicDetails,
|
|
ItemsOwned,
|
|
CustomizationsOwned,
|
|
CronAndAuth,
|
|
PartyAndQuest,
|
|
AvatarAndDrops,
|
|
PrivilegesAndGems,
|
|
ContributorDetails,
|
|
Transactions,
|
|
UserHistory,
|
|
Stats,
|
|
SubscriptionAndPerks,
|
|
UserProfile,
|
|
Achievements,
|
|
},
|
|
mixins: [userStateMixin],
|
|
beforeRouteUpdate (to, from, next) {
|
|
this.userIdentifier = to.params.userIdentifier;
|
|
next();
|
|
},
|
|
data () {
|
|
return {
|
|
userIdentifier: '',
|
|
resetCounter: 0,
|
|
unModifiedHero: {},
|
|
hero: {},
|
|
party: {},
|
|
groupPlans: [],
|
|
hasParty: false,
|
|
partyNotExistError: false,
|
|
adminHasPrivForParty: true,
|
|
};
|
|
},
|
|
watch: {
|
|
userIdentifier () {
|
|
// close modal if the page is opened in an existing tab from the modal
|
|
this.$root.$emit('bv::hide::modal', 'profile');
|
|
|
|
this.loadHero(this.userIdentifier);
|
|
},
|
|
},
|
|
mounted () {
|
|
this.userIdentifier = this.$route.params.userIdentifier;
|
|
},
|
|
methods: {
|
|
clearData () {
|
|
this.unModifiedHero = {};
|
|
this.hero = {};
|
|
},
|
|
|
|
async loadHero (userIdentifier) {
|
|
const id = userIdentifier.replace(/@/, ''); // allow "@name" to be entered
|
|
this.$emit('changeUserIdentifier', id); // change user identifier in Admin Panel's form
|
|
|
|
this.hero = await this.$store.dispatch('hall:getHero', { uuid: id });
|
|
this.unModifiedHero = JSON.parse(JSON.stringify(this.hero));
|
|
|
|
if (!this.hero.flags) {
|
|
this.hero.flags = {
|
|
chatRevoked: false,
|
|
chatShadowMuted: false,
|
|
};
|
|
}
|
|
|
|
if (!this.hero.permissions) {
|
|
this.hero.permissions = {};
|
|
}
|
|
|
|
this.hasParty = false;
|
|
this.partyNotExistError = false;
|
|
this.adminHasPrivForParty = true;
|
|
if (this.hero.party && this.hero.party._id) {
|
|
try {
|
|
this.party = await this.$store.dispatch('hall:getHeroParty', { groupId: this.hero.party._id });
|
|
this.hasParty = true;
|
|
} catch (e) {
|
|
if (e.message.includes('status code 401')) {
|
|
// @TODO is there a better way to recognise NotAuthorized error?
|
|
this.adminHasPrivForParty = false;
|
|
} else {
|
|
// the API's error message isn't worth reporting ("Request failed with status code 404")
|
|
this.partyNotExistError = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.hero.purchased.plan.planId === 'group_plan_auto') {
|
|
try {
|
|
this.groupPlans = await this.$store.dispatch('hall:getHeroGroupPlans', { heroId: this.hero._id });
|
|
} catch (e) {
|
|
this.groupPlans = [];
|
|
}
|
|
}
|
|
|
|
this.resetCounter += 1; // tell child components to reinstantiate from scratch
|
|
},
|
|
hasUnsavedChanges (...comparisons) {
|
|
for (const index in comparisons) {
|
|
if (index && comparisons[index]) {
|
|
const objs = comparisons[index];
|
|
const obj1 = objs[0];
|
|
const obj2 = objs[1];
|
|
if (!isEqualWith(obj1, obj2, (x, y) => {
|
|
if (typeof x === 'object' && typeof y === 'object') {
|
|
return undefined;
|
|
}
|
|
if (x === false && y === undefined) {
|
|
// Special case for checkboxes
|
|
return true;
|
|
}
|
|
return x == y; // eslint-disable-line eqeqeq
|
|
})) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
},
|
|
};
|
|
</script>
|