feat: add message component (#1082)

This PR adds a simple message component that replaces bulma's default message.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/1082
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
This commit is contained in:
konrad 2021-11-28 14:18:27 +00:00 committed by dpschen
parent 59e915cc10
commit f8d009a6aa
23 changed files with 170 additions and 127 deletions

View file

@ -8,7 +8,7 @@ const testAndAssertFailed = fixture => {
cy.wait(5000) // It can take waaaayy too long to log the user in cy.wait(5000) // It can take waaaayy too long to log the user in
cy.url().should('include', '/') cy.url().should('include', '/')
cy.get('div.notification.is-danger').contains('Wrong username or password.') cy.get('div.message.danger').contains('Wrong username or password.')
} }
context('Login', () => { context('Login', () => {

View file

@ -32,7 +32,7 @@ context('Registration', () => {
cy.get('h2').should('contain', `Hi ${fixture.username}!`) cy.get('h2').should('contain', `Hi ${fixture.username}!`)
}) })
it('Should fail', () => { it.only('Should fail', () => {
const fixture = { const fixture = {
username: 'test', username: 'test',
password: '123456', password: '123456',
@ -45,6 +45,6 @@ context('Registration', () => {
cy.get('#password').type(fixture.password) cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password) cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click() cy.get('#register-submit').click()
cy.get('div.notification.is-danger').contains('A user with this username already exists.') cy.get('div.message.danger').contains('A user with this username already exists.')
}) })
}) })

View file

@ -130,7 +130,10 @@
"ignorePatterns": [ "ignorePatterns": [
"*.test.*", "*.test.*",
"cypress/*" "cypress/*"
] ],
"globals": {
"defineProps": "readonly"
}
}, },
"postcss": { "postcss": {
"plugins": { "plugins": {

View file

@ -30,27 +30,25 @@
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a> <a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
</div> </div>
<div <message variant="success" v-if="successMsg !== '' && errorMsg === ''" class="mt-2">
class="notification is-success mt-2"
v-if="successMsg !== '' && errorMsg === ''"
>
{{ successMsg }} {{ successMsg }}
</div> </message>
<div <message variant="danger" v-if="errorMsg !== '' && successMsg === ''" class="mt-2">
class="notification is-danger mt-2"
v-if="errorMsg !== '' && successMsg === ''"
>
{{ errorMsg }} {{ errorMsg }}
</div> </message>
</div> </div>
</template> </template>
<script> <script>
import Message from '@/components/misc/message'
import {parseURL} from 'ufo' import {parseURL} from 'ufo'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl' import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
export default { export default {
name: 'apiConfig', name: 'apiConfig',
components: {
Message,
},
data() { data() {
return { return {
configureApi: false, configureApi: false,

View file

@ -1,15 +1,18 @@
<template> <template>
<div class="notification is-danger"> <message variant="danger">
<i18n-t keypath="loadingError.failed"> <i18n-t keypath="loadingError.failed">
<a @click="reload">{{ $t('loadingError.tryAgain') }}</a> <a @click="reload">{{ $t('loadingError.tryAgain') }}</a>
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a> <a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
</i18n-t> </i18n-t>
</div> </message>
</template> </template>
<script> <script>
import Message from '@/components/misc/message'
export default { export default {
name: 'error', name: 'error',
components: {Message},
methods: { methods: {
reload() { reload() {
window.location.reload() window.location.reload()

View file

@ -4,13 +4,11 @@
<template v-for="(s, i) in shortcuts" :key="i"> <template v-for="(s, i) in shortcuts" :key="i">
<h3>{{ $t(s.title) }}</h3> <h3>{{ $t(s.title) }}</h3>
<div class="message is-primary"> <message>
<div class="message-body"> {{
{{ s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages') }}
}} </message>
</div>
</div>
<dl class="shortcut-list"> <dl class="shortcut-list">
<template v-for="(sc, si) in s.shortcuts" :key="si"> <template v-for="(sc, si) in s.shortcuts" :key="si">
@ -30,11 +28,15 @@
<script> <script>
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types' import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
import Shortcut from '@/components/misc/shortcut.vue' import Shortcut from '@/components/misc/shortcut.vue'
import Message from '@/components/misc/message'
import {KEYBOARD_SHORTCUTS} from './shortcuts' import {KEYBOARD_SHORTCUTS} from './shortcuts'
export default { export default {
name: 'keyboard-shortcuts', name: 'keyboard-shortcuts',
components: {Shortcut}, components: {
Message,
Shortcut,
},
data() { data() {
return { return {
shortcuts: KEYBOARD_SHORTCUTS, shortcuts: KEYBOARD_SHORTCUTS,

View file

@ -0,0 +1,41 @@
<template>
<div class="message" :class="variant">
<slot/>
</div>
</template>
<script setup>
defineProps({
variant: {
type: String,
default: 'info',
},
})
</script>
<style lang="scss" scoped>
.message {
padding: .75rem 1rem;
border-radius: $radius;
}
.info {
border: 1px solid var(--primary);
background: hsla(var(--primary-hsl), .05);
}
.danger {
border: 1px solid var(--danger);
background: hsla(var(--danger-h), var(--danger-s), var(--danger-l), .05);
}
.warning {
border: 1px solid var(--warning);
background: hsla(var(--warning-h), var(--warning-s), var(--warning-l), .05);
}
.success {
border: 1px solid var(--success);
background: hsla(var(--success-h), var(--success-s), var(--success-l), .05);
}
</style>

View file

@ -2,14 +2,9 @@
<div class="no-auth-wrapper"> <div class="no-auth-wrapper">
<div class="noauth-container"> <div class="noauth-container">
<Logo class="logo" width="400" height="117" /> <Logo class="logo" width="400" height="117" />
<div class="message is-info" v-if="motd !== ''"> <message v-if="motd !== ''" class="my-2">
<div class="message-header"> {{ motd }}
<p>{{ $t('misc.info') }}</p> </message>
</div>
<div class="message-body">
{{ motd }}
</div>
</div>
<slot/> <slot/>
</div> </div>
</div> </div>
@ -17,6 +12,7 @@
<script setup> <script setup>
import Logo from '@/components/home/Logo.vue' import Logo from '@/components/home/Logo.vue'
import message from '@/components/misc/message'
import {useStore} from 'vuex' import {useStore} from 'vuex'
import {computed} from 'vue' import {computed} from 'vue'

View file

@ -16,7 +16,7 @@
<p v-if="error === errorNoApiUrl"> <p v-if="error === errorNoApiUrl">
{{ $t('ready.noApiUrlConfigured') }} {{ $t('ready.noApiUrlConfigured') }}
</p> </p>
<div class="notification is-danger" v-else> <message variant="danger" v-else>
<p> <p>
{{ $t('ready.errorOccured') }}<br/> {{ $t('ready.errorOccured') }}<br/>
{{ error }} {{ error }}
@ -24,7 +24,7 @@
<p> <p>
{{ $t('ready.checkApiUrl') }} {{ $t('ready.checkApiUrl') }}
</p> </p>
</div> </message>
<api-config :configure-open="true" @found-api="load"/> <api-config :configure-open="true" @found-api="load"/>
</card> </card>
</no-auth-wrapper> </no-auth-wrapper>
@ -43,6 +43,7 @@
<script> <script>
import Logo from '@/assets/logo.svg?component' import Logo from '@/assets/logo.svg?component'
import ApiConfig from '@/components/misc/api-config' import ApiConfig from '@/components/misc/api-config'
import Message from '@/components/misc/message'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper' import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
import {mapState} from 'vuex' import {mapState} from 'vuex'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl' import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
@ -50,6 +51,7 @@ import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
export default { export default {
name: 'ready', name: 'ready',
components: { components: {
Message,
Logo, Logo,
NoAuthWrapper, NoAuthWrapper,
ApiConfig, ApiConfig,

View file

@ -193,10 +193,6 @@ export default {
align-items: center; align-items: center;
} }
.message-body {
padding: .5rem .75rem;
}
} }
} }

View file

@ -23,7 +23,7 @@
@import "bulma-css-variables/sass/elements/content"; @import "bulma-css-variables/sass/elements/content";
@import "bulma-css-variables/sass/elements/icon"; @import "bulma-css-variables/sass/elements/icon";
@import "bulma-css-variables/sass/elements/image"; @import "bulma-css-variables/sass/elements/image";
@import "bulma-css-variables/sass/elements/notification"; //@import "bulma-css-variables/sass/elements/notification"; // not used
@import "bulma-css-variables/sass/elements/progress"; @import "bulma-css-variables/sass/elements/progress";
@import "bulma-css-variables/sass/elements/table"; @import "bulma-css-variables/sass/elements/table";
@import "bulma-css-variables/sass/elements/tag"; @import "bulma-css-variables/sass/elements/tag";
@ -48,7 +48,7 @@
// @import "bulma-css-variables/sass/components/level"; // not used // @import "bulma-css-variables/sass/components/level"; // not used
@import "bulma-css-variables/sass/components/media"; @import "bulma-css-variables/sass/components/media";
@import "bulma-css-variables/sass/components/menu"; @import "bulma-css-variables/sass/components/menu";
@import "bulma-css-variables/sass/components/message"; //@import "bulma-css-variables/sass/components/message"; // not used
@import "bulma-css-variables/sass/components/modal"; @import "bulma-css-variables/sass/components/modal";
@import "bulma-css-variables/sass/components/navbar"; @import "bulma-css-variables/sass/components/navbar";
@import "bulma-css-variables/sass/components/pagination"; @import "bulma-css-variables/sass/components/pagination";

View file

@ -7,5 +7,4 @@
@import "form"; @import "form";
@import "link-share"; @import "link-share";
@import "loading"; @import "loading";
@import "notification";
@import "flatpickr"; @import "flatpickr";

View file

@ -1,12 +0,0 @@
.notification {
border: $thickness solid $border;
}
.notifications {
left: 0.5rem !important;
bottom: 1rem !important;
}
.message .message-body {
border: $thickness solid;
}

View file

@ -3,7 +3,7 @@
<h2 v-if="userInfo"> <h2 v-if="userInfo">
{{ $t(`home.welcome${welcome}`, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}! {{ $t(`home.welcome${welcome}`, {username: userInfo.name !== '' ? userInfo.name : userInfo.username}) }}!
</h2> </h2>
<div class="notification is-danger" v-if="deletionScheduledAt !== null"> <message variant="danger" v-if="deletionScheduledAt !== null">
{{ {{
$t('user.deletion.scheduled', { $t('user.deletion.scheduled', {
date: formatDateShort(deletionScheduledAt), date: formatDateShort(deletionScheduledAt),
@ -13,7 +13,7 @@
<router-link :to="{name: 'user.settings', hash: '#deletion'}"> <router-link :to="{name: 'user.settings', hash: '#deletion'}">
{{ $t('user.deletion.scheduledCancel') }} {{ $t('user.deletion.scheduledCancel') }}
</router-link> </router-link>
</div> </message>
<add-task <add-task
:listId="defaultListId" :listId="defaultListId"
@taskAdded="updateTaskList" @taskAdded="updateTaskList"
@ -57,6 +57,7 @@
<script> <script>
import {mapState} from 'vuex' import {mapState} from 'vuex'
import Message from '@/components/misc/message'
import ShowTasks from './tasks/ShowTasks.vue' import ShowTasks from './tasks/ShowTasks.vue'
import {getHistory} from '../modules/listHistory' import {getHistory} from '../modules/listHistory'
import ListCard from '@/components/list/partials/list-card.vue' import ListCard from '@/components/list/partials/list-card.vue'
@ -67,6 +68,7 @@ import {parseDateOrNull} from '../helpers/parseDateOrNull'
export default { export default {
name: 'Home', name: 'Home',
components: { components: {
Message,
ListCard, ListCard,
ShowTasks, ShowTasks,
AddTask, AddTask,
@ -147,7 +149,7 @@ export default {
flex-wrap: wrap; flex-wrap: wrap;
max-height: calc(#{$list-height * 2} + #{$list-spacing * 2} - 4px); max-height: calc(#{$list-height * 2} + #{$list-spacing * 2} - 4px);
overflow: hidden; overflow: hidden;
@media screen and (max-width: $mobile) { @media screen and (max-width: $mobile) {
max-height: calc(#{$list-height * 4} + #{$list-spacing * 4} - 4px); max-height: calc(#{$list-height * 4} + #{$list-spacing * 4} - 4px);
} }

View file

@ -36,9 +36,9 @@
</div> </div>
</div> </div>
<transition name="fade"> <transition name="fade">
<div class="notification is-warning" v-if="currentList.isArchived"> <message variant="warning" v-if="currentList.isArchived" class="mb-4">
{{ $t('list.archived') }} {{ $t('list.archived') }}
</div> </message>
</transition> </transition>
<router-view/> <router-view/>
@ -46,6 +46,7 @@
</template> </template>
<script> <script>
import Message from '@/components/misc/message'
import ListModel from '../../models/list' import ListModel from '../../models/list'
import ListService from '../../services/list' import ListService from '../../services/list'
import {CURRENT_LIST} from '../../store/mutation-types' import {CURRENT_LIST} from '../../store/mutation-types'
@ -53,6 +54,7 @@ import {getListView} from '../../helpers/saveListView'
import {saveListToHistory} from '../../modules/listHistory' import {saveListToHistory} from '../../modules/listHistory'
export default { export default {
components: {Message},
data() { data() {
return { return {
listService: new ListService(), listService: new ListService(),

View file

@ -39,7 +39,7 @@
<div class="migration-in-progress"> <div class="migration-in-progress">
<img :alt="migrator.name" :src="migrator.icon" class="logo"/> <img :alt="migrator.name" :src="migrator.icon" class="logo"/>
<div class="progress-dots"> <div class="progress-dots">
<span v-for="i in progressDotsCount" :key="i" /> <span v-for="i in progressDotsCount" :key="i"/>
</div> </div>
<Logo class="logo"/> <Logo class="logo"/>
</div> </div>
@ -57,11 +57,9 @@
</div> </div>
</div> </div>
<div v-else> <div v-else>
<div class="message is-primary"> <message class="mb-4">
<div class="message-body"> {{ message }}
{{ message }} </message>
</div>
</div>
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button> <x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
</div> </div>
</div> </div>
@ -71,6 +69,7 @@
import AbstractMigrationService from '@/services/migrator/abstractMigration' import AbstractMigrationService from '@/services/migrator/abstractMigration'
import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile' import AbstractMigrationFileService from '@/services/migrator/abstractMigrationFile'
import Logo from '@/assets/logo.svg?component' import Logo from '@/assets/logo.svg?component'
import Message from '@/components/misc/message'
import {MIGRATORS} from './migrators' import {MIGRATORS} from './migrators'
@ -79,7 +78,10 @@ const PROGRESS_DOTS_COUNT = 8
export default { export default {
name: 'MigrateService', name: 'MigrateService',
components: { Logo }, components: {
Logo,
Message,
},
data() { data() {
return { return {
@ -101,7 +103,7 @@ export default {
beforeRouteEnter(to) { beforeRouteEnter(to) {
if (MIGRATORS[to.params.service] === undefined) { if (MIGRATORS[to.params.service] === undefined) {
return { name: 'not-found' } return {name: 'not-found'}
} }
}, },
@ -122,7 +124,7 @@ export default {
if (this.migrator.isFileMigrator) { if (this.migrator.isFileMigrator) {
return return
} }
this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url) this.authUrl = await this.migrationService.getAuthUrl().then(({url}) => url)
this.migratorAuthCode = location.hash.startsWith('#token=') this.migratorAuthCode = location.hash.startsWith('#token=')
@ -150,7 +152,7 @@ export default {
this.lastMigrationDate = null this.lastMigrationDate = null
this.message = '' this.message = ''
let migrationConfig = { code: this.migratorAuthCode } let migrationConfig = {code: this.migratorAuthCode}
if (this.migrator.isFileMigrator) { if (this.migrator.isFileMigrator) {
if (this.$refs.uploadInput.files.length === 0) { if (this.$refs.uploadInput.files.length === 0) {
@ -160,7 +162,7 @@ export default {
} }
try { try {
const { message } = await this.migrationService.migrate(migrationConfig) const {message} = await this.migrationService.migrate(migrationConfig)
this.message = message this.message = message
return this.$store.dispatch('namespaces/loadNamespaces') return this.$store.dispatch('namespaces/loadNamespaces')
} finally { } finally {
@ -173,24 +175,24 @@ export default {
<style lang="scss" scoped> <style lang="scss" scoped>
.migration-in-progress-container { .migration-in-progress-container {
max-width: 400px; max-width: 400px;
margin: 4rem auto 0; margin: 4rem auto 0;
text-align: center; text-align: center;
} }
.migration-in-progress { .migration-in-progress {
text-align: center; text-align: center;
display: flex; display: flex;
max-width: 400px; max-width: 400px;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 2rem; margin-bottom: 2rem;
} }
.logo { .logo {
display: block; display: block;
max-height: 100px; max-height: 100px;
max-width: 100px; max-width: 100px;
} }
.progress-dots { .progress-dots {

View file

@ -28,19 +28,20 @@
<div class="field"> <div class="field">
<label class="label">{{ $t('namespace.attributes.color') }}</label> <label class="label">{{ $t('namespace.attributes.color') }}</label>
<div class="control"> <div class="control">
<color-picker v-model="namespace.hexColor" /> <color-picker v-model="namespace.hexColor"/>
</div> </div>
</div> </div>
<div class="notification is-info mt-4"> <message class="mt-4">
<h4 class="title">{{ $t('namespace.create.tooltip') }}</h4> <h4 class="title">{{ $t('namespace.create.tooltip') }}</h4>
{{ $t('namespace.create.explanation') }} {{ $t('namespace.create.explanation') }}
</div> </message>
</create-edit> </create-edit>
</template> </template>
<script> <script>
import Message from '@/components/misc/message'
import NamespaceModel from '../../models/namespace' import NamespaceModel from '../../models/namespace'
import NamespaceService from '../../services/namespace' import NamespaceService from '../../services/namespace'
import CreateEdit from '@/components/misc/create-edit.vue' import CreateEdit from '@/components/misc/create-edit.vue'
@ -56,6 +57,7 @@ export default {
} }
}, },
components: { components: {
Message,
ColorPicker, ColorPicker,
CreateEdit, CreateEdit,
}, },
@ -72,7 +74,7 @@ export default {
const namespace = await this.namespaceService.create(this.namespace) const namespace = await this.namespaceService.create(this.namespace)
this.$store.commit('namespaces/addNamespace', namespace) this.$store.commit('namespaces/addNamespace', namespace)
this.$message.success({message: this.$t('namespace.create.success') }) this.$message.success({message: this.$t('namespace.create.success')})
this.$router.back() this.$router.back()
}, },
}, },

View file

@ -1,8 +1,8 @@
<template> <template>
<div> <div>
<div class="notification is-info is-light has-text-centered" v-if="loading"> <message v-if="loading">
{{ $t('sharing.authenticating') }} {{ $t('sharing.authenticating') }}
</div> </message>
<div v-if="authenticateWithPassword" class="box"> <div v-if="authenticateWithPassword" class="box">
<p class="pb-2"> <p class="pb-2">
{{ $t('sharing.passwordRequired') }} {{ $t('sharing.passwordRequired') }}
@ -25,18 +25,20 @@
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
<div class="notification is-danger mt-4" v-if="errorMessage !== ''"> <message variant="danger" class="mt-4" v-if="errorMessage !== ''">
{{ errorMessage }} {{ errorMessage }}
</div> </message>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import {mapGetters} from 'vuex' import {mapGetters} from 'vuex'
import Message from '@/components/misc/message'
export default { export default {
name: 'LinkSharingAuth', name: 'LinkSharingAuth',
components: {Message},
data() { data() {
return { return {
loading: true, loading: true,
@ -72,7 +74,7 @@ export default {
password: this.password, password: this.password,
}) })
this.$router.push({name: 'list.list', params: {listId: r.list_id}}) this.$router.push({name: 'list.list', params: {listId: r.list_id}})
} catch(e) { } catch (e) {
if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13001) { if (typeof e.response.data.code !== 'undefined' && e.response.data.code === 13001) {
this.authenticateWithPassword = true this.authenticateWithPassword = true
return return

View file

@ -2,9 +2,9 @@
<div> <div>
<h2 class="title has-text-centered">Login</h2> <h2 class="title has-text-centered">Login</h2>
<div class="box"> <div class="box">
<div class="notification is-success has-text-centered" v-if="confirmedEmailSuccess"> <message variant="success" class="has-text-centered" v-if="confirmedEmailSuccess">
{{ $t('user.auth.confirmEmailSuccess') }} {{ $t('user.auth.confirmEmailSuccess') }}
</div> </message>
<api-config @foundApi="hasApiUrl = true"/> <api-config @foundApi="hasApiUrl = true"/>
<form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled"> <form @submit.prevent="submit" id="loginform" v-if="hasApiUrl && localAuthEnabled">
<div class="field"> <div class="field">
@ -78,9 +78,9 @@
</router-link> </router-link>
</div> </div>
</div> </div>
<div class="notification is-danger" v-if="errorMessage"> <message variant="danger" v-if="errorMessage">
{{ errorMessage }} {{ errorMessage }}
</div> </message>
</form> </form>
<div <div
@ -110,11 +110,13 @@ import {LOADING} from '@/store/mutation-types'
import legal from '../../components/misc/legal' import legal from '../../components/misc/legal'
import ApiConfig from '@/components/misc/api-config.vue' import ApiConfig from '@/components/misc/api-config.vue'
import {getErrorText} from '@/message' import {getErrorText} from '@/message'
import Message from '@/components/misc/message'
import {redirectToProvider} from '../../helpers/redirectToProvider' import {redirectToProvider} from '../../helpers/redirectToProvider'
import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited' import {getLastVisited, clearLastVisited} from '../../helpers/saveLastVisited'
export default { export default {
components: { components: {
Message,
ApiConfig, ApiConfig,
legal, legal,
}, },
@ -149,7 +151,7 @@ export default {
const last = getLastVisited() const last = getLastVisited()
if (last !== null) { if (last !== null) {
this.$router.push({ this.$router.push({
name: last.name, name: last.name,
params: last.params, params: last.params,
}) })
clearLastVisited() clearLastVisited()
@ -206,7 +208,7 @@ export default {
try { try {
await this.$store.dispatch('auth/login', credentials) await this.$store.dispatch('auth/login', credentials)
this.$store.commit('auth/needsTotpPasscode', false) this.$store.commit('auth/needsTotpPasscode', false)
} catch(e) { } catch (e) {
if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) { if (e.response && e.response.data.code === 1017 && !credentials.totpPasscode) {
return return
} }

View file

@ -1,11 +1,11 @@
<template> <template>
<div> <div>
<div class="notification is-danger" v-if="errorMessage"> <message variant="danger" v-if="errorMessage">
{{ errorMessage }} {{ errorMessage }}
</div> </message>
<div class="notification is-info" v-if="loading"> <message v-if="loading">
{{ $t('user.auth.authenticating') }} {{ $t('user.auth.authenticating') }}
</div> </message>
</div> </div>
</template> </template>
@ -14,10 +14,12 @@ import {mapState} from 'vuex'
import {LOADING} from '@/store/mutation-types' import {LOADING} from '@/store/mutation-types'
import {getErrorText} from '@/message' import {getErrorText} from '@/message'
import Message from '@/components/misc/message'
import {clearLastVisited, getLastVisited} from '../../helpers/saveLastVisited' import {clearLastVisited, getLastVisited} from '../../helpers/saveLastVisited'
export default { export default {
name: 'Auth', name: 'Auth',
components: {Message},
data() { data() {
return { return {
errorMessage: '', errorMessage: '',

View file

@ -45,37 +45,37 @@
</x-button> </x-button>
</div> </div>
</div> </div>
<div class="notification is-info" v-if="this.passwordResetService.loading"> <message v-if="this.passwordResetService.loading">
{{ $t('misc.loading') }} {{ $t('misc.loading') }}
</div> </message>
<div class="notification is-danger" v-if="errorMsg"> <message v-if="errorMsg">
{{ errorMsg }} {{ errorMsg }}
</div> </message>
</form> </form>
<div class="has-text-centered" v-if="successMessage"> <div class="has-text-centered" v-if="successMessage">
<div class="notification is-success"> <message variant="success">
{{ successMessage }} {{ successMessage }}
</div> </message>
<x-button :to="{ name: 'user.login' }"> <x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
</div> </div>
<Legal /> <Legal/>
</div> </div>
</div> </div>
</template> </template>
<script setup> <script setup>
import {ref, reactive} from 'vue' import {ref, reactive} from 'vue'
import { useI18n } from 'vue-i18n' import {useI18n} from 'vue-i18n'
import Legal from '@/components/misc/legal' import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset' import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset' import PasswordResetService from '@/services/passwordReset'
import { useTitle } from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import Message from '@/components/misc/message'
const { t } = useI18n() const {t} = useI18n()
useTitle(() => t('user.auth.resetPassword')) useTitle(() => t('user.auth.resetPassword'))
const credentials = reactive({ const credentials = reactive({
@ -97,10 +97,10 @@ async function submit() {
const passwordReset = new PasswordResetModel({newPassword: credentials.password}) const passwordReset = new PasswordResetModel({newPassword: credentials.password})
try { try {
const { message } = passwordResetService.resetPassword(passwordReset) const {message} = passwordResetService.resetPassword(passwordReset)
successMessage.value = message successMessage.value = message
localStorage.removeItem('passwordResetToken') localStorage.removeItem('passwordResetToken')
} catch(e) { } catch (e) {
errorMsg.value = e.response.data.message errorMsg.value = e.response.data.message
} }
} }

View file

@ -83,12 +83,12 @@
</x-button> </x-button>
</div> </div>
</div> </div>
<div class="notification is-info" v-if="loading"> <message v-if="loading">
{{ $t('misc.loading') }} {{ $t('misc.loading') }}
</div> </message>
<div class="notification is-danger" v-if="errorMessage !== ''"> <message variant="danger" v-if="errorMessage !== ''">
{{ errorMessage }} {{ errorMessage }}
</div> </message>
</form> </form>
<legal/> <legal/>
</div> </div>
@ -97,13 +97,13 @@
<script setup> <script setup>
import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue' import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
import { useI18n } from 'vue-i18n' import {useI18n} from 'vue-i18n'
import router from '@/router' import router from '@/router'
import { store } from '@/store' import {store} from '@/store'
import { useTitle } from '@/composables/useTitle' import {useTitle} from '@/composables/useTitle'
import Legal from '@/components/misc/legal' import Legal from '@/components/misc/legal'
import Message from '@/components/misc/message'
// FIXME: use the `beforeEnter` hook of vue-router // FIXME: use the `beforeEnter` hook of vue-router
// Check if the user is already logged in, if so, redirect them to the homepage // Check if the user is already logged in, if so, redirect them to the homepage
@ -113,7 +113,7 @@ onBeforeMount(() => {
} }
}) })
const { t } = useI18n() const {t} = useI18n()
useTitle(() => t('user.auth.register')) useTitle(() => t('user.auth.register'))
const credentials = reactive({ const credentials = reactive({
@ -137,7 +137,7 @@ async function submit() {
try { try {
await store.dispatch('auth/register', toRaw(credentials)) await store.dispatch('auth/register', toRaw(credentials))
} catch(e) { } catch (e) {
errorMessage.value = e.message errorMessage.value = e.message
} }
} }

View file

@ -31,14 +31,14 @@
</x-button> </x-button>
</div> </div>
</div> </div>
<div class="notification is-danger" v-if="errorMsg"> <message variant="danger" v-if="errorMsg">
{{ errorMsg }} {{ errorMsg }}
</div> </message>
</form> </form>
<div class="has-text-centered" v-if="isSuccess"> <div class="has-text-centered" v-if="isSuccess">
<div class="notification is-success"> <message variant="success">
{{ $t('user.auth.resetPasswordSuccess') }} {{ $t('user.auth.resetPasswordSuccess') }}
</div> </message>
<x-button :to="{ name: 'user.login' }"> <x-button :to="{ name: 'user.login' }">
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
@ -57,6 +57,7 @@ import Legal from '@/components/misc/legal'
import PasswordResetModel from '@/models/passwordReset' import PasswordResetModel from '@/models/passwordReset'
import PasswordResetService from '@/services/passwordReset' import PasswordResetService from '@/services/passwordReset'
import { useTitle } from '@/composables/useTitle' import { useTitle } from '@/composables/useTitle'
import Message from '@/components/misc/message'
const { t } = useI18n() const { t } = useI18n()
useTitle(() => t('user.auth.resetPassword')) useTitle(() => t('user.auth.resetPassword'))