feature/use-setup-api-for-user-and-about-pages (#929)
All checks were successful
continuous-integration/drone/push Build is passing

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #929
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
This commit is contained in:
dpschen 2021-11-14 20:57:36 +00:00 committed by konrad
parent 507a73e74c
commit d0d4096f8b
9 changed files with 167 additions and 202 deletions

View File

@ -24,8 +24,8 @@ context('Registration', () => {
cy.visit('/register') cy.visit('/register')
cy.get('#username').type(fixture.username) cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email) cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password) cy.get('#password').type(fixture.password)
cy.get('#password2').type(fixture.password) cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click() cy.get('#register-submit').click()
cy.url().should('include', '/') cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00 cy.clock(1625656161057) // 13:00
@ -42,8 +42,8 @@ context('Registration', () => {
cy.visit('/register') cy.visit('/register')
cy.get('#username').type(fixture.username) cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email) cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password) cy.get('#password').type(fixture.password)
cy.get('#password2').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.notification.is-danger').contains('A user with this username already exists.')
}) })

View File

@ -0,0 +1,12 @@
import { computed, watchEffect } from 'vue'
import { setTitle } from '@/helpers/setTitle'
import { ComputedGetter, ComputedRef } from '@vue/reactivity'
export function useTitle<T>(titleGetter: ComputedGetter<T>) : ComputedRef<T> {
const titleRef = computed(titleGetter)
watchEffect(() => setTitle(titleRef.value))
return titleRef
}

View File

@ -1,8 +1,5 @@
export const setTitle = title => { export function setTitle(title) {
if (typeof title === 'undefined' || title === '') { document.title = (typeof title === 'undefined' || title === '')
document.title = 'Vikunja' ? 'Vikunja'
return : `${title} | Vikunja`
}
document.title = `${title} | Vikunja`
} }

View File

@ -13,10 +13,10 @@
> >
<div class="p-4"> <div class="p-4">
<p> <p>
{{ $t('about.frontendVersion', {version: this.frontendVersion}) }} {{ $t('about.frontendVersion', {version: frontendVersion}) }}
</p> </p>
<p> <p>
{{ $t('about.apiVersion', {version: this.apiVersion}) }} {{ $t('about.apiVersion', {version: apiVersion}) }}
</p> </p>
</div> </div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end"> <footer class="modal-card-foot is-flex is-justify-content-flex-end">
@ -32,18 +32,11 @@
</template> </template>
<script> <script setup>
import {VERSION} from '../version.json' import {computed} from 'vue'
export default { import { store } from '@/store'
name: 'About', import {VERSION as frontendVersion} from '@/version.json'
computed: {
frontendVersion() { const apiVersion = computed(() => store.state.config.version)
return VERSION
},
apiVersion() {
return this.$store.state.config.version
},
},
}
</script> </script>

View File

@ -35,36 +35,25 @@
</div> </div>
</template> </template>
<script> <script setup>
import DataExportService from '../../services/dataExport' import {ref, computed, reactive} from 'vue'
import DataExportService from '@/services/dataExport'
import {store} from '@/store'
export default { const dataExportService = reactive(new DataExportService())
name: 'data-export-download', const password = ref('')
data() { const errPasswordRequired = ref(false)
return { const passwordInput = ref(null)
dataExportService: DataExportService,
password: '', const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
errPasswordRequired: false,
} function download() {
}, if (password.value === '' && isLocalUser.value) {
created() { errPasswordRequired.value = true
this.dataExportService = new DataExportService() passwordInput.value.focus()
},
computed: {
isLocalUser() {
return this.$store.state.auth.info?.isLocalUser
},
},
methods: {
download() {
if (this.password === '' && this.isLocalUser) {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return return
} }
this.dataExportService.download(this.password) dataExportService.download(password.value)
},
},
} }
</script> </script>

View File

@ -60,55 +60,49 @@
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
</div> </div>
<legal/> <Legal />
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import PasswordResetModel from '../../models/passwordReset' import {ref, reactive} from 'vue'
import PasswordResetService from '../../services/passwordReset' import { useI18n } from 'vue-i18n'
import Legal from '../../components/misc/legal'
export default { import Legal from '@/components/misc/legal'
components: {
Legal, import PasswordResetModel from '@/models/passwordReset'
}, import PasswordResetService from '@/services/passwordReset'
data() { import { useTitle } from '@/composables/useTitle'
return {
passwordResetService: new PasswordResetService(), const { t } = useI18n()
credentials: { useTitle(() => t('user.auth.resetPassword'))
const credentials = reactive({
password: '', password: '',
password2: '', password2: '',
}, })
errorMsg: '',
successMessage: '',
}
},
mounted() { const passwordResetService = reactive(new PasswordResetService())
this.setTitle(this.$t('user.auth.resetPassword')) const errorMsg = ref('')
}, const successMessage = ref('')
methods: { async function submit() {
async submit() { errorMsg.value = ''
this.errorMsg = ''
if (this.credentials.password2 !== this.credentials.password) { if (credentials.password2 !== credentials.password) {
this.errorMsg = this.$t('user.auth.passwordsDontMatch') errorMsg.value = t('user.auth.passwordsDontMatch')
return return
} }
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password}) const passwordReset = new PasswordResetModel({newPassword: credentials.password})
try { try {
const { message } = this.passwordResetService.resetPassword(passwordReset) const { message } = passwordResetService.resetPassword(passwordReset)
this.successMessage = message successMessage.value = message
localStorage.removeItem('passwordResetToken') localStorage.removeItem('passwordResetToken')
} catch(e) { } catch(e) {
this.errorMsg = e.response.data.message errorMsg.value = e.response.data.message
} }
},
},
} }
</script> </script>

View File

@ -36,12 +36,12 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label class="label" for="password1">{{ $t('user.auth.password') }}</label> <label class="label" for="password">{{ $t('user.auth.password') }}</label>
<div class="control"> <div class="control">
<input <input
class="input" class="input"
id="password1" id="password"
name="password1" name="password"
:placeholder="$t('user.auth.passwordPlaceholder')" :placeholder="$t('user.auth.passwordPlaceholder')"
required required
type="password" type="password"
@ -52,17 +52,17 @@
</div> </div>
</div> </div>
<div class="field"> <div class="field">
<label class="label" for="password2">{{ $t('user.auth.passwordRepeat') }}</label> <label class="label" for="passwordValidation">{{ $t('user.auth.passwordRepeat') }}</label>
<div class="control"> <div class="control">
<input <input
class="input" class="input"
id="password2" id="passwordValidation"
name="password2" name="passwordValidation"
:placeholder="$t('user.auth.passwordPlaceholder')" :placeholder="$t('user.auth.passwordPlaceholder')"
required required
type="password" type="password"
autocomplete="new-password" autocomplete="new-password"
v-model="credentials.password2" v-model="passwordValidation"
@keyup.enter="submit" @keyup.enter="submit"
/> />
</div> </div>
@ -95,61 +95,50 @@
</div> </div>
</template> </template>
<script> <script setup>
import router from '../../router' import {ref, reactive, toRaw, computed, onBeforeMount} from 'vue'
import {mapState} from 'vuex' import { useI18n } from 'vue-i18n'
import {LOADING} from '@/store/mutation-types'
import Legal from '../../components/misc/legal'
export default { import router from '@/router'
components: { import { store } from '@/store'
Legal, import { useTitle } from '@/composables/useTitle'
},
data() { import Legal from '@/components/misc/legal'
return {
credentials: { // FIXME: use the `beforeEnter` hook of vue-router
// Check if the user is already logged in, if so, redirect them to the homepage
onBeforeMount(() => {
if (store.state.auth.authenticated) {
router.push({name: 'home'})
}
})
const { t } = useI18n()
useTitle(() => t('user.auth.register'))
const credentials = reactive({
username: '', username: '',
email: '', email: '',
password: '', password: '',
password2: '', })
}, const passwordValidation = ref('')
errorMessage: '',
}
},
beforeMount() {
// Check if the user is already logged in, if so, redirect them to the homepage
if (this.authenticated) {
router.push({name: 'home'})
}
},
mounted() {
this.setTitle(this.$t('user.auth.register'))
},
computed: mapState({
authenticated: state => state.auth.authenticated,
loading: LOADING,
}),
methods: {
async submit() {
this.errorMessage = ''
if (this.credentials.password2 !== this.credentials.password) { const loading = computed(() => store.state.loading)
this.errorMessage = this.$t('user.auth.passwordsDontMatch') const errorMessage = ref('')
async function submit() {
errorMessage.value = ''
if (credentials.password !== passwordValidation.value) {
errorMessage.value = t('user.auth.passwordsDontMatch')
return return
} }
const credentials = {
username: this.credentials.username,
email: this.credentials.email,
password: this.credentials.password,
}
try { try {
await this.$store.dispatch('auth/register', credentials) await store.dispatch('auth/register', toRaw(credentials))
} catch(e) { } catch(e) {
this.errorMessage = e.message errorMessage.value = e.message
} }
},
},
} }
</script> </script>

View File

@ -43,42 +43,38 @@
{{ $t('user.auth.login') }} {{ $t('user.auth.login') }}
</x-button> </x-button>
</div> </div>
<legal/> <Legal />
</div> </div>
</div> </div>
</template> </template>
<script> <script setup>
import PasswordResetModel from '../../models/passwordReset' import {ref, reactive} from 'vue'
import PasswordResetService from '../../services/passwordReset' import { useI18n } from 'vue-i18n'
import Legal from '../../components/misc/legal'
export default { import Legal from '@/components/misc/legal'
components: {
Legal, import PasswordResetModel from '@/models/passwordReset'
}, import PasswordResetService from '@/services/passwordReset'
data() { import { useTitle } from '@/composables/useTitle'
return {
passwordResetService: new PasswordResetService(), const { t } = useI18n()
passwordReset: new PasswordResetModel(), useTitle(() => t('user.auth.resetPassword'))
errorMsg: '',
isSuccess: false, // Not sure if this instance needs a shalloRef at all
} const passwordResetService = reactive(new PasswordResetService())
}, const passwordReset = ref(new PasswordResetModel())
mounted() { const errorMsg = ref('')
this.setTitle(this.$t('user.auth.resetPassword')) const isSuccess = ref(false)
},
methods: { async function submit() {
async submit() { errorMsg.value = ''
this.errorMsg = ''
try { try {
await this.passwordResetService.requestResetPassword(this.passwordReset) await passwordResetService.requestResetPassword(passwordReset.value)
this.isSuccess = true isSuccess.value = true
} catch(e) { } catch(e) {
this.errorMsg = e.response.data.message errorMsg.value = e.response.data.message
} }
},
},
} }
</script> </script>

View File

@ -57,24 +57,19 @@
</div> </div>
</template> </template>
<script> <script setup>
import {mapState} from 'vuex' import {computed} from 'vue'
import { store } from '@/store'
import { useI18n } from 'vue-i18n'
import { useTitle } from '@/composables/useTitle'
export default { const { t } = useI18n()
name: 'Settings', useTitle(() => t('user.settings.title'))
mounted() {
this.setTitle(this.$t('user.settings.title')) const totpEnabled = computed(() => store.state.config.totpEnabled)
}, const caldavEnabled = computed(() => store.state.config.caldavEnabled)
computed: { const migratorsEnabled = computed(() => store.getters['config/migratorsEnabled'])
...mapState('config', ['totpEnabled', 'caldavEnabled']), const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
migratorsEnabled() {
return this.$store.getters['config/migratorsEnabled']
},
isLocalUser() {
return this.$store.state.auth.info?.isLocalUser
},
},
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>