userEmailSetting
This commit is contained in:
@@ -123,7 +123,7 @@
|
||||
|
||||
.input-area {
|
||||
width: 320px;
|
||||
margin: 0 auto;
|
||||
margin: 1rem auto 0;
|
||||
}
|
||||
|
||||
.edit-link {
|
||||
|
||||
@@ -15,409 +15,12 @@
|
||||
|
||||
<table class="table">
|
||||
<user-name-setting />
|
||||
<user-name-setting />
|
||||
<user-email-setting />
|
||||
<tr>
|
||||
<td colspan="3"></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="form-horizontal">
|
||||
<h5>{{ $t('language') }}</h5>
|
||||
<select
|
||||
class="form-control"
|
||||
:value="user.preferences.language"
|
||||
@change="changeLanguage($event)"
|
||||
>
|
||||
<option
|
||||
v-for="lang in availableLanguages"
|
||||
:key="lang.code"
|
||||
:value="lang.code"
|
||||
>
|
||||
{{ lang.name }}
|
||||
</option>
|
||||
</select>
|
||||
<small>
|
||||
{{ $t('americanEnglishGovern') }}
|
||||
<br>
|
||||
<strong v-html="$t('helpWithTranslation')"></strong>
|
||||
</small>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<h5>{{ $t('dateFormat') }}</h5>
|
||||
<select
|
||||
v-model="user.preferences.dateFormat"
|
||||
class="form-control"
|
||||
@change="set('dateFormat')"
|
||||
>
|
||||
<option
|
||||
v-for="dateFormat in availableFormats"
|
||||
:key="dateFormat"
|
||||
:value="dateFormat"
|
||||
>
|
||||
{{ dateFormat }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="form-horizontal">
|
||||
<div class="form-group">
|
||||
<h5>{{ $t('audioTheme') }}</h5>
|
||||
<select
|
||||
v-model="user.preferences.sound"
|
||||
class="form-control"
|
||||
@change="changeAudioTheme"
|
||||
>
|
||||
<option
|
||||
v-for="sound in availableAudioThemes"
|
||||
:key="sound"
|
||||
:value="sound"
|
||||
>
|
||||
{{ $t(`audioTheme_${sound}`) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-primary btn-xs"
|
||||
@click="playAudio"
|
||||
>
|
||||
{{ $t('demo') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
<div
|
||||
v-if="hasClass"
|
||||
class="form-horizontal"
|
||||
>
|
||||
<h5>{{ $t('characterBuild') }}</h5>
|
||||
<h6 v-once>
|
||||
{{ $t('class') + ': ' }}
|
||||
<!-- @TODO: what is classText-->
|
||||
<!-- span(v-if='classText') {{ classText }} -->
|
||||
<button
|
||||
v-once
|
||||
class="btn btn-danger btn-xs"
|
||||
@click="changeClassForUser(true)"
|
||||
>
|
||||
{{ $t('changeClass') }}
|
||||
</button>
|
||||
<small class="cost">
|
||||
3 {{ $t('gems') }}
|
||||
<!-- @TODO add icon span.Pet_Currency_Gem1x.inline-gems-->
|
||||
</small>
|
||||
</h6>
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.advancedCollapsed"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('advancedCollapsed')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('startAdvCollapsedPop')"
|
||||
>{{ $t('startAdvCollapsed') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.dailyDueDefaultView"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('dailyDueDefaultView')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('dailyDueDefaultViewPop')"
|
||||
>{{ $t('dailyDueDefaultView') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
<div
|
||||
v-if="party.memberCount === 1"
|
||||
class="checkbox"
|
||||
>
|
||||
<label>
|
||||
<input
|
||||
v-model="user.preferences.displayInviteToPartyWhenPartyIs1"
|
||||
type="checkbox"
|
||||
class="mr-2"
|
||||
@change="set('displayInviteToPartyWhenPartyIs1')"
|
||||
>
|
||||
<span
|
||||
class="hint"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('displayInviteToPartyWhenPartyIs1')"
|
||||
>{{ $t('displayInviteToPartyWhenPartyIs1') }}</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="btn btn-primary mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('fixValPop')"
|
||||
@click="openRestoreModal()"
|
||||
>
|
||||
{{ $t('fixVal') }}
|
||||
</button>
|
||||
<button
|
||||
v-if="user.preferences.disableClasses == true"
|
||||
class="btn btn-primary mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
:popover="$t('enableClassPop')"
|
||||
@click="changeClassForUser(false)"
|
||||
>
|
||||
{{ $t('enableClass') }}
|
||||
</button>
|
||||
<hr>
|
||||
<day-start-adjustment />
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<h2>{{ $t('registration') }}</h2>
|
||||
<div class="panel-body">
|
||||
<div>
|
||||
<ul class="list-inline">
|
||||
<li
|
||||
v-for="network in SOCIAL_AUTH_NETWORKS"
|
||||
:key="network.key"
|
||||
>
|
||||
<button
|
||||
v-if="!user.auth[network.key].id && network.key !== 'facebook'"
|
||||
class="btn btn-primary mb-2"
|
||||
@click="socialAuth(network.key, user)"
|
||||
>
|
||||
{{ $t('registerWithSocial', {network: network.name}) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="!hasBackupAuthOption(network.key) && user.auth[network.key].id"
|
||||
class="btn btn-primary mb-2"
|
||||
disabled="disabled"
|
||||
>
|
||||
{{ $t('registeredWithSocial', {network: network.name}) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="hasBackupAuthOption(network.key) && user.auth[network.key].id"
|
||||
class="btn btn-danger"
|
||||
@click="deleteSocialAuth(network)"
|
||||
>
|
||||
{{ $t('detachSocial', {network: network.name}) }}
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<hr>
|
||||
<div v-if="!user.auth.local.has_password">
|
||||
<h5 v-if="!user.auth.local.email">
|
||||
{{ $t('addLocalAuth') }}
|
||||
</h5>
|
||||
<h5 v-if="user.auth.local.email">
|
||||
{{ $t('addPasswordAuth') }}
|
||||
</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="localAuth"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div
|
||||
v-if="!user.auth.local.email"
|
||||
class="form-group"
|
||||
>
|
||||
<input
|
||||
v-model="localAuth.email"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('email')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="localAuth.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('password')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="localAuth.confirmPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('confirmPass')"
|
||||
required="required"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="addLocalAuth()"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="usersettings">
|
||||
<h5>{{ $t('changeDisplayName') }}</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="changeDisplayName"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeDisplayname"
|
||||
v-model="temporaryDisplayName"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newDisplayName')"
|
||||
:class="{'is-invalid input-invalid': displayNameInvalid}"
|
||||
>
|
||||
<div
|
||||
v-if="displayNameIssues.length > 0"
|
||||
class="mb-3"
|
||||
>
|
||||
<div
|
||||
v-for="issue in displayNameIssues"
|
||||
:key="issue"
|
||||
class="input-error"
|
||||
>
|
||||
{{ issue }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
:disabled="displayNameCannotSubmit"
|
||||
@click="changeDisplayName(temporaryDisplayName)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<h5>{{ $t('changeUsername') }}</h5>
|
||||
<div
|
||||
class="form"
|
||||
name="changeUsername"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
</div>
|
||||
<h5 v-if="user.auth.local.has_password">
|
||||
{{ $t('changeEmail') }}
|
||||
</h5>
|
||||
<div
|
||||
v-if="user.auth.local.email"
|
||||
class="form"
|
||||
name="changeEmail"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changeEmail"
|
||||
v-model="emailUpdates.newEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newEmail')"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
v-if="user.auth.local.has_password"
|
||||
class="form-group"
|
||||
>
|
||||
<input
|
||||
v-model="emailUpdates.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('password')"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="changeUser('email', emailUpdates)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<h5 v-if="user.auth.local.has_password">
|
||||
{{ $t('changePass') }}
|
||||
</h5>
|
||||
<div
|
||||
v-if="user.auth.local.has_password"
|
||||
class="form"
|
||||
name="changePassword"
|
||||
novalidate="novalidate"
|
||||
>
|
||||
<div class="form-group">
|
||||
<input
|
||||
id="changePassword"
|
||||
v-model="passwordUpdates.password"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('oldPass')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordUpdates.newPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('newPass')"
|
||||
>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input
|
||||
v-model="passwordUpdates.confirmPassword"
|
||||
class="form-control"
|
||||
type="password"
|
||||
:placeholder="$t('confirmPass')"
|
||||
>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
type="submit"
|
||||
@click="changeUser('password', passwordUpdates)"
|
||||
>
|
||||
{{ $t('submit') }}
|
||||
</button>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div>
|
||||
<h5>{{ $t('dangerZone') }}</h5>
|
||||
<div>
|
||||
<button
|
||||
v-b-popover.hover.auto="$t('resetAccPop')"
|
||||
class="btn btn-danger mr-2 mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
popover-placement="right"
|
||||
@click="openResetModal()"
|
||||
>
|
||||
{{ $t('resetAccount') }}
|
||||
</button>
|
||||
<button
|
||||
v-b-popover.hover.auto="$t('deleteAccPop')"
|
||||
class="btn btn-danger mb-2"
|
||||
popover-trigger="mouseenter"
|
||||
@click="openDeleteModal()"
|
||||
>
|
||||
{{ $t('deleteAccount') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -446,246 +49,15 @@
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import hello from 'hellojs';
|
||||
import axios from 'axios';
|
||||
import debounce from 'lodash/debounce';
|
||||
import { mapState } from '@/libs/store';
|
||||
import dayStartAdjustment from '../../components/settings/dayStartAdjustment';
|
||||
import { SUPPORTED_SOCIAL_NETWORKS } from '../../../../common/script/constants';
|
||||
import changeClass from '../../../../common/script/ops/changeClass';
|
||||
import notificationsMixin from '../../mixins/notifications';
|
||||
import sounds from '../../libs/sounds';
|
||||
import { buildAppleAuthUrl } from '../../libs/auth';
|
||||
import UserNameSetting from '@/pages/settings/inlineSettings/userNameSetting';
|
||||
|
||||
// @TODO: this needs our window.env fix
|
||||
// import { availableLanguages } from '../../../server/libs/i18n';
|
||||
import UserEmailSetting from '@/pages/settings/inlineSettings/userEmailSetting';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserEmailSetting,
|
||||
UserNameSetting,
|
||||
dayStartAdjustment,
|
||||
},
|
||||
mixins: [notificationsMixin],
|
||||
data () {
|
||||
return {
|
||||
SOCIAL_AUTH_NETWORKS: [],
|
||||
party: {},
|
||||
// Made available by the server as a script
|
||||
availableFormats: ['MM/dd/yyyy', 'dd/MM/yyyy', 'yyyy/MM/dd'],
|
||||
temporaryDisplayName: '',
|
||||
emailUpdates: {},
|
||||
passwordUpdates: {},
|
||||
localAuth: {
|
||||
username: '',
|
||||
email: '',
|
||||
password: '',
|
||||
confirmPassword: '',
|
||||
},
|
||||
displayNameIssues: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
availableLanguages: 'i18n.availableLanguages',
|
||||
content: 'content',
|
||||
}),
|
||||
availableAudioThemes () {
|
||||
return ['off', ...this.content.audioThemes];
|
||||
},
|
||||
hasClass () {
|
||||
return this.$store.getters['members:hasClass'](this.user);
|
||||
},
|
||||
|
||||
displayNameInvalid () {
|
||||
if (this.temporaryDisplayName.length <= 1) return false;
|
||||
return !this.displayNameValid;
|
||||
},
|
||||
displayNameValid () {
|
||||
if (this.temporaryDisplayName.length <= 1) return false;
|
||||
return this.displayNameIssues.length === 0;
|
||||
},
|
||||
displayNameCannotSubmit () {
|
||||
if (this.temporaryDisplayName.length <= 1) return true;
|
||||
return !this.displayNameValid;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
|
||||
temporaryDisplayName: {
|
||||
handler () {
|
||||
this.validateDisplayName(this.temporaryDisplayName);
|
||||
},
|
||||
deep: true,
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.SOCIAL_AUTH_NETWORKS = SUPPORTED_SOCIAL_NETWORKS;
|
||||
// @TODO: We may need to request the party here
|
||||
this.party = this.$store.state.party;
|
||||
|
||||
this.temporaryDisplayName = this.user.profile.name;
|
||||
this.emailUpdates.newEmail = this.user.auth.local.email || null;
|
||||
this.localAuth.username = this.user.auth.local.username || null;
|
||||
this.soundIndex = 0;
|
||||
|
||||
this.$store.dispatch('common:setTitle', {
|
||||
section: this.$t('settings'),
|
||||
});
|
||||
|
||||
hello.init({
|
||||
facebook: process.env.FACEBOOK_KEY, // eslint-disable-line no-process-env
|
||||
google: process.env.GOOGLE_CLIENT_ID, // eslint-disable-line no-process-env
|
||||
}, {
|
||||
redirect_uri: '', // eslint-disable-line
|
||||
});
|
||||
|
||||
const focusID = this.$route.query.focus;
|
||||
if (focusID !== undefined && focusID !== null) {
|
||||
this.$nextTick(() => {
|
||||
const element = document.getElementById(focusID);
|
||||
if (element !== undefined && element !== null) {
|
||||
element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
validateDisplayName: debounce(function checkName (displayName) {
|
||||
if (displayName.length <= 1 || displayName === this.user.profile.name) {
|
||||
this.displayNameIssues = [];
|
||||
return;
|
||||
}
|
||||
this.$store.dispatch('auth:verifyDisplayName', {
|
||||
displayName,
|
||||
}).then(res => {
|
||||
if (res.issues !== undefined) {
|
||||
this.displayNameIssues = res.issues;
|
||||
} else {
|
||||
this.displayNameIssues = [];
|
||||
}
|
||||
});
|
||||
}, 500),
|
||||
set (preferenceType, subtype) {
|
||||
const settings = {};
|
||||
if (!subtype) {
|
||||
settings[`preferences.${preferenceType}`] = this.user.preferences[preferenceType];
|
||||
} else {
|
||||
settings[`preferences.${preferenceType}.${subtype}`] = this.user.preferences[preferenceType][subtype];
|
||||
}
|
||||
return this.$store.dispatch('user:set', settings);
|
||||
},
|
||||
hideHeader () {
|
||||
this.set('hideHeader');
|
||||
if (!this.user.preferences.hideHeader || !this.user.preferences.stickyHeader) return;
|
||||
this.user.preferences.hideHeader = false;
|
||||
this.set('stickyHeader');
|
||||
},
|
||||
toggleStickyHeader () {
|
||||
this.set('stickyHeader');
|
||||
},
|
||||
showTour () {
|
||||
// @TODO: Do we still use this?
|
||||
// User.set({'flags.showTour':true});
|
||||
// Guide.goto('intro', 0, true);
|
||||
},
|
||||
hasBackupAuthOption (networkKeyToCheck) {
|
||||
if (this.user.auth.local.username) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.SOCIAL_AUTH_NETWORKS.find(network => {
|
||||
if (network.key !== networkKeyToCheck) {
|
||||
if (this.user.auth[network.key]) {
|
||||
return !!this.user.auth[network.key].id;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
},
|
||||
async changeLanguage (e) {
|
||||
const newLang = e.target.value;
|
||||
this.user.preferences.language = newLang;
|
||||
await this.set('language');
|
||||
setTimeout(() => window.location.reload(true));
|
||||
},
|
||||
async changeUser (attribute, updates) {
|
||||
await axios.put(`/api/v4/user/auth/update-${attribute}`, updates);
|
||||
if (attribute === 'username') {
|
||||
this.user.auth.local.username = updates[attribute];
|
||||
this.localAuth.username = this.user.auth.local.username;
|
||||
this.user.flags.verifiedUsername = true;
|
||||
} else if (attribute === 'email') {
|
||||
this.user.auth.local.email = updates.newEmail;
|
||||
window.alert(this.$t('emailSuccess')); // eslint-disable-line no-alert
|
||||
} else if (attribute === 'password') {
|
||||
this.passwordUpdates = {};
|
||||
this.$store.dispatch('snackbars:add', {
|
||||
title: 'Habitica',
|
||||
text: this.$t('passwordSuccess'),
|
||||
type: 'success',
|
||||
timeout: true,
|
||||
});
|
||||
}
|
||||
},
|
||||
async changeDisplayName (newName) {
|
||||
await axios.put('/api/v4/user/', { 'profile.name': newName });
|
||||
window.alert(this.$t('displayNameSuccess')); // eslint-disable-line no-alert
|
||||
this.user.profile.name = newName;
|
||||
this.temporaryDisplayName = newName;
|
||||
},
|
||||
openRestoreModal () {
|
||||
this.$root.$emit('bv::show::modal', 'restore');
|
||||
},
|
||||
openResetModal () {
|
||||
this.$root.$emit('bv::show::modal', 'reset');
|
||||
},
|
||||
openDeleteModal () {
|
||||
this.$root.$emit('bv::show::modal', 'delete');
|
||||
},
|
||||
async deleteSocialAuth (network) {
|
||||
await axios.delete(`/api/v4/user/auth/social/${network.key}`);
|
||||
this.user.auth[network.key] = {};
|
||||
this.text(this.$t('detachedSocial', { network: network.name }));
|
||||
},
|
||||
async socialAuth (network) {
|
||||
if (network === 'apple') {
|
||||
window.location.href = buildAppleAuthUrl();
|
||||
} else {
|
||||
const auth = await hello(network).login({ scope: 'email' });
|
||||
await this.$store.dispatch('auth:socialAuth', {
|
||||
auth,
|
||||
});
|
||||
window.location.href = '/';
|
||||
}
|
||||
},
|
||||
async changeClassForUser (confirmationNeeded) {
|
||||
if (confirmationNeeded && !window.confirm(this.$t('changeClassConfirmCost'))) return; // eslint-disable-line no-alert
|
||||
try {
|
||||
changeClass(this.user);
|
||||
await axios.post('/api/v4/user/change-class');
|
||||
} catch (e) {
|
||||
window.alert(e.message); // eslint-disable-line no-alert
|
||||
}
|
||||
},
|
||||
async addLocalAuth () {
|
||||
if (this.localAuth.email === '') {
|
||||
this.localAuth.email = this.user.auth.local.email;
|
||||
}
|
||||
await axios.post('/api/v4/user/auth/local/register', this.localAuth);
|
||||
window.location.href = '/user/settings/site';
|
||||
},
|
||||
|
||||
changeAudioTheme () {
|
||||
this.soundIndex = 0;
|
||||
this.set('sound');
|
||||
},
|
||||
playAudio () {
|
||||
this.$root.$emit('playSound', sounds[this.soundIndex]);
|
||||
this.soundIndex = (this.soundIndex + 1) % sounds.length;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div class="input-area">
|
||||
<div class="label-line">
|
||||
<div class="settings-label">
|
||||
{{ $t(customLabel ?? "password") }}
|
||||
</div>
|
||||
<div class="forgot-password">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent=""
|
||||
>
|
||||
{{ $t('forgotPassword') }} TODO
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
>
|
||||
<input
|
||||
v-model="currentPassword"
|
||||
class="form-control"
|
||||
:placeholder="$t('password')"
|
||||
type="text"
|
||||
@keyup="$emit('passwordValue', currentPassword)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'CurrentPasswordInput',
|
||||
props: ['customLabel'],
|
||||
data () {
|
||||
return {
|
||||
currentPassword: '',
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.label-line {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.settings-label {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.forgot-password {
|
||||
a {
|
||||
font-size: 12px;
|
||||
line-height: 1.33;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,183 @@
|
||||
<template>
|
||||
<fragment>
|
||||
<tr
|
||||
v-if="!show"
|
||||
>
|
||||
<td class="settings-label">
|
||||
{{ $t("email") }}
|
||||
</td>
|
||||
<td class="settings-value">
|
||||
{{ user?.auth?.local?.email }}
|
||||
</td>
|
||||
<td class="settings-button">
|
||||
<a
|
||||
class="edit-link"
|
||||
@click.prevent="show = true"
|
||||
>
|
||||
{{ $t('edit') }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr
|
||||
v-if="show"
|
||||
class="expanded"
|
||||
>
|
||||
<td colspan="3">
|
||||
<div
|
||||
v-once
|
||||
class="dialog-title"
|
||||
>
|
||||
{{ $t("email") }}
|
||||
</div>
|
||||
<div
|
||||
v-once
|
||||
class="dialog-disclaimer"
|
||||
>
|
||||
{{ $t("changeEmailDisclaimer") }}
|
||||
</div>
|
||||
|
||||
<div class="input-area">
|
||||
<div class="settings-label">
|
||||
{{ $t("email") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div
|
||||
class="input-group"
|
||||
:class="{ 'is-valid': validEmail }"
|
||||
>
|
||||
<input
|
||||
id="changeEmail"
|
||||
v-model="updates.newEmail"
|
||||
class="form-control"
|
||||
type="text"
|
||||
:class="{ 'is-invalid input-invalid': !validEmail }"
|
||||
@blur="restoreEmptyEmail()"
|
||||
>
|
||||
<div class="input-floating-checkmark">
|
||||
<div
|
||||
v-once
|
||||
class="svg-icon color check-icon"
|
||||
v-html="icons.checkIcon"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<current-password-input @passwordValue="updates.password = $event" />
|
||||
|
||||
<save-cancel-buttons
|
||||
:disable-save="allowedToSave"
|
||||
@saveClicked="changeEmail()"
|
||||
@cancelClicked="resetAndClose()"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
</fragment>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~@/assets/scss/colors.scss';
|
||||
|
||||
.input-group {
|
||||
position: relative;
|
||||
background: white;
|
||||
}
|
||||
|
||||
input {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
|
||||
.input-floating-checkmark {
|
||||
position: absolute;
|
||||
background: none !important;
|
||||
right: 0.5rem;
|
||||
top: 0.5rem;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.input-group.is-valid {
|
||||
border-color: $green-10 !important;
|
||||
}
|
||||
|
||||
.input-group:not(.is-valid) {
|
||||
.check-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.check-icon {
|
||||
width: 12px;
|
||||
height: 10px;
|
||||
color: $green-50;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import * as validator from 'validator';
|
||||
import { mapState } from '@/libs/store';
|
||||
|
||||
import checkIcon from '@/assets/svg/check.svg';
|
||||
import SaveCancelButtons from '@/pages/settings/inlineSettings/_saveCancelButtons';
|
||||
import { _InlineSettingMixin } from '@/pages/settings/inlineSettings/_inlineSettingMixin';
|
||||
import CurrentPasswordInput from '@/pages/settings/inlineSettings/_currentPasswordInput';
|
||||
|
||||
export default {
|
||||
components: { CurrentPasswordInput, SaveCancelButtons },
|
||||
mixins: [_InlineSettingMixin],
|
||||
data () {
|
||||
return {
|
||||
show: false,
|
||||
updates: {
|
||||
newEmail: '',
|
||||
password: '',
|
||||
},
|
||||
icons: Object.freeze({
|
||||
checkIcon,
|
||||
}),
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
user: 'user.data',
|
||||
}),
|
||||
validEmail () {
|
||||
return validator.isEmail(this.updates.newEmail);
|
||||
},
|
||||
allowedToSave () {
|
||||
return !this.validEmail || this.updates.password.length === 0;
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
this.restoreEmptyEmail();
|
||||
},
|
||||
methods: {
|
||||
resetAndClose () {
|
||||
this.show = false;
|
||||
},
|
||||
restoreEmptyEmail () {
|
||||
if (this.updates.newEmail.length < 1) {
|
||||
this.updates.newEmail = this.user.auth.local.email;
|
||||
}
|
||||
},
|
||||
async changeEmail () {
|
||||
await axios.put('/api/v4/user/auth/update-email', this.updates);
|
||||
|
||||
this.user.auth.local.email = this.updates.newEmail;
|
||||
window.alert(this.$t('emailSuccess')); // eslint-disable-line no-alert
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -51,7 +51,7 @@
|
||||
class="form-control"
|
||||
type="text"
|
||||
:placeholder="$t('newUsername')"
|
||||
:class="{ 'is-invalid input-invalid': usernameInvalid }"
|
||||
:class="{ 'is-invalid input-invalid': !usernameValid }"
|
||||
@blur="restoreEmptyUsername()"
|
||||
>
|
||||
<div class="input-floating-checkmark">
|
||||
@@ -159,15 +159,6 @@ export default {
|
||||
// remove the @ from the value
|
||||
return this.inputValue.replace('@', '');
|
||||
},
|
||||
verifiedUsername () {
|
||||
return this.user.flags.verifiedUsername;
|
||||
},
|
||||
usernameInvalid () {
|
||||
if (this.cleanedInputValue.length <= 1) {
|
||||
return false;
|
||||
}
|
||||
return !this.usernameValid;
|
||||
},
|
||||
usernameValid () {
|
||||
if (this.cleanedInputValue.length <= 1) {
|
||||
return false;
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
"usernameVerifiedConfirmation": "Your username, <%= username %>, is confirmed!",
|
||||
"usernameNotVerified": "Please confirm your username.",
|
||||
"changeUsernameDisclaimer": "Your username is used for invitations, @mentions in chat, and messaging. It must be 1 to 20 characters, containing only letters a to z, numbers 0 to 9, hyphens, or underscores, and cannot include any inappropriate terms.",
|
||||
"changeEmailDisclaimer": "This is the email address that you use to log in to Habitica, as well as receive notifications.",
|
||||
"verifyUsernameVeteranPet": "One of these Veteran Pets will be waiting for you after you've finished confirming!",
|
||||
"mentioning": "Mentioning",
|
||||
"suggestMyUsername": "Suggest my username",
|
||||
|
||||
Reference in New Issue
Block a user