aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorHenry Jameson <me@hjkos.com>2018-12-11 01:01:16 +0300
committerHenry Jameson <me@hjkos.com>2018-12-11 01:01:16 +0300
commit3452864260d8a53afc839fb1265946ebfbd80cee (patch)
tree73354d7eb98c73ef2330cb63b73dc127802bd1e4 /src
parentaeecd2b09b7c31644a2c601fc1b8d123e2b263b0 (diff)
parentfb5261b926adfb5b9bbe1bf55e36fe8b5f4eb57f (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/boot/after_store.js2
-rw-r--r--src/components/instance_specific_panel/instance_specific_panel.js3
-rw-r--r--src/components/instance_specific_panel/instance_specific_panel.vue2
-rw-r--r--src/components/interface_language_switcher/interface_language_switcher.vue3
-rw-r--r--src/components/registration/registration.js84
-rw-r--r--src/components/registration/registration.vue146
-rw-r--r--src/components/settings/settings.js4
-rw-r--r--src/components/settings/settings.vue14
-rw-r--r--src/i18n/en.json12
-rw-r--r--src/i18n/ru.json12
-rw-r--r--src/modules/errors.js12
-rw-r--r--src/modules/users.js47
12 files changed, 264 insertions, 77 deletions
diff --git a/src/boot/after_store.js b/src/boot/after_store.js
index ea5d4ecd..a80baaf5 100644
--- a/src/boot/after_store.js
+++ b/src/boot/after_store.js
@@ -58,6 +58,7 @@ const afterStoreSetup = ({store, i18n}) => {
var loginMethod = (config.loginMethod)
var scopeCopy = (config.scopeCopy)
var subjectLineBehavior = (config.subjectLineBehavior)
+ var alwaysShowSubjectInput = (config.alwaysShowSubjectInput)
store.dispatch('setInstanceOption', { name: 'theme', value: theme })
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: 'scopeCopy', value: scopeCopy })
store.dispatch('setInstanceOption', { name: 'subjectLineBehavior', value: subjectLineBehavior })
+ store.dispatch('setInstanceOption', { name: 'alwaysShowSubjectInput', value: alwaysShowSubjectInput })
if (chatDisabled) {
store.dispatch('disableChat')
}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.js b/src/components/instance_specific_panel/instance_specific_panel.js
index 09e3d055..9bb5e945 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.js
+++ b/src/components/instance_specific_panel/instance_specific_panel.js
@@ -2,6 +2,9 @@ const InstanceSpecificPanel = {
computed: {
instanceSpecificPanelContent () {
return this.$store.state.instance.instanceSpecificPanelContent
+ },
+ show () {
+ return !this.$store.state.config.hideISP
}
}
}
diff --git a/src/components/instance_specific_panel/instance_specific_panel.vue b/src/components/instance_specific_panel/instance_specific_panel.vue
index ca8e00c0..a7b74667 100644
--- a/src/components/instance_specific_panel/instance_specific_panel.vue
+++ b/src/components/instance_specific_panel/instance_specific_panel.vue
@@ -1,5 +1,5 @@
<template>
- <div class="instance-specific-panel">
+ <div v-if="show" class="instance-specific-panel">
<div class="panel panel-default">
<div class="panel-body">
<div v-html="instanceSpecificPanelContent">
diff --git a/src/components/interface_language_switcher/interface_language_switcher.vue b/src/components/interface_language_switcher/interface_language_switcher.vue
index 4b541888..3f58af2c 100644
--- a/src/components/interface_language_switcher/interface_language_switcher.vue
+++ b/src/components/interface_language_switcher/interface_language_switcher.vue
@@ -1,5 +1,8 @@
<template>
<div>
+ <label for="interface-language-switcher">
+ {{ $t('settings.interfaceLanguage') }}
+ </label>
<label for="interface-language-switcher" class='select'>
<select id="interface-language-switcher" v-model="language">
<option v-for="(langCode, i) in languageCodes" :value="langCode">
diff --git a/src/components/registration/registration.js b/src/components/registration/registration.js
index f7f8a720..e5ead8bc 100644
--- a/src/components/registration/registration.js
+++ b/src/components/registration/registration.js
@@ -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 = {
+ mixins: [validationMixin],
data: () => ({
- user: {},
- error: false,
- registering: false
+ user: {
+ email: '',
+ fullname: '',
+ username: '',
+ password: '',
+ confirm: ''
+ }
}),
+ validations: {
+ user: {
+ email: { required },
+ username: { required },
+ fullname: { required },
+ password: { required },
+ confirm: {
+ required,
+ sameAsPassword: sameAs('password')
+ }
+ }
+ },
created () {
- if ((!this.$store.state.instance.registrationOpen && !this.token) || !!this.$store.state.users.currentUser) {
+ if ((!this.registrationOpen && !this.token) || this.signedIn) {
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) {
- this.$router.push('/registration')
- }
},
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: {
- submit () {
- this.registering = true
+ ...mapActions(['signUp']),
+ async submit () {
this.user.nickname = this.user.username
this.user.token = this.token
- this.$store.state.api.backendInteractor.register(this.user).then(
- (response) => {
- if (response.ok) {
- const data = {
- oauth: this.$store.state.oauth,
- instance: this.$store.state.instance.server
- }
- oauthApi.getOrCreateApp(data).then((app) => {
- oauthApi.getTokenWithCredentials(
- {
- 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
- })
- }
+
+ this.$v.$touch()
+
+ if (!this.$v.$invalid) {
+ try {
+ await this.signUp(this.user)
+ this.$router.push('/main/friends')
+ } catch (error) {
+ console.warn('Registration failed: ' + error)
}
- )
+ }
}
}
}
diff --git a/src/components/registration/registration.vue b/src/components/registration/registration.vue
index 087cab6b..8cb1392b 100644
--- a/src/components/registration/registration.vue
+++ b/src/components/registration/registration.vue
@@ -7,50 +7,90 @@
<form v-on:submit.prevent='submit(user)' class='registration-form'>
<div class='container'>
<div class='text-fields'>
- <div class='form-group'>
- <label for='username'>{{$t('login.username')}}</label>
- <input :disabled="registering" v-model='user.username' class='form-control' id='username' placeholder='e.g. lain'>
+ <div class='form-group' :class="{ 'form-group--error': $v.user.username.$error }">
+ <label class='form--label' for='sign-up-username'>{{$t('login.username')}}</label>
+ <input :disabled="isPending" v-model.trim='$v.user.username.$model' class='form-control' id='sign-up-username' placeholder='e.g. lain'>
</div>
- <div class='form-group'>
- <label for='fullname'>{{$t('registration.fullname')}}</label>
- <input :disabled="registering" v-model='user.fullname' class='form-control' id='fullname' placeholder='e.g. Lain Iwakura'>
+ <div class="form-error" v-if="$v.user.username.$dirty">
+ <ul>
+ <li v-if="!$v.user.username.required">
+ <span>{{$t('registration.validations.username_required')}}</span>
+ </li>
+ </ul>
</div>
- <div class='form-group'>
- <label for='email'>{{$t('registration.email')}}</label>
- <input :disabled="registering" v-model='user.email' class='form-control' id='email' type="email">
+
+ <div class='form-group' :class="{ 'form-group--error': $v.user.fullname.$error }">
+ <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 class='form-group'>
- <label for='bio'>{{$t('registration.bio')}}</label>
- <input :disabled="registering" v-model='user.bio' class='form-control' id='bio'>
+ <div class="form-error" v-if="$v.user.fullname.$dirty">
+ <ul>
+ <li v-if="!$v.user.fullname.required">
+ <span>{{$t('registration.validations.fullname_required')}}</span>
+ </li>
+ </ul>
</div>
- <div class='form-group'>
- <label for='password'>{{$t('login.password')}}</label>
- <input :disabled="registering" v-model='user.password' class='form-control' id='password' type='password'>
+
+ <div class='form-group' :class="{ 'form-group--error': $v.user.email.$error }">
+ <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 class='form-group'>
- <label for='password_confirmation'>{{$t('registration.password_confirm')}}</label>
- <input :disabled="registering" v-model='user.confirm' class='form-control' id='password_confirmation' type='password'>
+ <div class="form-error" v-if="$v.user.email.$dirty">
+ <ul>
+ <li v-if="!$v.user.email.required">
+ <span>{{$t('registration.validations.email_required')}}</span>
+ </li>
+ </ul>
</div>
- <!--
+
<div class='form-group'>
- <label for='captcha'>Captcha</label>
- <img src='/qvittersimplesecurity/captcha.jpg' alt='captcha' class='captcha'>
- <input :disabled="registering" v-model='user.captcha' placeholder='Enter captcha' type='test' class='form-control' id='captcha'>
+ <label class='form--label' for='bio'>{{$t('registration.bio')}}</label>
+ <input :disabled="isPending" v-model='user.bio' class='form-control' id='bio'>
+ </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' >
<label for='token'>{{$t('registration.token')}}</label>
<input disabled='true' v-model='token' class='form-control' id='token' type='text'>
</div>
<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 class='terms-of-service' v-html="termsofservice">
+
+ <div class='terms-of-service' v-html="termsOfService">
</div>
</div>
- <div v-if="error" class='form-group'>
- <div class='alert error'>{{error}}</div>
+ <div v-if="serverValidationErrors.length" class='form-group'>
+ <div class='alert error'>
+ <span v-for="error in serverValidationErrors">{{error}}</span>
+ </div>
</div>
</form>
</div>
@@ -60,6 +100,7 @@
<script src="./registration.js"></script>
<style lang="scss">
@import '../../_variables.scss';
+$validations-cRed: #f04124;
.registration-form {
display: flex;
@@ -89,6 +130,55 @@
flex-direction: column;
padding: 0.3em 0.0em 0.3em;
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 {
@@ -102,8 +192,6 @@
}
.btn {
- //align-self: flex-start;
- //width: 10em;
margin-top: 0.6em;
height: 28px;
}
diff --git a/src/components/settings/settings.js b/src/components/settings/settings.js
index 91a2014a..19bd2e5b 100644
--- a/src/components/settings/settings.js
+++ b/src/components/settings/settings.js
@@ -13,6 +13,7 @@ const settings = {
hideAttachmentsLocal: user.hideAttachments,
hideAttachmentsInConvLocal: user.hideAttachmentsInConv,
hideNsfwLocal: user.hideNsfw,
+ hideISPLocal: user.hideISP,
hidePostStatsLocal: typeof user.hidePostStats === 'undefined'
? instance.hidePostStats
: user.hidePostStats,
@@ -83,6 +84,9 @@ const settings = {
hideNsfwLocal (value) {
this.$store.dispatch('setOption', { name: 'hideNsfw', value })
},
+ hideISPLocal (value) {
+ this.$store.dispatch('setOption', { name: 'hideISP', value })
+ },
'notificationVisibilityLocal.likes' (value) {
this.$store.dispatch('setOption', { name: 'notificationVisibility', value: this.$store.state.config.notificationVisibility })
},
diff --git a/src/components/settings/settings.vue b/src/components/settings/settings.vue
index 64b8f231..dec33505 100644
--- a/src/components/settings/settings.vue
+++ b/src/components/settings/settings.vue
@@ -14,7 +14,7 @@
<div @click.prevent class="alert transparent" v-if="!currentSaveStateNotice.error">
{{ $t('settings.saving_ok') }}
</div>
- </template>
+ </template>
</transition>
</div>
<div class="panel-body">
@@ -22,8 +22,16 @@
<tab-switcher>
<div :label="$t('settings.general')" >
<div class="setting-item">
- <h2>{{ $t('settings.interfaceLanguage') }}</h2>
- <interface-language-switcher />
+ <h2>{{ $t('settings.interface') }}</h2>
+ <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 class="setting-item">
<h2>{{$t('nav.timeline')}}</h2>
diff --git a/src/i18n/en.json b/src/i18n/en.json
index c0d30b59..a1fc43aa 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -72,7 +72,15 @@
"fullname": "Display name",
"password_confirm": "Password confirmation",
"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": {
"attachmentRadius": "Attachments",
@@ -116,6 +124,7 @@
"general": "General",
"hide_attachments_in_convo": "Hide attachments in conversations",
"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_user_stats": "Hide user statistics (e.g. the number of followers)",
"import_followers_from_a_csv_file": "Import follows from a csv file",
@@ -124,6 +133,7 @@
"checkboxRadius": "Checkboxes",
"instance_default": "(default: {value})",
"instance_default_simple" : "(default)",
+ "interface": "Interface",
"interfaceLanguage": "Interface language",
"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",
diff --git a/src/i18n/ru.json b/src/i18n/ru.json
index 391fe797..438e0608 100644
--- a/src/i18n/ru.json
+++ b/src/i18n/ru.json
@@ -55,7 +55,15 @@
"fullname": "Отображаемое имя",
"password_confirm": "Подтверждение пароля",
"registration": "Регистрация",
- "token": "Код приглашения"
+ "token": "Код приглашения",
+ "validations": {
+ "username_required": "не должно быть пустым",
+ "fullname_required": "не должно быть пустым",
+ "email_required": "не должен быть пустым",
+ "password_required": "не должен быть пустым",
+ "password_confirmation_required": "не должно быть пустым",
+ "password_confirmation_match": "должно совпадать с паролем"
+ }
},
"settings": {
"attachmentRadius": "Прикреплённые файлы",
@@ -97,10 +105,12 @@
"general": "Общие",
"hide_attachments_in_convo": "Прятать вложения в разговорах",
"hide_attachments_in_tl": "Прятать вложения в ленте",
+ "hide_isp": "Скрыть серверную панель",
"import_followers_from_a_csv_file": "Импортировать читаемых из файла .csv",
"import_theme": "Загрузить Тему",
"inputRadius": "Поля ввода",
"checkboxRadius": "Чекбоксы",
+ "interface": "Интерфейс",
"interfaceLanguage": "Язык интерфейса",
"limited_availability": "Не доступно в вашем браузере",
"links": "Ссылки",
diff --git a/src/modules/errors.js b/src/modules/errors.js
new file mode 100644
index 00000000..c809e1b5
--- /dev/null
+++ b/src/modules/errors.js
@@ -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]
+ }, [])
+}
+
diff --git a/src/modules/users.js b/src/modules/users.js
index 8630ee0d..6d966c3b 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -1,6 +1,8 @@
import backendInteractorService from '../services/backend_interactor_service/backend_interactor_service.js'
import { compact, map, each, merge } from 'lodash'
import { set } from 'vue'
+import oauthApi from '../services/new_api/oauth'
+import {humanizeErrors} from './errors'
// TODO: Unify with mergeOrAdd in statuses.js
export const mergeOrAdd = (arr, obj, item) => {
@@ -46,15 +48,28 @@ export const mutations = {
setColor (state, { user: {id}, highlighted }) {
const user = state.usersObject[id]
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 = {
+ loggingIn: false,
lastLoginName: false,
currentUser: false,
- loggingIn: false,
users: [],
- usersObject: {}
+ usersObject: {},
+ signUpPending: false,
+ signUpErrors: []
}
const users = {
@@ -80,6 +95,34 @@ const users = {
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) {
store.commit('clearCurrentUser')
store.commit('setToken', false)