feature/use-setup-api-for-user-and-about-pages (#929)

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#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: '',
errPasswordRequired: false,
}
},
created() {
this.dataExportService = new DataExportService()
},
computed: {
isLocalUser() {
return this.$store.state.auth.info?.isLocalUser
},
},
methods: {
download() {
if (this.password === '' && this.isLocalUser) {
this.errPasswordRequired = true
this.$refs.passwordInput.focus()
return
}
this.dataExportService.download(this.password) const isLocalUser = computed(() => store.state.auth.info?.isLocalUser)
},
}, function download() {
if (password.value === '' && isLocalUser.value) {
errPasswordRequired.value = true
passwordInput.value.focus()
return
}
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,
},
data() {
return {
passwordResetService: new PasswordResetService(),
credentials: {
password: '',
password2: '',
},
errorMsg: '',
successMessage: '',
}
},
mounted() { import PasswordResetModel from '@/models/passwordReset'
this.setTitle(this.$t('user.auth.resetPassword')) import PasswordResetService from '@/services/passwordReset'
}, import { useTitle } from '@/composables/useTitle'
methods: { const { t } = useI18n()
async submit() { useTitle(() => t('user.auth.resetPassword'))
this.errorMsg = ''
if (this.credentials.password2 !== this.credentials.password) { const credentials = reactive({
this.errorMsg = this.$t('user.auth.passwordsDontMatch') password: '',
return password2: '',
} })
let passwordReset = new PasswordResetModel({newPassword: this.credentials.password}) const passwordResetService = reactive(new PasswordResetService())
try { const errorMsg = ref('')
const { message } = this.passwordResetService.resetPassword(passwordReset) const successMessage = ref('')
this.successMessage = message
localStorage.removeItem('passwordResetToken') async function submit() {
} catch(e) { errorMsg.value = ''
this.errorMsg = e.response.data.message
} if (credentials.password2 !== credentials.password) {
}, errorMsg.value = t('user.auth.passwordsDontMatch')
}, return
}
const passwordReset = new PasswordResetModel({newPassword: credentials.password})
try {
const { message } = passwordResetService.resetPassword(passwordReset)
successMessage.value = message
localStorage.removeItem('passwordResetToken')
} catch(e) {
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() {
return {
credentials: {
username: '',
email: '',
password: '',
password2: '',
},
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) { import Legal from '@/components/misc/legal'
this.errorMessage = this.$t('user.auth.passwordsDontMatch')
return
}
const credentials = { // FIXME: use the `beforeEnter` hook of vue-router
username: this.credentials.username, // Check if the user is already logged in, if so, redirect them to the homepage
email: this.credentials.email, onBeforeMount(() => {
password: this.credentials.password, if (store.state.auth.authenticated) {
} router.push({name: 'home'})
}
})
try { const { t } = useI18n()
await this.$store.dispatch('auth/register', credentials) useTitle(() => t('user.auth.register'))
} catch(e) {
this.errorMessage = e.message const credentials = reactive({
} username: '',
}, email: '',
}, password: '',
})
const passwordValidation = ref('')
const loading = computed(() => store.state.loading)
const errorMessage = ref('')
async function submit() {
errorMessage.value = ''
if (credentials.password !== passwordValidation.value) {
errorMessage.value = t('user.auth.passwordsDontMatch')
return
}
try {
await store.dispatch('auth/register', toRaw(credentials))
} catch(e) {
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 passwordResetService.requestResetPassword(passwordReset.value)
await this.passwordResetService.requestResetPassword(this.passwordReset) isSuccess.value = true
this.isSuccess = true } catch(e) {
} catch(e) { errorMsg.value = e.response.data.message
this.errorMsg = 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>