mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
feat(usernames): verification step during Justin intro for social users
This commit is contained in:
@@ -65,6 +65,7 @@ input, textarea, input.form-control, textarea.form-control {
|
|||||||
padding-right: 40px;
|
padding-right: 40px;
|
||||||
background-image: url(~client/assets/svg/for-css/alert.svg);
|
background-image: url(~client/assets/svg/for-css/alert.svg);
|
||||||
background-size: 16px 16px;
|
background-size: 16px 16px;
|
||||||
|
margin-bottom: 0.1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
h3(v-once) {{$t('welcomeTo')}}
|
h3(v-once) {{$t('welcomeTo')}}
|
||||||
.svg-icon.logo(v-html='icons.logoPurple')
|
.svg-icon.logo(v-html='icons.logoPurple')
|
||||||
|
|
||||||
.avatar-section.row(:class='{"page-2": modalPage === 2}')
|
.avatar-section.row(v-if='modalPage > 1 || user.auth.local.email', :class='{"page-2": modalPage === 2}')
|
||||||
.col-6.offset-3
|
.col-6.offset-3
|
||||||
.user-creation-bg(v-if='!editing')
|
.user-creation-bg(v-if='!editing')
|
||||||
avatar(:member='user', :avatarOnly='!editing', :class='{"edit-avatar": editing}')
|
avatar(:member='user', :avatarOnly='!editing', :class='{"edit-avatar": editing}')
|
||||||
@@ -187,18 +187,18 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
#extra.section.container.customize-section(v-if='activeTopPage === "extra"')
|
#extra.section.container.customize-section(v-if='activeTopPage === "extra"')
|
||||||
.row.sub-menu
|
.row.sub-menu
|
||||||
.col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
|
.col-3.offset-1.text-center.sub-menu-item(@click='changeSubPage("glasses")', :class='{active: activeSubPage === "glasses"}')
|
||||||
strong(v-once) {{$t('glasses')}}
|
strong(v-once) {{ $t('glasses') }}
|
||||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("wheelchair")', :class='{active: activeSubPage === "wheelchair"}')
|
.col-4.text-center.sub-menu-item(@click='changeSubPage("wheelchair")', :class='{active: activeSubPage === "wheelchair"}')
|
||||||
strong(v-once) {{$t('wheelchair')}}
|
strong(v-once) {{ $t('wheelchair') }}
|
||||||
.col-3.text-center.sub-menu-item(@click='changeSubPage("flower")', :class='{active: activeSubPage === "flower"}')
|
.col-3.text-center.sub-menu-item(@click='changeSubPage("flower")', :class='{active: activeSubPage === "flower"}')
|
||||||
strong(v-once) {{$t('accent')}}
|
strong(v-once) {{ $t('accent') }}
|
||||||
.row.sub-menu(v-if='editing')
|
.row.sub-menu(v-if='editing')
|
||||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("ears")' :class='{active: activeSubPage === "ears"}')
|
.col-4.text-center.sub-menu-item(@click='changeSubPage("ears")' :class='{active: activeSubPage === "ears"}')
|
||||||
strong(v-once) {{$t('animalEars')}}
|
strong(v-once) {{ $t('animalEars') }}
|
||||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("tails")' :class='{active: activeSubPage === "tails"}')
|
.col-4.text-center.sub-menu-item(@click='changeSubPage("tails")' :class='{active: activeSubPage === "tails"}')
|
||||||
strong(v-once) {{$t('animalTails')}}
|
strong(v-once) {{ $t('animalTails') }}
|
||||||
.col-4.text-center.sub-menu-item(@click='changeSubPage("headband")' :class='{active: activeSubPage === "headband"}')
|
.col-4.text-center.sub-menu-item(@click='changeSubPage("headband")' :class='{active: activeSubPage === "headband"}')
|
||||||
strong(v-once) {{$t('headband')}}
|
strong(v-once) {{ $t('headband') }}
|
||||||
#glasses.row(v-if='activeSubPage === "glasses"')
|
#glasses.row(v-if='activeSubPage === "glasses"')
|
||||||
.col-12.customize-options
|
.col-12.customize-options
|
||||||
.option(v-for='option in eyewear', :class='{active: option.active}')
|
.option(v-for='option in eyewear', :class='{active: option.active}')
|
||||||
@@ -305,7 +305,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
)
|
)
|
||||||
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
span.svg-icon.inline.icon-12.color(v-html="icons.pin")
|
||||||
.purchase-background.set(v-if='!ownsSet("background", set.items) && set.identifier !== "incentiveBackgrounds"' @click='unlock(setKeys("background", set.items))')
|
.purchase-background.set(v-if='!ownsSet("background", set.items) && set.identifier !== "incentiveBackgrounds"' @click='unlock(setKeys("background", set.items))')
|
||||||
span.label Purchase Set
|
span.label {{ $t('purchaseAll') }}
|
||||||
.svg-icon.gem(v-html='icons.gem')
|
.svg-icon.gem(v-html='icons.gem')
|
||||||
span.price 15
|
span.price 15
|
||||||
.row.customize-menu(v-if='filterBackgrounds')
|
.row.customize-menu(v-if='filterBackgrounds')
|
||||||
@@ -320,7 +320,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
.container.interests-section(v-if='modalPage === 3 && !editing')
|
.container.interests-section(v-if='modalPage === 3 && !editing')
|
||||||
.section.row
|
.section.row
|
||||||
.col-12.text-center
|
.col-12.text-center
|
||||||
h2 I want to work on:
|
h2 {{ $t('wantToWorkOn') }}
|
||||||
.section.row
|
.section.row
|
||||||
.col-6
|
.col-6
|
||||||
.task-option
|
.task-option
|
||||||
@@ -353,28 +353,36 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
input.custom-control-input#self_care(type="checkbox", value='self_care', v-model='taskCategories')
|
input.custom-control-input#self_care(type="checkbox", value='self_care', v-model='taskCategories')
|
||||||
label.custom-control-label(v-once, for="self_care") {{ $t('self_care') }}
|
label.custom-control-label(v-once, for="self_care") {{ $t('self_care') }}
|
||||||
|
|
||||||
.section.row.justin-message-section(:class='{top: modalPage > 1}', v-if='!editing')
|
.section.d-flex.justify-content-center(:class='{top: modalPage > 1}', v-if='!editing')
|
||||||
.col-12
|
.justin-section.d-flex.align-items-center
|
||||||
.justin-message.d-flex.flex-column.justify-content-center
|
.featured-label
|
||||||
.featured-label
|
span.rectangle
|
||||||
span.rectangle
|
span.text Justin
|
||||||
span.text Justin
|
span.rectangle
|
||||||
span.rectangle
|
.justin-message
|
||||||
.npc_justin_textbox
|
.corner-decoration(:style="{top: '-2px', right: '-2px'}")
|
||||||
|
.corner-decoration(:style="{top: '-2px', left: '-2px'}")
|
||||||
|
.corner-decoration(:style="{bottom: '-2px', right: '-2px'}")
|
||||||
|
.corner-decoration(:style="{bottom: '-2px', left: '-2px'}")
|
||||||
div(v-if='modalPage === 1')
|
div(v-if='modalPage === 1')
|
||||||
p(v-once) {{$t('justinIntroMessage1')}}
|
p(v-once, v-html='$t("justinIntroMessage1")')
|
||||||
p(v-once) {{$t('justinIntroMessage2')}}
|
p(v-once, v-if='user.auth.local.email') {{ $t('justinIntroMessage2') }}
|
||||||
|
p(v-once, v-if='!user.auth.local.email') {{ $t('justinIntroMessageUsername') }}
|
||||||
div(v-if='modalPage === 2')
|
div(v-if='modalPage === 2')
|
||||||
p So how would you like to look? Don’t worry, you can change this later.
|
p {{ $t('justinIntroMessageAppearance') }}
|
||||||
div(v-if='modalPage === 3')
|
div(v-if='modalPage === 3')
|
||||||
p(v-once) {{$t('justinIntroMessage3')}}
|
p(v-once) {{ $t('justinIntroMessage3') }}
|
||||||
|
.npc-justin-textbox
|
||||||
|
.section.mr-5.ml-5(v-if='modalPage === 1 && !user.auth.local.email')
|
||||||
|
username-form(@usernameConfirmed='modalPage += 1', :avatarIntro='"true"')
|
||||||
|
.small.text-center(v-html="$t('usernameTOSRequirements')")
|
||||||
|
|
||||||
.section.container.footer(v-if='!editing')
|
.section.container.footer
|
||||||
.row
|
.row(v-if='!editing && !(modalPage === 1 && !user.auth.local.email)')
|
||||||
.col-3.offset-1.text-center
|
.col-3.offset-1.text-center
|
||||||
div(v-if='modalPage > 1', @click='prev()')
|
div(v-if='modalPage > 1', @click='prev()')
|
||||||
.prev-arrow
|
.prev-arrow
|
||||||
.prev(v-once) {{$t('prev')}}
|
.prev(v-once) {{ $t('prev') }}
|
||||||
.col-4.text-center.circles
|
.col-4.text-center.circles
|
||||||
.circle(:class="{active: modalPage === 1}")
|
.circle(:class="{active: modalPage === 1}")
|
||||||
.circle(:class="{active: modalPage === 2}")
|
.circle(:class="{active: modalPage === 2}")
|
||||||
@@ -390,12 +398,9 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
<style>
|
<style>
|
||||||
/* @TODO do not rely on avatar-modal___BV_modal_body_,
|
/* @TODO do not rely on avatar-modal___BV_modal_body_,
|
||||||
it already changed once when bootstrap-vue reached version 1 */
|
it already changed once when bootstrap-vue reached version 1 */
|
||||||
.page-2 #avatar-modal___BV_modal_body_ {
|
|
||||||
margin-top: 9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-2 .modal-content {
|
.page-2 #avatar-modal___BV_modal_body_ {
|
||||||
margin-top: 7em;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#avatar-modal___BV_modal_body_, #avatar-modal___BV_modal_body_ {
|
#avatar-modal___BV_modal_body_, #avatar-modal___BV_modal_body_ {
|
||||||
@@ -421,6 +426,25 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.corner-decoration {
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: #ffbe5d;
|
||||||
|
border: inherit;
|
||||||
|
outline: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: normal;
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
.purchase-all {
|
.purchase-all {
|
||||||
margin-bottom: 1em;
|
margin-bottom: 1em;
|
||||||
}
|
}
|
||||||
@@ -444,7 +468,7 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
width: 190px;
|
width: 190px;
|
||||||
margin: 0 auto;
|
margin: 0 auto 1.25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-creation-bg {
|
.user-creation-bg {
|
||||||
@@ -464,32 +488,22 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
left: 9.2em;
|
left: 9.2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.justin-message {
|
.justin-section {
|
||||||
background-image: url('~client/assets/svg/for-css/tutorial-border.svg');
|
|
||||||
height: 144px;
|
|
||||||
width: 400px;
|
|
||||||
padding: 2em;
|
|
||||||
margin: 0 auto;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
.featured-label {
|
.justin-message {
|
||||||
position: absolute;
|
border-color: #ffa623;
|
||||||
top: -1em;
|
border-style: solid;
|
||||||
|
border-width: 2px;
|
||||||
.text {
|
outline-color: #b36213;
|
||||||
min-height: auto;
|
outline-style: solid;
|
||||||
color: $white;
|
outline-width: 2px;
|
||||||
}
|
position: relative;
|
||||||
}
|
padding: 2em;
|
||||||
|
margin: 2px;
|
||||||
.npc_justin_textbox {
|
height: 100%;
|
||||||
position: absolute;
|
width: 400px;
|
||||||
right: 1em;
|
|
||||||
top: -3.6em;
|
|
||||||
width: 48px;
|
|
||||||
height: 52px;
|
|
||||||
background-image: url('~client/assets/images/justin_textbox.png');
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: auto;
|
margin: auto;
|
||||||
@@ -500,15 +514,27 @@ b-modal#avatar-modal(title="", :size='editing ? "lg" : "md"', :hide-header='true
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.justin-message-section {
|
.npc-justin-textbox {
|
||||||
margin-top: 4em;
|
position: absolute;
|
||||||
margin-bottom: 2em;
|
right: 1rem;
|
||||||
|
top: -3.1rem;
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background-image: url('~client/assets/images/justin_textbox.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.justin-message-section.top {
|
.featured-label {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: -16em;
|
top: -1rem;
|
||||||
left: 3.5em;
|
left: 1.5rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
margin: auto;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
font-size: 12px;
|
||||||
|
min-height: auto;
|
||||||
|
color: $white;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.circles {
|
.circles {
|
||||||
@@ -865,6 +891,7 @@ import get from 'lodash/get';
|
|||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
import avatar from './avatar';
|
import avatar from './avatar';
|
||||||
|
import usernameForm from './settings/usernameForm';
|
||||||
import { getBackgroundShopSets } from '../../common/script/libs/shops';
|
import { getBackgroundShopSets } from '../../common/script/libs/shops';
|
||||||
import unlock from '../../common/script/ops/unlock';
|
import unlock from '../../common/script/ops/unlock';
|
||||||
import buy from '../../common/script/ops/buy/buy';
|
import buy from '../../common/script/ops/buy/buy';
|
||||||
@@ -1022,6 +1049,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
avatar,
|
avatar,
|
||||||
toggleSwitch,
|
toggleSwitch,
|
||||||
|
usernameForm,
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
if (this.editing) this.modalPage = 2;
|
if (this.editing) this.modalPage = 2;
|
||||||
|
|||||||
216
website/client/components/settings/usernameForm.vue
Normal file
216
website/client/components/settings/usernameForm.vue
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
<template lang="pug">
|
||||||
|
div
|
||||||
|
.form-group
|
||||||
|
.d-flex.align-items-center
|
||||||
|
label.mr-3(for='displayName') {{ $t('displayName') }}
|
||||||
|
.flex-grow-1
|
||||||
|
input#displayName.form-control(
|
||||||
|
type='text',
|
||||||
|
:placeholder="$t('newDisplayName')",
|
||||||
|
v-model='temporaryDisplayName',
|
||||||
|
@blur='restoreEmptyDisplayName()',
|
||||||
|
:class='{"is-invalid input-invalid": displayNameInvalid, "input-valid": displayNameValid, "text-darker": temporaryDisplayName.length > 0}')
|
||||||
|
.mb-3(v-if="displayNameIssues.length > 0")
|
||||||
|
.input-error.text-center(v-for="issue in displayNameIssues") {{ issue }}
|
||||||
|
.form-group
|
||||||
|
.d-flex.align-items-center
|
||||||
|
label.mr-3(for='username') {{ $t('username') }}
|
||||||
|
.flex-grow-1
|
||||||
|
.input-group-prepend.input-group-text @
|
||||||
|
input#username.form-control(
|
||||||
|
type='text',
|
||||||
|
:placeholder="$t('newUsername')",
|
||||||
|
v-model='temporaryUsername',
|
||||||
|
@blur='restoreEmptyUsername()',
|
||||||
|
:class='{"is-invalid input-invalid": usernameInvalid, "input-valid": usernameValid, "text-darker": temporaryUsername.length > 0}')
|
||||||
|
.mb-3(v-if="usernameIssues.length > 0")
|
||||||
|
.input-error.text-center(v-for="issue in usernameIssues") {{ issue }}
|
||||||
|
.small.text-center(v-if='!avatarIntro') {{ $t('usernameLimitations') }}
|
||||||
|
.row.justify-content-center
|
||||||
|
button.btn.btn-primary(type='submit', @click='submitNames()' :disabled='usernameCannotSubmit') {{ $t(avatarIntro ? 'getStarted' : 'saveAndConfirm') }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin: 0.25rem auto 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.col-3 {
|
||||||
|
padding-right: 0rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
background-color: $gray-700;
|
||||||
|
border-radius: 2px;
|
||||||
|
border: solid 1px $gray-500;
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
border: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-error {
|
||||||
|
color: $red-50;
|
||||||
|
font-size: 90%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-prepend {
|
||||||
|
margin-right: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-text {
|
||||||
|
border: 0px;
|
||||||
|
background-color: $white;
|
||||||
|
color: $gray-300;
|
||||||
|
padding: 0rem 0rem 0rem 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
color: $gray-100;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 0rem;
|
||||||
|
margin-left: 1rem;
|
||||||
|
min-width: 90px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.small {
|
||||||
|
color: $gray-200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-darker {
|
||||||
|
color: $gray-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
#username {
|
||||||
|
padding-left: 0.25rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import axios from 'axios';
|
||||||
|
import debounce from 'lodash/debounce';
|
||||||
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
user: 'user.data',
|
||||||
|
}),
|
||||||
|
displayNameInvalid () {
|
||||||
|
if (this.temporaryDisplayName.length < 1) return false;
|
||||||
|
return !this.displayNameValid;
|
||||||
|
},
|
||||||
|
displayNameValid () {
|
||||||
|
if (this.temporaryDisplayName.length < 1) return false;
|
||||||
|
return this.displayNameIssues.length === 0;
|
||||||
|
},
|
||||||
|
usernameCannotSubmit () {
|
||||||
|
if (this.temporaryUsername.length < 1) return true;
|
||||||
|
return !this.usernameValid || !this.displayNameValid;
|
||||||
|
},
|
||||||
|
usernameInvalid () {
|
||||||
|
if (this.temporaryUsername.length < 1) return false;
|
||||||
|
return !this.usernameValid;
|
||||||
|
},
|
||||||
|
usernameValid () {
|
||||||
|
if (this.temporaryUsername.length < 1) return false;
|
||||||
|
return this.usernameIssues.length === 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
displayNameIssues: [],
|
||||||
|
temporaryDisplayName: '',
|
||||||
|
temporaryUsername: '',
|
||||||
|
usernameIssues: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async close () {
|
||||||
|
this.$root.$emit('habitica::resync-requested');
|
||||||
|
await this.$store.dispatch('user:fetch', {forceLoad: true});
|
||||||
|
this.$root.$emit('habitica::resync-completed');
|
||||||
|
if (this.avatarIntro) {
|
||||||
|
this.$emit('usernameConfirmed');
|
||||||
|
} else {
|
||||||
|
this.$root.$emit('bv::hide::modal', 'verify-username');
|
||||||
|
this.$router.go(0);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restoreEmptyDisplayName () {
|
||||||
|
if (this.temporaryDisplayName.length < 1) {
|
||||||
|
this.temporaryDisplayName = this.user.profile.name;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
restoreEmptyUsername () {
|
||||||
|
if (this.temporaryUsername.length < 1) {
|
||||||
|
this.temporaryUsername = this.user.auth.local.username;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async submitNames () {
|
||||||
|
if (this.temporaryDisplayName !== this.user.profile.name) {
|
||||||
|
await axios.put('/api/v4/user/', {'profile.name': this.temporaryDisplayName});
|
||||||
|
}
|
||||||
|
await axios.put('/api/v4/user/auth/update-username', {username: this.temporaryUsername});
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
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),
|
||||||
|
validateUsername: debounce(function checkName (username) {
|
||||||
|
if (username.length <= 1 || username === this.user.auth.local.username) {
|
||||||
|
this.usernameIssues = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$store.dispatch('auth:verifyUsername', {
|
||||||
|
username,
|
||||||
|
}).then(res => {
|
||||||
|
if (res.issues !== undefined) {
|
||||||
|
this.usernameIssues = res.issues;
|
||||||
|
} else {
|
||||||
|
this.usernameIssues = [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 500),
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.temporaryDisplayName = this.user.profile.name;
|
||||||
|
this.temporaryUsername = this.user.auth.local.username;
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
avatarIntro: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
temporaryDisplayName: {
|
||||||
|
handler () {
|
||||||
|
this.validateDisplayName(this.temporaryDisplayName);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
temporaryUsername: {
|
||||||
|
handler () {
|
||||||
|
this.validateUsername(this.temporaryUsername);
|
||||||
|
},
|
||||||
|
deep: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -10,36 +10,7 @@
|
|||||||
div.nametag-header(v-html='icons.helloNametag')
|
div.nametag-header(v-html='icons.helloNametag')
|
||||||
h2.text-center {{ $t('usernameTime') }}
|
h2.text-center {{ $t('usernameTime') }}
|
||||||
p.text-center(v-html="$t('usernameInfo')")
|
p.text-center(v-html="$t('usernameInfo')")
|
||||||
.form-group
|
username-form
|
||||||
.row.align-items-center
|
|
||||||
.col-3
|
|
||||||
label(for='displayName') {{ $t('displayName') }}
|
|
||||||
.col-9
|
|
||||||
input#displayName.form-control(
|
|
||||||
type='text',
|
|
||||||
:placeholder="$t('newDisplayName')",
|
|
||||||
v-model='temporaryDisplayName',
|
|
||||||
@blur='restoreEmptyDisplayName()',
|
|
||||||
:class='{"is-invalid input-invalid": displayNameInvalid, "input-valid": displayNameValid, "text-darker": temporaryDisplayName.length > 0}')
|
|
||||||
.mb-3(v-if="displayNameIssues.length > 0")
|
|
||||||
.input-error.text-center(v-for="issue in displayNameIssues") {{ issue }}
|
|
||||||
.form-group
|
|
||||||
.row.align-items-center
|
|
||||||
.col-3
|
|
||||||
label(for='username') {{ $t('username') }}
|
|
||||||
.col-9
|
|
||||||
.input-group-prepend.input-group-text @
|
|
||||||
input#username.form-control(
|
|
||||||
type='text',
|
|
||||||
:placeholder="$t('newUsername')",
|
|
||||||
v-model='temporaryUsername',
|
|
||||||
@blur='restoreEmptyUsername()',
|
|
||||||
:class='{"is-invalid input-invalid": usernameInvalid, "input-valid": usernameValid, "text-darker": temporaryUsername.length > 0}')
|
|
||||||
.mb-3(v-if="usernameIssues.length > 0")
|
|
||||||
.input-error.text-center(v-for="issue in usernameIssues") {{ issue }}
|
|
||||||
.small.text-center {{ $t('usernameLimitations') }}
|
|
||||||
.row.justify-content-center
|
|
||||||
button.btn.btn-primary(type='submit', @click='submitNames()' :disabled='usernameCannotSubmit') {{ $t('saveAndConfirm') }}
|
|
||||||
.scene_veteran_pets.center-block
|
.scene_veteran_pets.center-block
|
||||||
.small.text-center.mb-3 {{ $t('verifyUsernameVeteranPet') }}
|
.small.text-center.mb-3 {{ $t('verifyUsernameVeteranPet') }}
|
||||||
.small.text-center.tos-footer(v-html="$t('usernameTOSRequirements')")
|
.small.text-center.tos-footer(v-html="$t('usernameTOSRequirements')")
|
||||||
@@ -62,57 +33,15 @@
|
|||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import '~client/assets/scss/colors.scss';
|
@import '~client/assets/scss/colors.scss';
|
||||||
|
|
||||||
button {
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.center-block {
|
.center-block {
|
||||||
margin: 0 auto 1em auto;
|
margin: 0 auto 1em auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.col-3 {
|
|
||||||
padding-right: 0rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
background-color: $gray-700;
|
|
||||||
border-radius: 2px;
|
|
||||||
border: solid 1px $gray-500;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
color: $purple-200;
|
color: $purple-200;
|
||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
input {
|
|
||||||
border: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-error {
|
|
||||||
color: $red-50;
|
|
||||||
font-size: 90%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-prepend {
|
|
||||||
margin-right: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-group-text {
|
|
||||||
border: 0px;
|
|
||||||
background-color: $white;
|
|
||||||
color: $gray-300;
|
|
||||||
padding: 0rem 0rem 0rem 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
label {
|
|
||||||
color: $gray-100;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: 0rem;
|
|
||||||
margin-left: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nametag-header {
|
.nametag-header {
|
||||||
background-color: $gray-700;
|
background-color: $gray-700;
|
||||||
border-radius: 0.3rem 0.3rem 0rem 0rem;
|
border-radius: 0.3rem 0.3rem 0rem 0rem;
|
||||||
@@ -122,17 +51,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
p {
|
p {
|
||||||
color: #686274;
|
color: $gray-100;
|
||||||
}
|
}
|
||||||
|
|
||||||
.small {
|
.small {
|
||||||
color: $gray-200;
|
color: $gray-200;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-darker {
|
|
||||||
color: $gray-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tos-footer {
|
.tos-footer {
|
||||||
background-color: $gray-700;
|
background-color: $gray-700;
|
||||||
border-radius: 0rem 0rem 0.3rem 0.3rem;
|
border-radius: 0rem 0rem 0.3rem 0.3rem;
|
||||||
@@ -140,129 +65,22 @@
|
|||||||
margin-right: -3rem;
|
margin-right: -3rem;
|
||||||
padding: 1rem 4rem 1rem 4rem;
|
padding: 1rem 4rem 1rem 4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#username {
|
|
||||||
padding-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import axios from 'axios';
|
|
||||||
import debounce from 'lodash/debounce';
|
|
||||||
import helloNametag from 'assets/svg/hello-habitican.svg';
|
import helloNametag from 'assets/svg/hello-habitican.svg';
|
||||||
import { mapState } from 'client/libs/store';
|
import usernameForm from './usernameForm';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
computed: {
|
components: {
|
||||||
...mapState({
|
usernameForm,
|
||||||
user: 'user.data',
|
|
||||||
}),
|
|
||||||
displayNameInvalid () {
|
|
||||||
if (this.temporaryDisplayName.length < 1) return false;
|
|
||||||
return !this.displayNameValid;
|
|
||||||
},
|
|
||||||
displayNameValid () {
|
|
||||||
if (this.temporaryDisplayName.length < 1) return false;
|
|
||||||
return this.displayNameIssues.length === 0;
|
|
||||||
},
|
|
||||||
usernameCannotSubmit () {
|
|
||||||
if (this.temporaryUsername.length < 1) return true;
|
|
||||||
return !this.usernameValid || !this.displayNameValid;
|
|
||||||
},
|
|
||||||
usernameInvalid () {
|
|
||||||
if (this.temporaryUsername.length < 1) return false;
|
|
||||||
return !this.usernameValid;
|
|
||||||
},
|
|
||||||
usernameValid () {
|
|
||||||
if (this.temporaryUsername.length < 1) return false;
|
|
||||||
return this.usernameIssues.length === 0;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
icons: Object.freeze({
|
icons: Object.freeze({
|
||||||
helloNametag,
|
helloNametag,
|
||||||
}),
|
}),
|
||||||
displayNameIssues: [],
|
|
||||||
temporaryDisplayName: '',
|
|
||||||
temporaryUsername: '',
|
|
||||||
usernameIssues: [],
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
async submitNames () {
|
|
||||||
if (this.temporaryDisplayName !== this.user.profile.name) {
|
|
||||||
await axios.put('/api/v4/user/', {'profile.name': this.temporaryDisplayName});
|
|
||||||
}
|
|
||||||
await axios.put('/api/v4/user/auth/update-username', {username: this.temporaryUsername});
|
|
||||||
this.close();
|
|
||||||
},
|
|
||||||
async close () {
|
|
||||||
this.$root.$emit('habitica::resync-requested');
|
|
||||||
await this.$store.dispatch('user:fetch', {forceLoad: true});
|
|
||||||
this.$root.$emit('habitica::resync-completed');
|
|
||||||
this.$root.$emit('bv::hide::modal', 'verify-username');
|
|
||||||
this.$router.go(0);
|
|
||||||
},
|
|
||||||
restoreEmptyDisplayName () {
|
|
||||||
if (this.temporaryDisplayName.length < 1) {
|
|
||||||
this.temporaryDisplayName = this.user.profile.name;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
restoreEmptyUsername () {
|
|
||||||
if (this.temporaryUsername.length < 1) {
|
|
||||||
this.temporaryUsername = this.user.auth.local.username;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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),
|
|
||||||
validateUsername: debounce(function checkName (username) {
|
|
||||||
if (username.length <= 1 || username === this.user.auth.local.username) {
|
|
||||||
this.usernameIssues = [];
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.$store.dispatch('auth:verifyUsername', {
|
|
||||||
username,
|
|
||||||
}).then(res => {
|
|
||||||
if (res.issues !== undefined) {
|
|
||||||
this.usernameIssues = res.issues;
|
|
||||||
} else {
|
|
||||||
this.usernameIssues = [];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 500),
|
|
||||||
},
|
|
||||||
mounted () {
|
|
||||||
this.temporaryDisplayName = this.user.profile.name;
|
|
||||||
this.temporaryUsername = this.user.auth.local.username;
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
temporaryDisplayName: {
|
|
||||||
handler () {
|
|
||||||
this.validateDisplayName(this.temporaryDisplayName);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
temporaryUsername: {
|
|
||||||
handler () {
|
|
||||||
this.validateUsername(this.temporaryUsername);
|
|
||||||
},
|
|
||||||
deep: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
"noPhoto": "This Habitican hasn't added a photo.",
|
"noPhoto": "This Habitican hasn't added a photo.",
|
||||||
"other": "Other",
|
"other": "Other",
|
||||||
"fullName": "Full Name",
|
"fullName": "Full Name",
|
||||||
"displayName": "Display Name",
|
"displayName": "Display name",
|
||||||
"changeDisplayName": "Change Display Name",
|
"changeDisplayName": "Change Display Name",
|
||||||
"newDisplayName": "New Display Name",
|
"newDisplayName": "New Display Name",
|
||||||
"displayPhoto": "Photo",
|
"displayPhoto": "Photo",
|
||||||
|
|||||||
@@ -329,7 +329,7 @@
|
|||||||
"joinToday": "Join Habitica Today",
|
"joinToday": "Join Habitica Today",
|
||||||
"featuredIn": "Featured in",
|
"featuredIn": "Featured in",
|
||||||
"signup": "Sign Up",
|
"signup": "Sign Up",
|
||||||
"getStarted": "Get Started",
|
"getStarted": "Get Started!",
|
||||||
"mobileApps": "Mobile Apps",
|
"mobileApps": "Mobile Apps",
|
||||||
"learnMore": "Learn More"
|
"learnMore": "Learn More"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -254,6 +254,7 @@
|
|||||||
"resetFilters": "Clear all filters",
|
"resetFilters": "Clear all filters",
|
||||||
"applyFilters": "Apply Filters",
|
"applyFilters": "Apply Filters",
|
||||||
|
|
||||||
|
"wantToWorkOn": "I want to work on:",
|
||||||
"categories": "Categories",
|
"categories": "Categories",
|
||||||
"habiticaOfficial": "Habitica Official",
|
"habiticaOfficial": "Habitica Official",
|
||||||
"animals": "Animals",
|
"animals": "Animals",
|
||||||
|
|||||||
@@ -6,9 +6,11 @@
|
|||||||
"welcomeTo": "Welcome to",
|
"welcomeTo": "Welcome to",
|
||||||
"welcomeBack": "Welcome back!",
|
"welcomeBack": "Welcome back!",
|
||||||
"justin": "Justin",
|
"justin": "Justin",
|
||||||
"justinIntroMessage1": "Hello there! You must be new here. My name is Justin, your guide to Habitica.",
|
"justinIntroMessage1": "Hello there! You must be new here. My name is <strong>Justin</strong>, and I'll be your guide in Habitica.",
|
||||||
"justinIntroMessage2": "To start, you'll need to create an avatar.",
|
"justinIntroMessage2": "To start, you'll need to create an avatar.",
|
||||||
"justinIntroMessage3": "Great! Now, what are you interested in working on throughout this journey?",
|
"justinIntroMessage3": "Great! Now, what are you interested in working on throughout this journey?",
|
||||||
|
"justinIntroMessageUsername": "Before we begin, let’s figure out what to call you. Below you’ll find a display name and username I’ve generated for you. After you’ve picked a display name and username, we’ll get started by creating an avatar!",
|
||||||
|
"justinIntroMessageAppearance": "So how would you like to look? Don’t worry, you can change this later.",
|
||||||
"introTour": "Here we are! I've filled out some Tasks for you based on your interests, so you can get started right away. Click a Task to edit or add new Tasks to fit your routine!",
|
"introTour": "Here we are! I've filled out some Tasks for you based on your interests, so you can get started right away. Click a Task to edit or add new Tasks to fit your routine!",
|
||||||
"prev": "Prev",
|
"prev": "Prev",
|
||||||
"next": "Next",
|
"next": "Next",
|
||||||
|
|||||||
@@ -210,7 +210,7 @@
|
|||||||
"haveCouponCode": "Do you have a coupon code?",
|
"haveCouponCode": "Do you have a coupon code?",
|
||||||
"subscriptionAlreadySubscribedLeadIn": "Thanks for subscribing!",
|
"subscriptionAlreadySubscribedLeadIn": "Thanks for subscribing!",
|
||||||
"subscriptionAlreadySubscribed1": "To see your subscription details and cancel, renew, or change your subscription, please go to <a href='/user/settings/subscription'>User icon > Settings > Subscription</a>.",
|
"subscriptionAlreadySubscribed1": "To see your subscription details and cancel, renew, or change your subscription, please go to <a href='/user/settings/subscription'>User icon > Settings > Subscription</a>.",
|
||||||
"purchaseAll": "Purchase All",
|
"purchaseAll": "Purchase Set",
|
||||||
"gemsPurchaseNote": "Subscribers can buy gems for gold in the Market! For easy access, you can also pin the gem to your Rewards column.",
|
"gemsPurchaseNote": "Subscribers can buy gems for gold in the Market! For easy access, you can also pin the gem to your Rewards column.",
|
||||||
"gemsRemaining": "gems remaining",
|
"gemsRemaining": "gems remaining",
|
||||||
"notEnoughGemsToBuy": "You are unable to buy that amount of gems"
|
"notEnoughGemsToBuy": "You are unable to buy that amount of gems"
|
||||||
|
|||||||
Reference in New Issue
Block a user