forked from AkkomaGang/akkoma-fe
Merge remote-tracking branch 'upstream/develop' into feature/theming2
* upstream/develop: Fix color fallback order Use console.warn instead of console.log Get rid of mutation_types file, use inline approach. Minor fixes Add fallback color rule. Change english validation error messages Clean up the code Validate name presence on client-side as well Better styling for client-side validation. Add I18n for validation errors. Fix broken ToS link. Fix linter errors Add client validation for registration form Use Array.reduce instead of lodash.reduce Humanize validation errors returned on registration Added user option to hide instance-specific panel, rearranged config screen to better categorize it / adjustments to language selector fix
This commit is contained in:
commit
3452864260
14 changed files with 271 additions and 1056 deletions
|
@ -31,6 +31,7 @@
|
||||||
"vue-router": "^3.0.1",
|
"vue-router": "^3.0.1",
|
||||||
"vue-template-compiler": "^2.3.4",
|
"vue-template-compiler": "^2.3.4",
|
||||||
"vue-timeago": "^3.1.2",
|
"vue-timeago": "^3.1.2",
|
||||||
|
"vuelidate": "^0.7.4",
|
||||||
"vuex": "^3.0.1",
|
"vuex": "^3.0.1",
|
||||||
"whatwg-fetch": "^2.0.3"
|
"whatwg-fetch": "^2.0.3"
|
||||||
},
|
},
|
||||||
|
|
|
@ -58,6 +58,7 @@ const afterStoreSetup = ({store, i18n}) => {
|
||||||
var loginMethod = (config.loginMethod)
|
var loginMethod = (config.loginMethod)
|
||||||
var scopeCopy = (config.scopeCopy)
|
var scopeCopy = (config.scopeCopy)
|
||||||
var subjectLineBehavior = (config.subjectLineBehavior)
|
var subjectLineBehavior = (config.subjectLineBehavior)
|
||||||
|
var alwaysShowSubjectInput = (config.alwaysShowSubjectInput)
|
||||||
|
|
||||||
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
|
||||||
store.dispatch('setInstanceOption', { name: 'background', value: background })
|
store.dispatch('setInstanceOption', { name: 'background', value: background })
|
||||||
|
@ -75,6 +76,7 @@ const afterStoreSetup = ({store, i18n}) => {
|
||||||
store.dispatch('setInstanceOption', { name: 'loginMethod', value: loginMethod })
|
store.dispatch('setInstanceOption', { name: 'loginMethod', value: loginMethod })
|
||||||
store.dispatch('setInstanceOption', { name: 'scopeCopy', value: scopeCopy })
|
store.dispatch('setInstanceOption', { name: 'scopeCopy', value: scopeCopy })
|
||||||
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
|
||||||
|
store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput })
|
||||||
if (chatDisabled) {
|
if (chatDisabled) {
|
||||||
store.dispatch('disableChat')
|
store.dispatch('disableChat')
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,9 @@ const InstanceSpecificPanel = {
|
||||||
computed: {
|
computed: {
|
||||||
instanceSpecificPanelContent () {
|
instanceSpecificPanelContent () {
|
||||||
return this.$store.state.instance.instanceSpecificPanelContent
|
return this.$store.state.instance.instanceSpecificPanelContent
|
||||||
|
},
|
||||||
|
show () {
|
||||||
|
return !this.$store.state.config.hideISP
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="instance-specific-panel">
|
<div v-if="show" class="instance-specific-panel">
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div v-html="instanceSpecificPanelContent">
|
<div v-html="instanceSpecificPanelContent">
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
|
<label for="interface-language-switcher">
|
||||||
|
{{ $t('settings.interfaceLanguage') }}
|
||||||
|
</label>
|
||||||
<label for="interface-language-switcher" class='select'>
|
<label for="interface-language-switcher" class='select'>
|
||||||
<select id="interface-language-switcher" v-model="language">
|
<select id="interface-language-switcher" v-model="language">
|
||||||
<option v-for="(langCode, i) in languageCodes" :value="langCode">
|
<option v-for="(langCode, i) in languageCodes" :value="langCode">
|
||||||
|
|
|
@ -1,57 +1,61 @@
|
||||||
import oauthApi from '../../services/new_api/oauth.js'
|
import { validationMixin } from 'vuelidate'
|
||||||
|
import { required, sameAs } from 'vuelidate/lib/validators'
|
||||||
|
import { mapActions, mapState } from 'vuex'
|
||||||
|
|
||||||
const registration = {
|
const registration = {
|
||||||
|
mixins: [validationMixin],
|
||||||
data: () => ({
|
data: () => ({
|
||||||
user: {},
|
user: {
|
||||||
error: false,
|
email: '',
|
||||||
registering: false
|
fullname: '',
|
||||||
}),
|
username: '',
|
||||||
created () {
|
password: '',
|
||||||
if ((!this.$store.state.instance.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) {
|
confirm: ''
|
||||||
this.$router.push('/main/all')
|
|
||||||
}
|
}
|
||||||
// Seems like this doesn't work at first page open for some reason
|
}),
|
||||||
if (this.$store.state.instance.registrationOpen && this.token) {
|
validations: {
|
||||||
this.$router.push('/registration')
|
user: {
|
||||||
|
email: { required },
|
||||||
|
username: { required },
|
||||||
|
fullname: { required },
|
||||||
|
password: { required },
|
||||||
|
confirm: {
|
||||||
|
required,
|
||||||
|
sameAsPassword: sameAs('password')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created () {
|
||||||
|
if ((!this.registrationOpen && !this.token) || this.signedIn) {
|
||||||
|
this.$router.push('/main/all')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
termsofservice () { return this.$store.state.instance.tos },
|
token () { return this.$route.params.token },
|
||||||
token () { return this.$route.params.token }
|
...mapState({
|
||||||
|
registrationOpen: (state) => state.instance.registrationOpen,
|
||||||
|
signedIn: (state) => !!state.users.currentUser,
|
||||||
|
isPending: (state) => state.users.signUpPending,
|
||||||
|
serverValidationErrors: (state) => state.users.signUpErrors,
|
||||||
|
termsOfService: (state) => state.instance.tos
|
||||||
|
})
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit () {
|
...mapActions(['signUp']),
|
||||||
this.registering = true
|
async submit () {
|
||||||
this.user.nickname = this.user.username
|
this.user.nickname = this.user.username
|
||||||
this.user.token = this.token
|
this.user.token = this.token
|
||||||
this.$store.state.api.backendInteractor.register(this.user).then(
|
|
||||||
(response) => {
|
this.$v.$touch()
|
||||||
if (response.ok) {
|
|
||||||
const data = {
|
if (!this.$v.$invalid) {
|
||||||
oauth: this.$store.state.oauth,
|
try {
|
||||||
instance: this.$store.state.instance.server
|
await this.signUp(this.user)
|
||||||
}
|
this.$router.push('/main/friends')
|
||||||
oauthApi.getOrCreateApp(data).then((app) => {
|
} catch (error) {
|
||||||
oauthApi.getTokenWithCredentials(
|
console.warn('Registration failed: ' + error)
|
||||||
{
|
|
||||||
app,
|
|
||||||
instance: data.instance,
|
|
||||||
username: this.user.username,
|
|
||||||
password: this.user.password})
|
|
||||||
.then((result) => {
|
|
||||||
this.$store.commit('setToken', result.access_token)
|
|
||||||
this.$store.dispatch('loginUser', result.access_token)
|
|
||||||
this.$router.push('/main/friends')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
this.registering = false
|
|
||||||
response.json().then((data) => {
|
|
||||||
this.error = data.error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,50 +7,90 @@
|
||||||
<form v-on:submit.prevent='submit(user)' class='registration-form'>
|
<form v-on:submit.prevent='submit(user)' class='registration-form'>
|
||||||
<div class='container'>
|
<div class='container'>
|
||||||
<div class='text-fields'>
|
<div class='text-fields'>
|
||||||
<div class='form-group'>
|
<div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
|
||||||
<label for='username'>{{$t('login.username')}}</label>
|
<label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
|
||||||
<input :disabled="registering" v-model='user.username' class='form-control' id='username' placeholder='e.g. lain'>
|
<input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class="form-error" v-if="$v.user.username.$dirty">
|
||||||
<label for='fullname'>{{$t('registration.fullname')}}</label>
|
<ul>
|
||||||
<input :disabled="registering" v-model='user.fullname' class='form-control' id='fullname' placeholder='e.g. Lain Iwakura'>
|
<li v-if="!$v.user.username.required">
|
||||||
|
<span>{{$t('registration.validations.username_required')}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
|
||||||
<label for='email'>{{$t('registration.email')}}</label>
|
<div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
|
||||||
<input :disabled="registering" v-model='user.email' class='form-control' id='email' type="email">
|
<label class='form--label' for='sign-up-fullname'>{{$t('registration.fullname')}}</label>
|
||||||
|
<input :disabled="isPending" v-model.trim='$v.user.fullname.$model' class='form-control' id='sign-up-fullname' placeholder='e.g. Lain Iwakura'>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class="form-error" v-if="$v.user.fullname.$dirty">
|
||||||
<label for='bio'>{{$t('registration.bio')}}</label>
|
<ul>
|
||||||
<input :disabled="registering" v-model='user.bio' class='form-control' id='bio'>
|
<li v-if="!$v.user.fullname.required">
|
||||||
|
<span>{{$t('registration.validations.fullname_required')}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
|
||||||
<label for='password'>{{$t('login.password')}}</label>
|
<div class='form-group' :class="{ 'form-group--error': $v.user.email.$error }">
|
||||||
<input :disabled="registering" v-model='user.password' class='form-control' id='password' type='password'>
|
<label class='form--label' for='email'>{{$t('registration.email')}}</label>
|
||||||
|
<input :disabled="isPending" v-model='$v.user.email.$model' class='form-control' id='email' type="email">
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class="form-error" v-if="$v.user.email.$dirty">
|
||||||
<label for='password_confirmation'>{{$t('registration.password_confirm')}}</label>
|
<ul>
|
||||||
<input :disabled="registering" v-model='user.confirm' class='form-control' id='password_confirmation' type='password'>
|
<li v-if="!$v.user.email.required">
|
||||||
|
<span>{{$t('registration.validations.email_required')}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!--
|
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<label for='captcha'>Captcha</label>
|
<label class='form--label' for='bio'>{{$t('registration.bio')}}</label>
|
||||||
<img src='/qvittersimplesecurity/captcha.jpg' alt='captcha' class='captcha'>
|
<input :disabled="isPending" v-model='user.bio' class='form-control' id='bio'>
|
||||||
<input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'>
|
|
||||||
</div>
|
</div>
|
||||||
-->
|
|
||||||
|
<div class='form-group' :class="{ 'form-group--error': $v.user.password.$error }">
|
||||||
|
<label class='form--label' for='sign-up-password'>{{$t('login.password')}}</label>
|
||||||
|
<input :disabled="isPending" v-model='user.password' class='form-control' id='sign-up-password' type='password'>
|
||||||
|
</div>
|
||||||
|
<div class="form-error" v-if="$v.user.password.$dirty">
|
||||||
|
<ul>
|
||||||
|
<li v-if="!$v.user.password.required">
|
||||||
|
<span>{{$t('registration.validations.password_required')}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class='form-group' :class="{ 'form-group--error': $v.user.confirm.$error }">
|
||||||
|
<label class='form--label' for='sign-up-password-confirmation'>{{$t('registration.password_confirm')}}</label>
|
||||||
|
<input :disabled="isPending" v-model='user.confirm' class='form-control' id='sign-up-password-confirmation' type='password'>
|
||||||
|
</div>
|
||||||
|
<div class="form-error" v-if="$v.user.confirm.$dirty">
|
||||||
|
<ul>
|
||||||
|
<li v-if="!$v.user.confirm.required">
|
||||||
|
<span>{{$t('registration.validations.password_confirmation_required')}}</span>
|
||||||
|
</li>
|
||||||
|
<li v-if="!$v.user.confirm.sameAsPassword">
|
||||||
|
<span>{{$t('registration.validations.password_confirmation_match')}}</span>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class='form-group' v-if='token' >
|
<div class='form-group' v-if='token' >
|
||||||
<label for='token'>{{$t('registration.token')}}</label>
|
<label for='token'>{{$t('registration.token')}}</label>
|
||||||
<input disabled='true' v-model='token' class='form-control' id='token' type='text'>
|
<input disabled='true' v-model='token' class='form-control' id='token' type='text'>
|
||||||
</div>
|
</div>
|
||||||
<div class='form-group'>
|
<div class='form-group'>
|
||||||
<button :disabled="registering" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
|
<button :disabled="isPending" type='submit' class='btn btn-default'>{{$t('general.submit')}}</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class='terms-of-service' v-html="termsofservice">
|
|
||||||
|
<div class='terms-of-service' v-html="termsOfService">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="error" class='form-group'>
|
<div v-if="serverValidationErrors.length" class='form-group'>
|
||||||
<div class='alert error'>{{error}}</div>
|
<div class='alert error'>
|
||||||
|
<span v-for="error in serverValidationErrors">{{error}}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
@ -60,6 +100,7 @@
|
||||||
<script src="./registration.js"></script>
|
<script src="./registration.js"></script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
@import '../../_variables.scss';
|
@import '../../_variables.scss';
|
||||||
|
$validations-cRed: #f04124;
|
||||||
|
|
||||||
.registration-form {
|
.registration-form {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -89,6 +130,55 @@
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
padding: 0.3em 0.0em 0.3em;
|
padding: 0.3em 0.0em 0.3em;
|
||||||
line-height:24px;
|
line-height:24px;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes shakeError {
|
||||||
|
0% {
|
||||||
|
transform: translateX(0); }
|
||||||
|
15% {
|
||||||
|
transform: translateX(0.375rem); }
|
||||||
|
30% {
|
||||||
|
transform: translateX(-0.375rem); }
|
||||||
|
45% {
|
||||||
|
transform: translateX(0.375rem); }
|
||||||
|
60% {
|
||||||
|
transform: translateX(-0.375rem); }
|
||||||
|
75% {
|
||||||
|
transform: translateX(0.375rem); }
|
||||||
|
90% {
|
||||||
|
transform: translateX(-0.375rem); }
|
||||||
|
100% {
|
||||||
|
transform: translateX(0); } }
|
||||||
|
|
||||||
|
.form-group--error {
|
||||||
|
animation-name: shakeError;
|
||||||
|
animation-duration: .6s;
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group--error .form--label {
|
||||||
|
color: $validations-cRed;
|
||||||
|
color: var(--cRed, $validations-cRed);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-error {
|
||||||
|
margin-top: -0.7em;
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
span {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-error ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0 0 0 5px;
|
||||||
|
margin-top: 0;
|
||||||
|
|
||||||
|
li::before {
|
||||||
|
content: "• ";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
form textarea {
|
form textarea {
|
||||||
|
@ -102,8 +192,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
//align-self: flex-start;
|
|
||||||
//width: 10em;
|
|
||||||
margin-top: 0.6em;
|
margin-top: 0.6em;
|
||||||
height: 28px;
|
height: 28px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ const settings = {
|
||||||
hideAttachmentsLocal: user.hideAttachments,
|
hideAttachmentsLocal: user.hideAttachments,
|
||||||
hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
|
hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
|
||||||
hideNsfwLocal: user.hideNsfw,
|
hideNsfwLocal: user.hideNsfw,
|
||||||
|
hideISPLocal: user.hideISP,
|
||||||
hidePostStatsLocal: typeof user.hidePostStats === 'undefined'
|
hidePostStatsLocal: typeof user.hidePostStats === 'undefined'
|
||||||
? instance.hidePostStats
|
? instance.hidePostStats
|
||||||
: user.hidePostStats,
|
: user.hidePostStats,
|
||||||
|
@ -83,6 +84,9 @@ const settings = {
|
||||||
hideNsfwLocal (value) {
|
hideNsfwLocal (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
|
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
|
||||||
},
|
},
|
||||||
|
hideISPLocal (value) {
|
||||||
|
this.$store.dispatch('setOption', { name: 'hideISP', value })
|
||||||
|
},
|
||||||
'notificationVisibilityLocal.likes' (value) {
|
'notificationVisibilityLocal.likes' (value) {
|
||||||
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
|
<div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
|
||||||
{{ $t('settings.saving_ok') }}
|
{{ $t('settings.saving_ok') }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</transition>
|
</transition>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
|
@ -22,8 +22,16 @@
|
||||||
<tab-switcher>
|
<tab-switcher>
|
||||||
<div :label="$t('settings.general')" >
|
<div :label="$t('settings.general')" >
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{ $t('settings.interfaceLanguage') }}</h2>
|
<h2>{{ $t('settings.interface') }}</h2>
|
||||||
<interface-language-switcher />
|
<ul class="setting-list">
|
||||||
|
<li>
|
||||||
|
<interface-language-switcher />
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<input type="checkbox" id="hideISP" v-model="hideISPLocal">
|
||||||
|
<label for="hideISP">{{$t('settings.hide_isp')}}</label>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div class="setting-item">
|
<div class="setting-item">
|
||||||
<h2>{{$t('nav.timeline')}}</h2>
|
<h2>{{$t('nav.timeline')}}</h2>
|
||||||
|
|
|
@ -72,7 +72,15 @@
|
||||||
"fullname": "Display name",
|
"fullname": "Display name",
|
||||||
"password_confirm": "Password confirmation",
|
"password_confirm": "Password confirmation",
|
||||||
"registration": "Registration",
|
"registration": "Registration",
|
||||||
"token": "Invite token"
|
"token": "Invite token",
|
||||||
|
"validations": {
|
||||||
|
"username_required": "cannot be left blank",
|
||||||
|
"fullname_required": "cannot be left blank",
|
||||||
|
"email_required": "cannot be left blank",
|
||||||
|
"password_required": "cannot be left blank",
|
||||||
|
"password_confirmation_required": "cannot be left blank",
|
||||||
|
"password_confirmation_match": "should be the same as password"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"attachmentRadius": "Attachments",
|
"attachmentRadius": "Attachments",
|
||||||
|
@ -116,6 +124,7 @@
|
||||||
"general": "General",
|
"general": "General",
|
||||||
"hide_attachments_in_convo": "Hide attachments in conversations",
|
"hide_attachments_in_convo": "Hide attachments in conversations",
|
||||||
"hide_attachments_in_tl": "Hide attachments in timeline",
|
"hide_attachments_in_tl": "Hide attachments in timeline",
|
||||||
|
"hide_isp": "Hide instance-specific panel",
|
||||||
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
"hide_post_stats": "Hide post statistics (e.g. the number of favorites)",
|
||||||
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
"hide_user_stats": "Hide user statistics (e.g. the number of followers)",
|
||||||
"import_followers_from_a_csv_file": "Import follows from a csv file",
|
"import_followers_from_a_csv_file": "Import follows from a csv file",
|
||||||
|
@ -124,6 +133,7 @@
|
||||||
"checkboxRadius": "Checkboxes",
|
"checkboxRadius": "Checkboxes",
|
||||||
"instance_default": "(default: {value})",
|
"instance_default": "(default: {value})",
|
||||||
"instance_default_simple" : "(default)",
|
"instance_default_simple" : "(default)",
|
||||||
|
"interface": "Interface",
|
||||||
"interfaceLanguage": "Interface language",
|
"interfaceLanguage": "Interface language",
|
||||||
"invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
|
"invalid_theme_imported": "The selected file is not a supported Pleroma theme. No changes to your theme were made.",
|
||||||
"limited_availability": "Unavailable in your browser",
|
"limited_availability": "Unavailable in your browser",
|
||||||
|
|
|
@ -55,7 +55,15 @@
|
||||||
"fullname": "Отображаемое имя",
|
"fullname": "Отображаемое имя",
|
||||||
"password_confirm": "Подтверждение пароля",
|
"password_confirm": "Подтверждение пароля",
|
||||||
"registration": "Регистрация",
|
"registration": "Регистрация",
|
||||||
"token": "Код приглашения"
|
"token": "Код приглашения",
|
||||||
|
"validations": {
|
||||||
|
"username_required": "не должно быть пустым",
|
||||||
|
"fullname_required": "не должно быть пустым",
|
||||||
|
"email_required": "не должен быть пустым",
|
||||||
|
"password_required": "не должен быть пустым",
|
||||||
|
"password_confirmation_required": "не должно быть пустым",
|
||||||
|
"password_confirmation_match": "должно совпадать с паролем"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"attachmentRadius": "Прикреплённые файлы",
|
"attachmentRadius": "Прикреплённые файлы",
|
||||||
|
@ -97,10 +105,12 @@
|
||||||
"general": "Общие",
|
"general": "Общие",
|
||||||
"hide_attachments_in_convo": "Прятать вложения в разговорах",
|
"hide_attachments_in_convo": "Прятать вложения в разговорах",
|
||||||
"hide_attachments_in_tl": "Прятать вложения в ленте",
|
"hide_attachments_in_tl": "Прятать вложения в ленте",
|
||||||
|
"hide_isp": "Скрыть серверную панель",
|
||||||
"import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
|
"import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
|
||||||
"import_theme": "Загрузить Тему",
|
"import_theme": "Загрузить Тему",
|
||||||
"inputRadius": "Поля ввода",
|
"inputRadius": "Поля ввода",
|
||||||
"checkboxRadius": "Чекбоксы",
|
"checkboxRadius": "Чекбоксы",
|
||||||
|
"interface": "Интерфейс",
|
||||||
"interfaceLanguage": "Язык интерфейса",
|
"interfaceLanguage": "Язык интерфейса",
|
||||||
"limited_availability": "Не доступно в вашем браузере",
|
"limited_availability": "Не доступно в вашем браузере",
|
||||||
"links": "Ссылки",
|
"links": "Ссылки",
|
||||||
|
|
12
src/modules/errors.js
Normal file
12
src/modules/errors.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
import { capitalize } from 'lodash'
|
||||||
|
|
||||||
|
export function humanizeErrors (errors) {
|
||||||
|
return Object.entries(errors).reduce((errs, [k, val]) => {
|
||||||
|
let message = val.reduce((acc, message) => {
|
||||||
|
let key = capitalize(k.replace(/_/g, ' '))
|
||||||
|
return acc + [key, message].join(' ') + '. '
|
||||||
|
}, '')
|
||||||
|
return [...errs, message]
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
|
||||||
import { compact, map, each, merge } from 'lodash'
|
import { compact, map, each, merge } from 'lodash'
|
||||||
import { set } from 'vue'
|
import { set } from 'vue'
|
||||||
|
import oauthApi from '../services/new_api/oauth'
|
||||||
|
import {humanizeErrors} from './errors'
|
||||||
|
|
||||||
// TODO: Unify with mergeOrAdd in statuses.js
|
// TODO: Unify with mergeOrAdd in statuses.js
|
||||||
export const mergeOrAdd = (arr, obj, item) => {
|
export const mergeOrAdd = (arr, obj, item) => {
|
||||||
|
@ -46,15 +48,28 @@ export const mutations = {
|
||||||
setColor (state, { user: {id}, highlighted }) {
|
setColor (state, { user: {id}, highlighted }) {
|
||||||
const user = state.usersObject[id]
|
const user = state.usersObject[id]
|
||||||
set(user, 'highlight', highlighted)
|
set(user, 'highlight', highlighted)
|
||||||
|
},
|
||||||
|
signUpPending (state) {
|
||||||
|
state.signUpPending = true
|
||||||
|
state.signUpErrors = []
|
||||||
|
},
|
||||||
|
signUpSuccess (state) {
|
||||||
|
state.signUpPending = false
|
||||||
|
},
|
||||||
|
signUpFailure (state, errors) {
|
||||||
|
state.signUpPending = false
|
||||||
|
state.signUpErrors = errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultState = {
|
export const defaultState = {
|
||||||
|
loggingIn: false,
|
||||||
lastLoginName: false,
|
lastLoginName: false,
|
||||||
currentUser: false,
|
currentUser: false,
|
||||||
loggingIn: false,
|
|
||||||
users: [],
|
users: [],
|
||||||
usersObject: {}
|
usersObject: {},
|
||||||
|
signUpPending: false,
|
||||||
|
signUpErrors: []
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = {
|
const users = {
|
||||||
|
@ -80,6 +95,34 @@ const users = {
|
||||||
store.commit('setUserForStatus', status)
|
store.commit('setUserForStatus', status)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
async signUp (store, userInfo) {
|
||||||
|
store.commit('signUpPending')
|
||||||
|
|
||||||
|
let rootState = store.rootState
|
||||||
|
|
||||||
|
let response = await rootState.api.backendInteractor.register(userInfo)
|
||||||
|
if (response.ok) {
|
||||||
|
const data = {
|
||||||
|
oauth: rootState.oauth,
|
||||||
|
instance: rootState.instance.server
|
||||||
|
}
|
||||||
|
let app = await oauthApi.getOrCreateApp(data)
|
||||||
|
let result = await oauthApi.getTokenWithCredentials({
|
||||||
|
app,
|
||||||
|
instance: data.instance,
|
||||||
|
username: userInfo.username,
|
||||||
|
password: userInfo.password
|
||||||
|
})
|
||||||
|
store.commit('signUpSuccess')
|
||||||
|
store.commit('setToken', result.access_token)
|
||||||
|
store.dispatch('loginUser', result.access_token)
|
||||||
|
} else {
|
||||||
|
let data = await response.json()
|
||||||
|
let errors = humanizeErrors(JSON.parse(data.error))
|
||||||
|
store.commit('signUpFailure', errors)
|
||||||
|
throw Error(errors)
|
||||||
|
}
|
||||||
|
},
|
||||||
logout (store) {
|
logout (store) {
|
||||||
store.commit('clearCurrentUser')
|
store.commit('clearCurrentUser')
|
||||||
store.commit('setToken', false)
|
store.commit('setToken', false)
|
||||||
|
|
Loading…
Reference in a new issue